【问题标题】:Haskell "Apply"? [duplicate]Haskell“申请”? [复制]
【发布时间】:2012-06-01 19:16:24
【问题描述】:

可能重复:
Why is such a function definition not allowed in haskell?

我是 Haskell 世界的新手,从 Lisp 迁移过来。我正在努力适应 Haskell 完全不同的世界观,而我发现许多新的和令人兴奋的事情之一就是类型系统。作为一名 Lisper,我想我会尝试在 Haskell 中实现一个在 Lisp 世界中非常重要的功能:apply。对于那些不知道的人,apply 接受一个函数和一个参数列表,并在这些参数上调用函数。在 Scheme 中,(apply + '(1 2 3)) 与调用 (+ 1 2 3) 相同,返回 6。

我的 Haskell 代码如下所示:

apply x [] = x
apply f (x:xs) = apply (f x) xs

但 Haskell 抱怨:

ERROR line 2 - Type error in function binding
*** Term           : apply
*** Type           : (b -> a) -> [b] -> a
*** Does not match : a -> [b] -> a
*** Because        : unification would give infinite type

我想我明白为什么。 Apply 的类型需要根据给出的列表的长度而有所不同。给定一个包含 3 个项目的列表,apply 的类型需要是:(a -> a -> a -> b) -> [a] -> b,但给定一个包含 6 个项目的列表,apply 的类型需要是:(a -> a -> a -> a -> a -> a -> b) -> [a] -> b

我尝试了这个可怕的解决方法:

data FnOrDat a b = Dat b | Fn (a -> FnOrDat a b)

apply :: (FnOrDat a b) -> [a] -> (FnOrDat a b)
apply x [] = x
apply (Fn f) (x:xs) = apply (f x) xs
apply (Dat _) _ = error "Cannot apply something which is not a function!"

add a = Fn (\b -> Dat (a + b))

main = putStrLn $ show $ x where Dat x = apply (Fn add) [5,1]

这可行,但它几乎不能算作apply 函数,因为我不能将apply 传递给普通函数,我必须使用专门编写的函数来使用我的(尴尬的)FnOrDat 抽象。如果我想写一个添加四个数字的函数,我需要写

add4 a = Fn (\b -> Fn (\c -> Fn (\d -> Dat (a + b + c + d))))

呃。

所以 - 我是否遗漏了什么,或者要求一个通用的apply 基本上就像要求一个可以操作任意长度元组的函数? apply 在 Haskell 的静态类型世界观中是否有意义?

【问题讨论】:

    标签: haskell types lisp polyvariadic


    【解决方案1】:

    apply 在 Haskell 中不是很有用,因为你不能给函数指定类型。正如您在 FnOrDat 中看到的那样,您实际上是在将 Lisp 语言作为 EDSL 嵌入到 Haskell 中以强制通过。

    要求一个通用应用基本上就像要求一个可以操作任意长度元组的函数?

    没错。您可以为某些有用的类型组合提供类型类实例,但实际上并不需要或使用通用可变参数apply


    附带说明,您应该考虑升级到 GHC 和 Haskell Platform,而不是过时的 Hugs 系统,因为您错过了过去 10 年开发的大多数库、工具和语言功能。

    【讨论】:

    • 谢谢!实际上,我确实在我的“主”计算机上使用了 GHC。不过我是在其他地方输入的,所以我只是使用 codepad.org 来测试我的代码 sn-ps。我猜他们用的是拥抱?
    • @Ord 他们确实使用拥抱,带有-98 标志。见codepad.org/about
    【解决方案2】:

    尽管有 Don 的解释,foldl1 (+) 实际上会添加列表的所有元素。因此,可以说fold 系列函数与 OP 所描述的 apply 非常接近。

    【讨论】:

    • 是的,我猜对于某些类型的函数。但是如果你有一个像discriminant a b c = b*b - 4*a*c 这样的函数,在Lisp 中你可以说(apply discriminant '(2 3 4))。你不能用 fold 做到这一点。
    • 另一方面,我不知道为什么 apply 会被这样使用。在 Lisp 中,当您有一个接受任意数量参数的函数时,您通常使用 apply,并且此概念不会延续到 Haskell。
    • 嗯,是的,但是apply3 f (a:b:c:_) = f a b c 是一个班轮。
    • 你可以使用柯里化来玩游戏,以将 arity 反映到嵌套元组中,uncurry ( uncurry discriminant ) ((2,3),4) ==> -23。这不是 Haskell 要做的事情。
    • @DonStewart 酷!我以前从未听说过uncurry。但是,是的,我明白为什么它不是一个常见的 Haskell 习语。
    【解决方案3】:

    ... 在 Lisp 世界中非常重要的一个函数:apply。对于那些 谁不知道,apply 接受一个函数和一个参数列表,并且 在这些参数上调用函数。在 Scheme 中,(apply + '(1 2 3)) 与调用 (+ 1 2 3) 相同,并返回 6。 ...

    这很简单:

    foldr  (+) 0 [1,2,3]
    foldr1 (+)   [1,2,3]
    

    结果为 6。

    将函数应用于列表的每个元素:

    map f list
    

    例如

    map (2*) [1,2,3]
    

    结果为 [2,4,6]

    这是你要找的吗?

    【讨论】:

    • 不完全。如果你定义discriminant a b c = b*b - 4*a*c,在 Lisp 中你可以说(apply discriminant '(2 3 4))。另一个(更有用的)示例:在 Lisp 中,map 函数可以采用任意数量的列表参数。我不仅可以说(map square '(1 2 3 4)) -> '(1 4 9 16),还可以说(map + '(1 2 3) '(4 5 6)) -> '(5 7 9)(类似于zipWith)或(map * '(1 2 3) '(3 2 1) '(0 2 0)) -> '(0 8 0)。如果我有一个列表列表 x,我可以说 (apply map + x) 来获得列表的逐项总和。你不能用 fold 做任何这些事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-02
    • 1970-01-01
    • 1970-01-01
    • 2013-09-23
    • 1970-01-01
    相关资源
    最近更新 更多