77

我需要一个函数,它接受任意数量的参数(所有相同类型),对它们做一些事情,然后返回结果。在我的具体情况下,论点列表是不切实际的。

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

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

动机:

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

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

4

5 回答 5

110

的关键点printf是能够返回字符串或函数。复制自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
于 2010-08-12T12:51:45.627 回答
10

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

于 2010-08-12T13:56:14.203 回答
10

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
于 2013-10-04T14:37:09.640 回答
7

在关于可变参数函数的 wiki 文章中,引用了这篇文章。我想这就是 printf 所做的,但我也不明白。无论如何,这肯定是一种矫枉过正,特别是因为你的论点都是相同的类型。只需将它们全部放在一个列表中即可。这就是列表的好处 - 任意数量的相同类型的东西。好吧,它不是很漂亮,但它不会比一个完整的多元函数更丑陋。

于 2010-08-12T12:05:18.270 回答
6

我查看了 delnan 引用的文章中链接的示例。看了一会儿,我想我终于明白是怎么回事了:

它从这个类型类开始:

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 = []andx = Truey = False,因此定义扩展为build' [True] False :: [Bool]。该签名绑定到 的第一个实例build',并且很明显它从那里去哪里。

于 2010-08-12T13:44:21.207 回答