web-dev-qa-db-ja.com

Haskell printfはどのように機能しますか?

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
96
Dan Burton

トリックは型クラスを使用することです。 printfの場合、キーはPrintfType型クラスです。メソッドを公開しませんが、とにかく型に重要な部分があります。

_class PrintfType r
printf :: PrintfType r => String -> r
_

したがって、printfには戻り型がオーバーロードされています。些細なケースでは、追加の引数がないため、rIO ()にインスタンス化できる必要があります。このために、インスタンスがあります

_instance PrintfType (IO ())
_

次に、可変数の引数をサポートするには、インスタンスレベルで再帰を使用する必要があります。特に、rPrintfTypeである場合、関数タイプ_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) 
_
125
hammar