如何创建polyvariadic haskell函数?

fuz*_*fuz 69 haskell variadic-functions function-parameter polyvariadic

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

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

有人可以解释如何实现这一点,或至少一些网页/纸/任何我可以找到一个良好的描述吗?

动机:

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

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

ken*_*ytm 104

关键点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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

例如:

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
Run Code Online (Sandbox Code Playgroud)

然后我们可以使用

*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
Run Code Online (Sandbox Code Playgroud)

  • 在C中它是不安全的(但是语言是整体的),但在C++ 0x中,可变参数模板允许类型安全的多变量函数. (2认同)

小智 9

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

  • 还有一点需要注意:这也是*惯用*编写它的方式.我认为我唯一使用的可变函数是`printf`; 我希望你的功能可以列出一个列表. (7认同)
  • 请看,德尔南已经说了这个.是的,我知道,这会更容易,但是通过在自己的程序中实现它来理解这些机制是如何工作的非常有用. (2认同)

小智 9

KennyTM的答案很棒.下面是exec过程的示例,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
Run Code Online (Sandbox Code Playgroud)


小智 7

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


Dan*_*att 6

我看了一下delnan引用的文章中链接的一个例子.在稍微盯着它之后,我想我终于理解了发生了什么:

它从这个类型类开始:

class BuildList a r  | r-> a where
    build' :: [a] -> a -> r
Run Code Online (Sandbox Code Playgroud)

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

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

> build True :: [Bool]
Run Code Online (Sandbox Code Playgroud)

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

> build' [] True :: [Bool]
Run Code Online (Sandbox Code Playgroud)

在这个例子中,build'显然是返回一个列表.由于函数依赖,我们只能绑定到BuildList类型类的这个实例:

instance BuildList a [a] where
    build' l x = reverse$ x:l
Run Code Online (Sandbox Code Playgroud)

非常直截了当.第二个例子更有趣.扩展定义build,它变成:

> build' [] True False :: [Bool]
Run Code Online (Sandbox Code Playgroud)

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

> (build' [] True) False :: [Bool]
Run Code Online (Sandbox Code Playgroud)

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

instance BuildList a r => BuildList a (a->r) where
    build' l x y = build'(x:l) y
Run Code Online (Sandbox Code Playgroud)

在此调用,l = []x = Truey = False,这样的定义扩展到build' [True] False :: [Bool].那个签名绑定到第一个实例build',并且从那里开始它的位置相当明显.