Haskell printf 是如何工作的?

Haskell 的类型安全是仅次于依赖类型语言的 一个也没有。但是 短信,打印有一些深层次的魔力,看起来相当不靠谱。

> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3

这背后的深层魔力是什么? Text.Printf.printf函数如何能够接受这样的可变参数?

在哈斯克尔,用什么样的一般技巧来考虑可变论点? 它是如何起作用的?

(附注: 使用这种技术时,显然会丧失某种类型的安全性。)

> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
16529 次浏览

The trick is to use type classes. In the case of printf, the key is the PrintfType type class. It does not expose any methods, but the important part is in the types anyway.

class PrintfType r
printf :: PrintfType r => String -> r

So printf has an overloaded return type. In the trivial case, we have no extra arguments, so we need to be able to instantiate r to IO (). For this, we have the instance

instance PrintfType (IO ())

Next, in order to support a variable number of arguments, we need to use recursion at the instance level. In particular we need an instance so that if r is a PrintfType, a function type x -> r is also a PrintfType.

-- instance PrintfType r => PrintfType (x -> r)

Of course, we only want to support arguments which can actually be formatted. That's where the second type class PrintfArg comes in. So the actual instance is

instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)

Here's a simplified version which takes any number of arguments in the Show class and just prints them:

{-# 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)

Here, bar takes an IO action which is built up recursively until there are no more arguments, at which point we simply execute it.

*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True

QuickCheck also uses the same technique, where the Testable class has an instance for the base case Bool, and a recursive one for functions which take arguments in the Arbitrary class.

class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)