柯里化是如何工作的? [英] How does currying work?

查看:33
本文介绍了柯里化是如何工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这是一个函数:(+) :: a ->(a -> a)如果我执行 (+) 4 7,该函数采用 4 并返回一个采用 7 并返回 11 的函数>.但是 4 会发生什么?第一个函数对 4 有什么作用?(a -> a)7 有什么关系?

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

max' :: Int ->(Int -> Int)最大' m n |米>n = 米|否则 = n

(Int -> Int) 将其参数与什么进行比较?它只需要一个参数,但需要两个参数才能做到 m >n.

解决方案

理解高阶函数

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

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

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

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

你也可以返回函数.下面我们创建一个函数,它接受一个函数和一个参数并返回另一个函数,该函数接受一个参数并返回一个结果.

let g f n = (m -> m `f` n);f = g (+) 2 in f 10 -- 返回 12

A (m -> m `f` n) 构造是一个 匿名1 个参数 m 的函数,将 f 应用于该 mn.基本上,当我们调用 g (+) 2 时,我们创建了一个只有一个参数的函数,它只是将 2 添加到它接收到的任何内容上.所以 let f = g (+) 2 in f 10 等于 12 而 let f = g (*) 5 in f 5 等于 25.

(另请参阅我对 HOF 的解释,以 Scheme 为例.)

了解柯里化

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

现在想象一下,你只能给它 1 个参数,它会返回一个函数?您可以稍后使用此函数将这个 1st 参数(现在包含在此新函数中)添加到其他内容中.例如:

f n = (m -> n - m)g = f 10g 8 -- 将返回 2g 4 -- 将返回 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 函数应用程序从左到右,所以 fabc 实际上是 (((fa) b) c),这完全有道理,一旦你知道 f 默认是柯里化的.

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

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

要真正确定柯里化的概念,请尝试在解释器中找到以下表达式的类型:

(+)(+) 2(+) 2 3地图地图 (x -> head x)地图 (x -> head x) [良心",做",成本"]地图头图头[良心",做",成本"]

部分应用和部分

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

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

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

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

成分

其他编写简洁灵活的代码的方便工具是 composition应用操作符.组合运算符 (.) 将函数链接在一起.应用运算符($)只是将左边的函数应用到右边的参数上,所以f $ x等价于f x.但是 ($) 在所有运算符中的优先级最低,所以我们可以用它来去掉括号: f (gxy) 等价于 f $ gxy.

当我们需要对同一个参数应用多个函数时它也很有用: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:.(点)和 $(美元符号)部分来自 Learn You a Haskell 以了解差异.

I'm very new to Haskell and FP in general. I've read many of the writings that describe what currying is, but I haven't found an explanation to how it actually works.

Here is a function: (+) :: a -> (a -> a) If I do (+) 4 7, the function takes 4 and returns a function that takes 7 and returns 11. But what happens to 4 ? What does that first function do with 4? What does (a -> a) do with 7?

Things get more confusing when I think about a more complicated function:

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

what does (Int -> Int) compare its parameter to? It only takes one parameter, but it needs two to do m > n.

解决方案

Understanding higher-order functions

Haskell, as a functional language, supports higher-order functions (HOFs). In mathematics HOFs are called functionals, but you don't need any mathematics to understand them. In usual imperative programming, like in Java, functions can accept values, like integers and strings, do something with them, and return back a value of some other type.

But what if functions themselves were no different from values, and you could accept a function as an argument or return it from another function? f a b c = a + b - c is a boring function, it sums a and b and then substracts c. But the function could be more interesting, if we could generalize it, what if we'd want sometimes to sum a and b, but sometimes multiply? Or divide by c instead of subtracting?

Remember, (+) is just a function of 2 numbers that returns a number, there's nothing special about it, so any function of 2 numbers that returns a number could be in place of it. Writing g a b c = a * b - c, h a b c = a + b / c and so on just doesn't cut it for us, we need a general solution, we are programmers after all! Here how it is done in Haskell:

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

And you can return functions too. Below we create a function that accepts a function and an argument and returns another function, which accepts a parameter and returns a result.

let g f n = (m -> m `f` n); f = g (+) 2 in f 10 -- returns 12

A (m -> m `f` n) construct is an anonymous function of 1 argument m that applies f to that m and n. Basically, when we call g (+) 2 we create a function of one argument, that just adds 2 to whatever it receives. So let f = g (+) 2 in f 10 equals 12 and let f = g (*) 5 in f 5 equals 25.

(See also my explanation of HOFs using Scheme as an example.)

Understanding currying

Currying is a technique that transforms a function of several arguments to a function of 1 argument that returns a function of 1 argument that returns a function of 1 argument... until it returns a value. It's easier than it sounds, for example we have a function of 2 arguments, like (+).

Now imagine that you could give only 1 argument to it, and it would return a function? You could use this function later to add this 1st argument, now encased in this new function, to something else. E.g.:

f n = (m -> n - m)
g = f 10
g 8 -- would return 2
g 4 -- would return 6

Guess what, Haskell curries all functions by default. Technically speaking, there are no functions of multiple arguments in Haskell, only functions of one argument, some of which may return new functions of one argument.

It's evident from the types. Write :t (++) in interpreter, where (++) is a function that concatenates 2 strings together, it will return (++) :: [a] -> [a] -> [a]. The type is not [a],[a] -> [a], but [a] -> [a] -> [a], meaning that (++) accepts one list and returns a function of type [a] -> [a]. This new function can accept yet another list, and it will finally return a new list of type [a].

That's why function application syntax in Haskell has no parentheses and commas, compare Haskell's f a b c with Python's or Java's f(a, b, c). It's not some weird aesthetic decision, in Haskell function application goes from left to right, so f a b c is actually (((f a) b) c), which makes complete sense, once you know that f is curried by default.

In types, however, the association is from right to left, so [a] -> [a] -> [a] is equivalent to [a] -> ([a] -> [a]). They are the same thing in Haskell, Haskell treats them exactly the same. Which makes sense, because when you apply only one argument, you get back a function of type [a] -> [a].

On the other hand, check the type of map: (a -> b) -> [a] -> [b], it receives a function as its first argument, and that's why it has parentheses.

To really hammer down the concept of currying, try to find the types of the following expressions in the interpreter:

(+)
(+) 2
(+) 2 3
map
map (x -> head x)
map (x -> head x) ["conscience", "do", "cost"]
map head
map head ["conscience", "do", "cost"]

Partial application and sections

Now that you understand HOFs and currying, Haskell gives you some syntax to make code shorter. When you call a function with 1 or multiple arguments to get back a function that still accepts arguments, it's called partial application.

You understand already that instead of creating anonymous functions you can just partially apply a function, so instead of writing (x -> replicate 3 x) you can just write (replicate 3). But what if you want to have a divide (/) operator instead of replicate? For infix functions Haskell allows you to partially apply it using either of arguments.

This is called sections: (2/) is equivalent to (x -> 2 / x) and (/2) is equivalent to (x -> x / 2). With backticks you can take a section of any binary function: (2`elem`) is equivalent to (xs -> 2 `elem` xs).

But remember, any function is curried by default in Haskell and therefore always accepts one argument, so sections can be actually used with any function: let (+^) be some weird function that sums 4 arguments, then let (+^) a b c d = a + b + c in (2+^) 3 4 5 returns 14.

Compositions

Other handy tools to write concise and flexible code are composition and application operator. Composition operator (.) chains functions together. Application operator ($) just applies function on the left side to the argument on the right side, so f $ x is equivalent to f x. However ($) has the lowest precedence of all operators, so we can use it to get rid of parentheses: f (g x y) is equivalent to f $ g x y.

It is also helpful when we need to apply multiple functions to the same argument: map ($2) [(2+), (10-), (20/)] would yield [4,8,10]. (f . g . h) (x + y + z), f (g (h (x + y + z))), f $ g $ h $ x + y + z and f . g . h $ x + y + z are equivalent, but (.) and ($) are different things, so read Haskell: difference between . (dot) and $ (dollar sign) and parts from Learn You a Haskell to understand the difference.

这篇关于柯里化是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆