109

Haskell の型安全性は、型依存型言語に勝るものはありません。しかし、 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
4

1 に答える 1

133

秘訣は型クラスを使用することです。の場合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) 
于 2011-10-19T21:49:39.030 に答える