我对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
(Int -> Int)
比较其参数是什么?它只需要一个参数,但它需要两个参数{.1}}。
答案 0 :(得分:13)
你可以把它想象成函数存储参数并返回一个只需要其他参数的新函数。新函数已经知道第一个参数,因为它与函数一起存储。这由编译器在内部处理。如果你想知道它是如何工作的,你可能会对this page感兴趣,虽然如果你是Haskell的新手可能会有点复杂。
如果函数调用完全饱和(因此所有参数同时传递),大多数编译器都使用普通的调用方案,就像在C中一样。
答案 1 :(得分:12)
这有帮助吗?
max' = \m -> \n -> if (m > n)
then m
else n
写成lambdas。 max'是一个lambda的值,它本身返回一个给定m的lambda,返回值。
因此max'4是
max' 4 = \n -> if (4 > n)
then 4
else n
答案 2 :(得分:10)
Haskell作为一种函数式语言,支持高阶函数(HOF)。在数学中,HOF被称为functionals,但你不需要任何数学来理解它们。在通常的命令式编程中,如在Java中,函数可以接受值,如整数和字符串,对它们执行某些操作,并返回其他类型的值。
但是如果函数本身与值没有什么不同,你可以接受一个函数作为参数或从另一个函数返回它? f a b c = a + b - c
是一个无聊的函数,它将a
和b
相加,然后减去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
应用于m
和n
。基本上,当我们调用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 st 参数添加到其他内容中。 E.g:
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或f(a, b, c)
进行比较。它不是一些奇怪的审美决定,在Haskell函数应用程序中从左到右,所以f a b c
实际上是(((f a) b) c)
,一旦你知道f
是[a] -> [a] -> [a]
,这是完全合理的默认情况下。
但是,在类型中,关联是从右到左,因此[a] -> ([a] -> [a])
相当于[a] -> [a]
。它们在Haskell中是相同的,Haskell对它们完全相同。这是有道理的,因为当你只应用一个参数时,你会得到一个(a -> b) -> [a] -> [b]
类型的函数。
另一方面,检查map
:(+)
(+) 2
(+) 2 3
map
map (\x -> head x)
map (\x -> head x) ["conscience", "do", "cost"]
map head
map head ["conscience", "do", "cost"]
的类型,它接收一个函数作为它的第一个参数,这就是为什么它有括号。
要真正理解currying的概念,请尝试在解释器中找到以下表达式的类型:
(\x -> replicate 3 x)
现在你了解了HOF和currying,Haskell为你提供了一些语法来缩短代码。当你用一个或多个参数调用一个函数来获取仍然接受参数的函数时,它被称为partial application。
您已经明白,不是创建匿名函数,而是可以部分应用函数,因此您可以只编写(replicate 3)
而不是编写(/)
。但是,如果您希望使用除replicate
运算符而不是(2/)
,该怎么办?对于中缀函数,Haskell允许您使用任一参数部分应用它。
这称为sections:(\x -> 2 / x)
相当于(/2)
,(\x -> x / 2)
相当于(2`elem`)
。使用反引号,您可以使用任何二元函数的一部分:(\xs -> 2 `elem` xs)
相当于(+^)
。
但请记住,默认情况下,任何函数都在Haskell中进行调整,因此总是接受一个参数,因此部分实际上可以与任何函数一起使用:let let (+^) a b c d = a + b + c in (2+^) 3 4 5
是一个奇怪的函数,它将4个参数相加,然后{{1返回14。
编写简洁灵活代码的其他便捷工具包括composition和application 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 + z
和f . g . h $ x + y + z
是等效的,但(.)
和($)
是不同的内容,请阅读Haskell: difference between . (dot) and $ (dollar sign)和parts from Learn You a Haskell了解其中的差异。
答案 3 :(得分:3)
如果Haskell没有内置支持,可以考虑如何将curry实现为更高阶函数。这是一个Haskell实现,适用于两个参数的函数。
curry :: (a -> b -> c) -> a -> (b -> c)
curry f a = \b -> f a b
现在你可以在两个参数和第一个参数上传递curry
函数,它将在一个参数上返回一个函数(这是一个闭包的例子。)
在ghci:
Prelude> let curry f a = \b -> f a b
Prelude> let g = curry (+) 5
Prelude> g 10
15
Prelude> g 15
20
Prelude>
幸运的是,我们不必在Haskell中执行此操作(如果您需要currying,则可以在Lisp中执行此操作),因为支持已内置于该语言中。
答案 4 :(得分:3)
如果您来自类C语言,他们的语法可能会帮助您理解它。例如,在PHP中,add函数可以这样实现:
function add($a) {
return function($b) use($a) {
return $a + $b;
};
}