Haskell printf如何工作?

Dan*_*ton 99 printf haskell variadic-functions polyvariadic

Haskell的类型安全性仅对于依赖类型的语言是首屈一指的.但是Text.Printf有一些深刻的魔力似乎相当类型.

> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
Run Code Online (Sandbox Code Playgroud)

这背后的深层魔力是什么?该Text.Printf.printf函数如何采用像这样的可变参数?

用于允许Haskell中的可变参数的一般技术是什么,它是如何工作的?

(旁注:使用这​​种技术时,某种类型的安全性显然会丢失.)

> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
Run Code Online (Sandbox Code Playgroud)

ham*_*mar 128

诀窍是使用类型类.在这种情况下printf,键是PrintfType类型类.它没有暴露任何方法,但重要的部分是在类型中无论如何.

class PrintfType r
printf :: PrintfType r => String -> r
Run Code Online (Sandbox Code Playgroud)

所以printf有一个重载的返回类型.在平凡的情况下,我们没有额外的参数,所以我们需要能够实例rIO ().为此,我们有实例

instance PrintfType (IO ())
Run Code Online (Sandbox Code Playgroud)

接下来,为了支持可变数量的参数,我们需要在实例级别使用递归.特别是我们需要一个实例,以便if r是a PrintfType,函数类型x -> r也是a PrintfType.

-- instance PrintfType r => PrintfType (x -> r)
Run Code Online (Sandbox Code Playgroud)

当然,我们只想支持实际可以格式化的参数.这就是第二个类型的PrintfArg用武之地.所以实际的实例是

instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Run Code Online (Sandbox Code Playgroud)

这是一个简化版本,它接受类中的任意数量的参数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)
Run Code Online (Sandbox Code Playgroud)

这里,bar采取递归建立的IO动作,直到没有更多参数为止,此时我们只需执行它.

*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
Run Code Online (Sandbox Code Playgroud)

QuickCheck也使用相同的技术,其中Testable类具有基本情况的实例Bool,以及用于在Arbitrary类中接受参数的函数的递归技术.

class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r) 
Run Code Online (Sandbox Code Playgroud)

  • @ThomasEding 编译器拒绝 `printf "%d" True` 的原因是因为没有 `PrintfArg` 的 `Bool` 实例。如果你传递了一个错误类型的参数,它_确实_有一个 `PrintfArg` 的实例,它会编译并在运行时抛出异常。例如:`printf "%d" "hi"` (2认同)