秘訣は型クラスを使うことです。の場合printf
、キーはPrintfType
型クラスです。メソッドは公開されていませんが、重要な部分はとにかく型にあります。
class PrintfType r
printf :: PrintfType r => String -> r
だから、printf
オーバーロードされた戻り値の型を持っています。私たちがインスタンス化できるようにする必要がありますので、些細なケースでは、我々は、余分な引数を持っていないr
しIO ()
。このため、インスタンスがあります
instance PrintfType (IO ())
次に、可変数の引数をサポートするために、インスタンスレベルで再帰を使用する必要があります。場合ように、特に、我々は、インスタンスが必要r
であるPrintfType
、関数型でx -> r
もありますPrintfType
。
-- instance PrintfType r => PrintfType (x -> r)
もちろん、実際にフォーマットできる引数のみをサポートする必要があります。ここで2番目のタイプのクラスPrintfArg
が登場します。実際のインスタンスは
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
これは、Show
クラスの任意の数の引数を取り、それらを出力するだけの単純化されたバージョンです。
{-# LANGUAGE FlexibleInstances #-}
foo :: FooType a => a
foo = bar (return ())
class FooType a where
bar :: IO () -> a
instance FooType (IO ()) where
bar = id
instance (Show x, FooType r) => FooType (x -> r) where
bar s x = bar (s >> print x)
ここでbar
は、引数がなくなるまで再帰的に作成されるIOアクションを実行します。その時点で、引数を実行します。
*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
QuickCheckも同じ手法を使用します。この場合、Testable
クラスには基本ケースのインスタンスがあり、クラスでBool
引数を取る関数には再帰インスタンスがありますArbitrary
。
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)