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