1. ホーム
  2. haskell

[解決済み] Haskellのprintfはどのように動作するのですか?

2022-11-05 05:40:01

質問

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 はオーバーロードされた戻り値の型を持っています。つまらないケースですが、余分な引数はないので、インスタンス化された 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)