如何创建多变量haskell函数?


71

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

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

有人可以解释如何实现这一目标,或者至少可以解释一些网页/论文/无论我能在哪里找到合适的描述吗?

动机:

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

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


看到您的用例会很有趣。
Volker Stolz 2010年

只是一些学校项目。我将编辑问题并发布目的。
2010年

3
您可能会考虑其他方法-带有列表的函数:评估:: [[(Char,Rational)]-> Expr-> Rational; 具有函数的函数:(字符-> Rational)-> Expr-> Rational;一个获取地图的函数:Data.Map.Map Char Rational-> Expr-> Rational。
sdcvvc 2010年

2
请注意,使用这种可变参数函数意味着,如果模块的用户不进行类型检查,则他们的用户将收到非常含糊的错误消息。理解“这不是列表”之类的错误比“缺少PrintfType ... for ...的实例”之类的错误要容易得多。
彼得

Answers:


106

关键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

谢谢肯尼TM!那正是我想要的!
2010年

@KennyTM:您知道吗,是否有用于多元函数的标签?
2010年

@FUZxxl:在这个网站上?可变函数有一个,但它是以C / C ++为中心的。
kennytm 2010年

2
在C语言中这是不安全的(但是从总体上来说,语言是不安全的),但是在C ++ 0x中,可变参数模板允许使用类型安全的多变量函数。
Matthieu M.

1
@FtheBuilder也许您可以提出一个问题,但是我想它与FlexibleInstances有关,并且数字不是具体类型而是a Num a => a
kennytm 2015年

10

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


2
请看,delnan恰好说了这一点。是的,我知道这会更容易,但是了解此类机制如何在自己的程序中实现是非常有用的。
2010年

7
还需要注意的一点:这也是编写它的惯用方式。我认为我使用的唯一可变参数函数是printf;我希望您的职能能列出一个清单。
Antal Spector-Zabusky 2010年

我可以想到的可变参数函数的唯一其他合理用法(除外printf)将是设计有这种特定要求的DSL。
Petr

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

7

在有关变参函数的Wiki文章中,引用了该文章。我想这就是printf的功能,但是我也不明白。无论如何,这当然是一个矫kill过正,尤其是因为您的论点都是相同的类型。只需将它们全部放在一个列表中即可。这就是列表的优点-任意数量的相同类型的东西。很好,它不是很漂亮,但是几乎不会比一个完整的多元函数更丑陋。


尽管这有点令人困惑,但这似乎正是我所需要的。
fuz

6

我看一个例子与delnan引用的文章链接。凝视了一下之后,我想我终于理解了发生了什么:

它从以下类型类开始:

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

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

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

> build True :: [Bool]

以来 buildbuild'使用空列表作为第一个参数进行调用,因此与以下内容相同:

> 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]。这将我们绑定到BuildListtypeclass的第二个实例:

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',并且从那里开始很明显。


该描述非常简单并且非常有用。我唯一不清楚的是那个定义build' l x = reverse $ x:l。但是我认为,KennyTM的答案更有用,因为我宁愿遍历参数,而不是将它们显示为列表。
2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.