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
的情况下,关键是PrintfType
types的类。 它没有公开任何方法,但是重要的部分在于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)