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

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

问题描述

Haskell 的类型安全性仅次于依赖类型的语言to none.但是 Text 有一些很深的魔法.Printf 看起来很奇怪.

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
" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3

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

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

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

(旁注:使用这​​种技术显然会失去一些类型安全性.)

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

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

推荐答案

诀窍是使用类型类.对于printf,关键是PrintfType 类型类.它不公开任何方法,但重要的部分还是在类型中.

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

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

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 ())

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

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)

当然,我们只想支持实际可以格式化的参数.这就是第二个类型类 PrintfArg 的用武之地.所以实际的实例是

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)

这是一个简化版本,它在 Show 类中接受任意数量的参数,然后将它们打印出来:

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)

这里,bar 执行一个 IO 操作,该操作以递归方式构建,直到没有更多参数,此时我们只需执行它.

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 也使用相同的技术,其中 Testable 类有一个基本情况 Bool 的实例,以及一个递归的,用于在 中接受参数的函数>任意类.

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天全站免登陆