如何创建一个多变量 haskell 函数?

我需要一个函数,它接受任意数量的参数(都是相同类型的) ,对它们进行处理,然后返回结果。在我的特殊情况下,列出一系列论点是不切实际的。

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

谁能解释一下如何做到这一点,或者至少给我一些网页/报纸/其他什么地方,我可以找到一个很好的描述这一点?

动机:

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

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

13360 次浏览

在关于可变函数的 wiki 文章中,引用了 这篇文章。我想 printf 就是干这个的,但我也不明白。无论如何,这是 当然一个杀伤力,特别是因为你的论点都是同一类型。把他们都放在一个单子上。这就是列表的好处——同类事物的任意数量。好吧,它不是很漂亮,但也不会比一个完整的多变量函数更难看。

printf的关键点是返回 String 或函数的能力,

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

我看了一下 Delnan 引用的文章中的 一个例子链接。盯着它看了一会儿,我想我终于明白发生了什么:

它从这个类型类开始:

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

管道(|)后面的位是函数依赖项。它说 a表示的类型可以由 r表示的类型来确定。换句话说,您不能使用相同的 r(返回类型)定义 BuildList类型类的两个实例,但是 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'的类型是什么?好吧,哈斯克尔的优先权规则意味着,上述内容也可以这样写:

> (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'的第一个实例,它从那里开始的位置相当明显。

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

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