如何创建polyvariadic haskell函数?

时间:2010-08-12 11:47:52

标签: haskell variadic-functions function-parameter polyvariadic

我需要一个函数,它接受任意数量的参数(所有相同的类型),对它们执行某些操作,然后返回结果。在我的具体案例中,参数列表是不切实际的。

当我查看haskell库时,我看到函数printf(来自模块Text.Printf)使用了类似的技巧。不幸的是,通过查看来源,我无法理解这种魔力。

有人可以解释如何实现这一点,或者至少是一些网页/论文/我能在哪里找到一个好的描述吗?

动机:

我需要这个的原因非常简单。对于学校(计算机科学课),我们需要编写一个能够“记录”数学表达式,将其表示为字符串的模块(通过为自己的数据类型编写Num / Real / etc的实例),并执行对它的各种操作。

此数据类型包含变量的特殊构造函数,可以由指定函数的值或任何值替换。其中一个目标是编写一个函数,该函数使用一些变量(类型为(Char,Rational)的对)获取这样的表达式,并计算表达式的结果。我们应该看看如何最好地表达函数的目标。 (我的想法:该函数返回另一个函数,它接受与函数中定义的变量一样多的参数 - 似乎是不可能的)。

5 个答案:

答案 0 :(得分:99)

printf的关键点是能够返回String或函数。复制自http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Text-Printf.html

printf :: (PrintfType r) => String -> r
printf fmts = spr fmts []

class PrintfType t where
    spr :: String -> [UPrintf] -> t

instance (IsChar c) => PrintfType [c] where
    spr fmts args = map fromChar (uprintf fmts (reverse args))

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \a -> spr fmts (toUPrintf a : args)

我们可以提取的基本结构是

variadicFunction :: VariadicReturnClass r => RequiredArgs -> r
variadicFunction reqArgs = variadicImpl reqArgs mempty

class VariadicReturnClass r where
   variadicImpl :: RequiredArgs -> AccumulatingType -> r

instance VariadicReturnClass ActualReturnType where
   variadicImpl reqArgs acc = constructActualResult reqArgs acc

instance (ArgClass a, VariadicReturnClass r) => VariadicReturnClass (a -> r) where
   variadicImpl reqArgs acc = \a -> variadicImpl reqArgs (specialize a `mappend` acc)

例如:

class SumRes r where 
    sumOf :: Integer -> r

instance SumRes Integer where
    sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
    sumOf x = sumOf . (x +) . toInteger

然后我们可以使用

*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0  :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59

答案 1 :(得分:9)

很多人都在告诉你如何创建可变函数,但我认为在这种情况下,你最好只使用类型[(Char,Rational)]的列表。

答案 2 :(得分:7)

在关于可变函数的wiki文章中,引用了this article。我想这就是printf的作用,但我也不理解。无论如何,这肯定是一种矫枉过正,特别是因为你的论点都属于同一类型。把它们全部放在一个列表中。这就是列表有用的东西 - 一个相同类型的任意数量的东西。很好,它不是很漂亮,但它几乎不会比完整的多变量函数更丑。

答案 3 :(得分:7)

KennyTM的答案很棒。下面是sumOf 1 4 7 10 :: Integer执行过程的示例,以便更好地说明。

sumOf 1 4 7 10
(( \ x -> ( sumOf . (x +) . toInteger ) 1 ) 4 7 10
((sumOf . (1 + ) . toInteger) 4 ) 7 10
( sumOf 5 ) 7 10
( sumOf . (5 + ) . toInteger ) 7 10
sumOf 12 10
sumOf . (12 + ) . toInteger 10
sumof 22
id 22
22

答案 4 :(得分:6)

我查看了delnan引用的文章中链接的an example。在稍微盯着它之后,我想我终于理解了发生了什么:

它从这个类型类开始:

class BuildList a r  | r-> a where
    build' :: [a] -> a -> r

管道(|)之后的那个位是功能依赖。它表示a表示的类型可以由r表示的类型确定。换句话说,您无法使用相同的BuildList(返回类型)定义r类型类的两个实例,但a不同。

向前跳到实际使用build'函数的位置:

> build True :: [Bool]

由于build只是使用空列表作为第一个参数调用build',因此它与以下内容相同:

> build' [] True :: [Bool]

在此示例中,build'显然正在返回一个列表。由于函数依赖性,我们只能绑定到BuildList类型类的实例:

instance BuildList a [a] where
    build' l x = reverse$ x:l

非常简单。第二个例子更有趣。扩展build的定义,它变为:

> build' [] True False :: [Bool]

在这种情况下,build'的类型是什么?好吧,Haskell的优先规则意味着上面的内容也可以写成:

> (build' [] True) False :: [Bool]

现在很明显,我们将两个参数传递给build',然后将该表达式的结果应用于值为“False”的参数。换句话说,表达式(build' [] True)应返回类型为Bool -> [Bool]函数。这将我们绑定到BuildList类型类的第二个实例:

instance BuildList a r => BuildList a (a->r) where
    build' l x y = build'(x:l) y

在此调用l = []x = True以及y = False中,定义会扩展为build' [True] False :: [Bool]。该签名绑定到build'的第一个实例,并且从那里开始它的位置相当明显。