curry如何工作?

Arl*_*len 13 haskell currying

我对Haskell和FP很新.我已经阅读了许多描述currying的文章,但我还没有找到它如何实际工作的解释.

这里是一个功能:(+) :: 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
Run Code Online (Sandbox Code Playgroud)

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

fuz*_*fuz 13

你可以把它想象成函数存储参数并返回一个只需要其他参数的新函数.新函数已经知道第一个参数,因为它与函数一起存储.这由编译器在内部处理.如果你想知道它是如何工作的,你可能会对这个页面感兴趣,虽然如果你是Haskell的新手可能会有点复杂.

如果函数调用完全饱和(因此所有参数同时传递),大多数编译器都使用普通的调用方案,就像在C中一样.


alt*_*ive 12

这有帮助吗?

max' = \m -> \n -> if (m > n)
                       then m
                       else n
Run Code Online (Sandbox Code Playgroud)

写成lambdas.max'是一个lambda的值,它本身返回一个给定m的lambda,返回值.

因此max'4是

max' 4 = \n -> if (4 > n)
                   then 4
                   else n
Run Code Online (Sandbox Code Playgroud)


Mir*_*lov 12

了解高阶函数

Haskell作为一种函数式语言,支持高阶函数(HOF).在数学中,HOF被称为函数,但是你不需要任何数学来理解它们.在通常的命令式编程中,如在Java中,函数可以接受值,如整数和字符串,对它们执行某些操作,并返回其他类型的值.

但是,如果函数本身与值没有区别,你可以接受一个函数作为参数或从另一个函数返回它?f a b c = a + b - c是一个无聊的功能,总结ab再substracts c.但功能可能会更有趣,如果我们能概括它,如果我们会想有时总结ab,但有时乘?或者除以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
Run Code Online (Sandbox Code Playgroud)

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

let g f n = (\m -> m `f` n); f = g (+) 2 in f 10 -- returns 12
Run Code Online (Sandbox Code Playgroud)

(\m -> m `f` n)构建体是匿名函数 1个论点m适用fmn.基本上,当我们调用时,g (+) 2我们创建一个参数的函数,它只是为它收到的任何东西加2.所以let f = g (+) 2 in f 10等于12,let f = g (*) 5 in f 5等于25.

(另请参阅我对使用Scheme作为示例的HOF的解释.)

了解currying

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

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

f n = (\m -> n - m)
g = f 10
g 8 -- would return 2
g 4 -- would return 6
Run Code Online (Sandbox Code Playgroud)

猜猜看,Haskell默认会调整所有函数.从技术上讲,Haskell中没有多个参数的函数,只有一个参数的函数,其中一些可能返回一个参数的新函数.

从类型来看很明显.:t (++)在解释器中写入,其中(++)一个函数将两个字符串连接在一起,它将返回(++) :: [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默认情况下是curry.

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

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

要真正理解currying的概念,请尝试在解释器中查找以下表达式的类型:

(+)
(+) 2
(+) 2 3
map
map (\x -> head x)
map (\x -> head x) ["conscience", "do", "cost"]
map head
map head ["conscience", "do", "cost"]
Run Code Online (Sandbox Code Playgroud)

部分申请和部分

既然你已经理解了HOF和currying,那么Haskell会为你提供一些语法来缩短代码.当你用一个或多个参数调用一个函数来获取仍然接受参数的函数时,它被称为部分应用程序.

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

这就是所谓的部分:(2/)等同于(\x -> 2 / x)(/2)等价于(\x -> x / 2).使用反引号,您可以获取任何二元函数的一部分:(2`elem`)相当于(\xs -> 2 `elem` xs).

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

成分

编写简洁灵活代码的其他方便工具是组合应用程序操作员.组合运算符(.)链一起运行.应用程序操作员($)只将左侧的函数应用于右侧的参数,因此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:之间的差异.(点)和$(美元符号)来自Learn You a Haskell的部分来理解差异.