秘訣は型クラスを使うことです。の場合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)