Haskellの型安全性は2番目です 誰にも 依存型付けされた言語のみ。しかし、 Text.Printf で行われているいくつかの深い魔法がありますが、それはむしろ型に合わないようです。
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
この背後にある深い魔法とは何ですか? Text.Printf.printf
関数はこのような可変引数を取りますか?
Haskellで可変引数を許可するために使用される一般的な手法は何ですか?また、どのように機能しますか?
(補足:この手法を使用すると、一部のタイプセーフティが明らかに失われます。)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
トリックは型クラスを使用することです。 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)
_