我需要一个函数,它接受任意数量的参数(所有相同的类型),对它们执行某些操作,然后返回结果。在我的具体案例中,参数列表是不切实际的。
当我查看haskell库时,我看到函数printf
(来自模块Text.Printf
)使用了类似的技巧。不幸的是,通过查看来源,我无法理解这种魔力。
有人可以解释如何实现这一点,或者至少是一些网页/论文/我能在哪里找到一个好的描述吗?
动机:
我需要这个的原因非常简单。对于学校(计算机科学课),我们需要编写一个能够“记录”数学表达式,将其表示为字符串的模块(通过为自己的数据类型编写Num / Real / etc的实例),并执行对它的各种操作。
此数据类型包含变量的特殊构造函数,可以由指定函数的值或任何值替换。其中一个目标是编写一个函数,该函数使用一些变量(类型为(Char,Rational)
的对)获取这样的表达式,并计算表达式的结果。我们应该看看如何最好地表达函数的目标。 (我的想法:该函数返回另一个函数,它接受与函数中定义的变量一样多的参数 - 似乎是不可能的)。
答案 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'
的第一个实例,并且从那里开始它的位置相当明显。