Haskell printf如何工作?

Haskell的types安全性对于依赖types语言来说是首屈一指的 。 但是, Text.Printf有一些非常神奇的东西 ,看起来相当不可思议 。

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

这背后有什么深奥的魔力? Text.Printf.printf函数如何使用像这样的可变参数?

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

(注意:使用这种技术时,某些types的安全性显然已经丢失。)

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

诀窍是使用types类。 在printf的情况下,关键是PrintfTypetypes的类。 它没有公开任何方法,但是重要的部分在于types。

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

所以printf有一个重载的返回types。 在平凡的情况下,我们没有额外的参数,所以我们需要能够实例化IO () 。 为此,我们有这样的例子

 instance PrintfType (IO ()) 

接下来,为了支持可变数量的参数,我们需要在实例级别使用recursion。 特别是我们需要一个实例,所以如果r是一个PrintfType ,函数typesx -> r也是一个PrintfType

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

当然,我们只想支持实际可以格式化的参数。 这就是第二种typesPrintfArg进来的地方,所以实际的实例是

 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 sx = bar (s >> print x) 

在这里, bar采用一个recursion构build的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类中的参数的函数的recursion函数。

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