Haskell printf如何工作?


104

Haskell的类型安全是第二首屈一指只依赖类型语言。但是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

15
您只能使用从属类型获得类型安全的printf。
2011

9
伦纳特说得很对。Haskell的类型安全性比Haskell依赖类型更多的语言第二。当然,如果您为格式选择比字符串更多的信息类型,则可以使类似printf的事物类型安全。
猪场

3
有关printf的多种变体,请参见oleg
sclv 2011年

1
@augustss您只能使用依赖类型或模板哈希来获取类型安全的printf!;-)
MathematicalOrchid

3
@MathematicalOrchid模板Haskell不计算在内。:)
2013

Answers:


131

诀窍是使用类型类。对于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)

当然,我们只想支持可以实际格式化的参数。那是第二类的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) 

好答案。我只想指出,haskell正在根据所应用的参数确定Foo的类型。要理解这一点,您可能希望按如下方式指定Foo的显式类型:λ>(foo ::(Show x,Show y)=> x-> y-> IO())3“ hello”
redfish64

1
虽然我了解了可变长度参数部分是如何实现的,但我仍然不了解编译器如何拒绝printf "%d" True。这对我来说是非常神秘的,因为似乎runtime(?)值"%d"在编译时需要一个来解密Int。这绝对让我感到困惑。。。特别是因为源代码不使用之类的东西DataKindsTemplateHaskell(我查了源代码,但没有理解它。)
托马斯Eding

2
@ThomasEding编译器拒绝的原因printf "%d" True是因为没有的Bool实例PrintfArg。如果传递的错误类型的参数确实具有的实例PrintfArg,则它会编译并在运行时引发异常。例如:printf "%d" "hi"
特拉维斯·桑德兰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.