gpt4 book ai didi

haskell - 柯里化(Currying)是如何工作的?

转载 作者:行者123 更新时间:2023-12-02 05:47:07 24 4
gpt4 key购买 nike

总的来说,我对 Haskell 和 FP 很陌生。我已经阅读了许多描述什么是柯里化的文章,但我还没有找到解释它实际上是如何工作的。

这是一个函数:(+) :: a -> (a -> a)如果我这样做 (+) 4 7 , 函数取 4并返回一个接受 7 的函数并返回 11 .但是4会发生什么? ?第一个函数对 4 有什么作用? ?什么(a -> a)7 ?

当我想到一个更复杂的函数时,事情变得更加困惑:

max' :: Int -> (Int -> Int)
max' m n | m > n = m
| otherwise = n

什么 (Int -> Int)比较它的参数?只需要一个参数,但需要两个参数来做 m > n .

最佳答案

理解高阶函数

Haskell 作为一种函数式语言,支持高阶函数 (HOF)。在数学中 HOF 被称为 functionals ,但你不需要任何数学来理解它们。在通常的命令式编程中,就像在 Java 中一样,函数可以接受值,比如整数和字符串,对它们做一些事情,然后返回某个其他类型的值。

但是,如果函数本身与值没有区别,并且您可以接受一个函数作为参数或从另一个函数返回它呢? f a b c = a + b - c是一个无聊的函数,它求和 ab然后减去 c .但是这个函数可能更有趣,如果我们可以概括它,如果我们有时想要求和 a 怎么办?和 b ,但有时会成倍增加?或除以 c而不是减去?

记住,(+)只是一个返回一个数字的 2 个数字的函数,它没有什么特别的,所以任何返回一个数字的 2 个数字的函数都可以代替它。写作 g a b c = a * b - c , h a b c = a + b / c等等只是不适合我们,我们需要一个通用的解决方案,毕竟我们是程序员!这里是如何在 Haskell 中完成的:

let f g h a b c = a `g` b `h` c in f (*) (/) 2 3 4 -- returns 1.5

你也可以返回函数。下面我们创建一个函数,它接受一个函数和一个参数并返回另一个函数,该函数接受一个参数并返回一个结果。
let g f n = (\m -> m `f` n); f = g (+) 2 in f 10 -- returns 12

一个 (\m -> m `f` n)构造是一个 anonymous function共 1 个参数 m适用 f到那个 mn .基本上,当我们拨打 g (+) 2 时我们创建了一个只有一个参数的函数,它只将收到的任何内容加 2。所以 let f = g (+) 2 in f 10等于 12 和 let f = g (*) 5 in f 5等于 25。

(另请参阅 my explanation of HOFs,以 Scheme 为例。)

了解柯里化

Currying是一种将具有多个参数的函数转换为具有 1 个参数的函数的技术,该函数返回一个具有 1 个参数的函数,该函数返回一个具有 1 个参数的函数......直到它返回一个值。这比听起来容易,例如我们有一个有 2 个参数的函数,比如 (+) .

现在想象一下,你只能给它 1 个参数,它会返回一个函数?您可以稍后使用此函数将现在包含在此新函数中的第一个参数添加到其他内容中。例如。:
f n = (\m -> n - m)
g = f 10
g 8 -- would return 2
g 4 -- would return 6

猜猜看,Haskell 默认对所有函数进行柯里化。从技术上讲,Haskell 中没有多参数的函数,只有一个参数的函数,其中一些可能返回一个参数的新函数。

从种类就可以看出来。写信 :t (++)在解释器中,其中 (++)是一个将 2 个字符串连接在一起的函数,它将返回 (++) :: [a] -> [a] -> [a] .类型不是 [a],[a] -> [a] ,但是 [a] -> [a] -> [a] , 意思是 (++)接受一个列表并返回一个 [a] -> [a] 类型的函数.这个新函数可以接受另一个列表,它最终会返回一个类型为 [a] 的新列表。 .

这就是为什么 Haskell 中的函数应用语法没有括号和逗号的原因,对比 Haskell 的 f a b c使用 Python 或 Java 的 f(a, b, c) .这不是什么奇怪的审美决定,在 Haskell 函数应用程序从左到右,所以 f a b c实际上是 (((f a) b) c) ,这是完全有道理的,一旦你知道 f默认是柯里化的。

然而,在类型中,关联是从右到左的,所以 [a] -> [a] -> [a]相当于 [a] -> ([a] -> [a]) .它们在 Haskell 中是一回事,Haskell 对待它们完全一样。这是有道理的,因为当你只应用一个参数时,你会得到一个 [a] -> [a] 类型的函数。 .

另一方面,检查 map 的类型: (a -> b) -> [a] -> [b] ,它接收一个函数作为它的第一个参数,这就是它有括号的原因。

要真正确定柯里化的概念,请尝试在解释器中找到以下表达式的类型:
(+)
(+) 2
(+) 2 3
map
map (\x -> head x)
map (\x -> head x) ["conscience", "do", "cost"]
map head
map head ["conscience", "do", "cost"]

部分应用和部分

现在您了解了 HOF 和柯里化,Haskell 为您提供了一些使代码更短的语法。当你调用一个带有 1 个或多个参数的函数来返回一个仍然接受参数的函数时,它被称为 partial application .

您已经了解,您可以只部分应用一个函数,而不是创建匿名函数,所以不要写 (\x -> replicate 3 x)你可以写 (replicate 3) .但是如果你想有一个除法怎么办 (/)运算符而不是 replicate ?对于中缀函数,Haskell 允许您使用任一参数部分应用它。

这被称为 sections : (2/)相当于 (\x -> 2 / x)(/2)相当于 (\x -> x / 2) .使用反引号,您可以获取任何二元函数的一部分: (2`elem`)相当于 (\xs -> 2 `elem` xs) .

但请记住,在 Haskell 中,任何函数默认都被柯里化,因此总是接受一个参数,因此部分实际上可以与任何函数一起使用: let (+^)是一些对 4 个参数求和的奇怪函数,然后 let (+^) a b c d = a + b + c in (2+^) 3 4 5返回 14。

组合物

其他编写简洁灵活代码的方便工具是 compositionapplication operator .组合运算符 (.)链一起发挥作用。应用运营商 ($)只是将左侧的函数应用于右侧的参数,所以 f $ x相当于 f x .然而 ($)在所有运算符中优先级最低,所以我们可以用它来去掉括号: f (g x y)相当于 f $ g x y .

当我们需要对同一个参数应用多个函数时,它也很有用: map ($2) [(2+), (10-), (20/)]将产生 [4,8,10] . (f . g . h) (x + y + z) , f (g (h (x + y + z))) , f $ g $ h $ x + y + zf . g . h $ x + y + z是等价的,但 (.)($)是不同的东西,所以阅读 Haskell: difference between . (dot) and $ (dollar sign)parts from Learn You a Haskell了解差异。

关于haskell - 柯里化(Currying)是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6652234/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com