问题描述
Haskell 的类型安全性仅次于依赖类型的语言.但是 Text 有一些很深的魔法.Printf 看起来很奇怪.
Haskell's type safety is second 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 ())
接下来,为了支持可变数量的参数,我们需要在实例级别使用递归.特别是我们需要一个实例,以便如果 r
是 PrintfType
,函数类型 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 如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!