Haskell printf如何工作? [英] How does Haskell printf work?

查看:139
本文介绍了Haskell printf如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Haskell的类型安全性是仅次于依赖类型的语言的第二个 to none 。但是,文字有一些深刻的魔力。

 > printf%d \\\
3
3
> printf%s%f%dfoo3.3 3
foo 3.3 3

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



什么是常规技术用于允许Haskell中的可变参数,以及它是如何工作的?
$ b (注意:使用这种技术时,某些类型的安全性明显丢失。 )

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


解决方案

诀窍是使用类型类。在 printf 的情况下,键是 PrintfType 类型类。它不会公开任何方法,但重要的部分在类型中。

  class PrintfType r 
printf: :PrintfType r =>字符串 - > r

所以 printf 有一个重载的返回类型。在一般情况下,我们没有额外的参数,所以我们需要能够将 r 实例化为 IO() 。为此,我们有实例

  instance PrintfType(IO())

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

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

当然,我们只想支持实际上可能存在的参数格式化。这就是第二个类型 PrintfArg 进来的地方。所以实际的实例是

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

以下是一个简化版本, code>显示类,然后打印它们:

  { - #LANGUAGE FlexibleInstances# } 

foo :: FooType a => a
foo = bar(return())

class FooType a where
bar :: IO() - > a

实例FooType(IO())其中
bar = id

实例(Show x,FooType r)=> FooType(x - > r)其中
bar sx = bar(s>> print x)

这里, bar 采用一个递归构建的IO动作,直到没有更多的参数为止,此时我们只需执行它。

  *主要> foo 3 :: IO()
3
* Main> foo 3hello:: IO()
3
hello
* Main> foo 3helloTrue :: IO()
3
hello
True

QuickCheck也使用相同的技术,其中 Testable 类具有基本情况 Bool ,以及在任意类中带参数的函数的递归函数。

  class可测试a 
实例可测试Bool
实例(Arbitrary x,Testable r)=>可测试(x - > r)


Haskell's type safety is second to none only to dependently-typed languages. But there is some deep magic going on with Text.Printf that seems rather type-wonky.

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

What is the deep magic behind this? How can the Text.Printf.printf function take variadic arguments like this?

What is the general technique used to allow for variadic arguments in Haskell, and how does it work?

(Side note: some type safety is apparently lost when using this technique.)

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

解决方案

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) 

这篇关于Haskell printf如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆