currying或部分应用有什么特别之处?


9

我每天都在阅读有关函数式编程的文章,并尝试尽可能地应用一些实践。但是我不明白在currying或部分应用程序中有什么独特之处。

以以下Groovy代码为例:

def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }

我不了解tripler1和之间的区别tripler2。他们不是都一样吗?在纯或部分功能的语言(例如Groovy,Scala,Haskell等)中支持“ currying”。但是,我可以通过简单地创建另一个命名或匿名方法来做相同的事情(left-curry,right-curry,n-curry或部分应用程序)函数或闭包将tripler2大多数语言(甚至C)中的参数转发到原始函数(如)

我在这里想念什么吗?在某些地方可以在Grails应用程序中使用currying和Partial应用程序,但是我很犹豫这样做,因为我在问自己“那有什么不同?”

请赐教。

编辑:你们是说部分应用程序/ currying比创建/调用将默认参数转发到原始函数的另一个函数更有效吗?


1
有人可以创建“ curry”或“ currying”标签吗?
Vigneshwaran

您如何用C咖喱?
乔治

这可能真的更多是关于部分应用程序程序员的
。stackexchange.com/ questions / 152868 /…

1
@Vigneshwaran:AFAIK,您不需要使用支持currying的语言来创建另一个函数。例如,在Haskell中f x y = x + y表示该f函数采用一个int参数。f xf应用于x)的结果是一个带有一个int参数的函数。结果f x y(或(f x) y,即f x施加到y)是不带任何输入参数,并且通过减少计算的表达式x + y
乔治

1
您可以实现相同的目标,但是用C付出的努力却要痛苦得多,而且效率不如haskell这样的默认行为那样有效
Daniel Gratzer 2012年

Answers:


8

咖喱化是关于将代表n个输入的函数变成代表每个n个输入的1个函数。部分应用是关于将某些输入固定到函数。

局部应用的动机主要是它使编写高阶函数库更加容易。例如,C ++ STL中的算法全部采用谓词或一元函数,bind1st允许库用户使用值绑定来钩住非一元函数。因此,库编写器不需要为所有采用一元函数提供二进制版本的算法提供重载函数

咖喱本身很有用,因为它可以在您想要的任何地方免费提供部分应用程序,即您不再需要像bind1st部分应用程序那样的功能。


currying特定于常规的还是跨语言适用的?
两栖游戏,2012年

@foampile它的东西是适用的跨语言,但讽刺常规咖喱并没有真正做到这一点programmers.stackexchange.com/questions/152868/...
JK。

@jk。您是说currying / partial应用程序比创建和调用另一个函数更有效吗?
Vigneshwaran

2
@Vigneshwaran-不一定性能更高,但是就程序员的时间而言,它肯定更加有效。还要注意,虽然currying支持许多功能语言,但OO或过程语言通常不支持。(或者至少不是语言本身。)
Stephen C

6

但是我可以通过简单地创建另一个命名或匿名函数或闭包(将参数转发给大多数语言的原始函数(如Tripler2))来做相同的事情(左咖喱,右咖喱,n咖喱或部分应用程序)(甚至C。)

优化器将对此进行研究,并迅速进行它可以理解的工作。对于最终用户来说,咖喱是一个不错的小技巧,但是从语言设计的角度来看,咖喱具有更好的好处。将所有方法处理为一元,这可能是另一种方法,这真的很好。A -> BB

它简化了为处理高阶函数而必须编写的方法。您使用该语言进行的静态分析和优化只有一条以已知方式运行的路径。参数绑定只是不属于设计范围,而不是需要箍来执行这种常见行为。


6

作为@jk。暗指可以使代码更通用。

例如,假设您具有以下三个功能(在Haskell中):

> let q a b = (2 + a) * b

> let r g = g 3

> let f a b = b (a 1)

f这里的函数将两个函数作为参数,传递1给第一个函数,并将第一次调用的结果传递给第二个函数。

如果我们f使用qr作为参数调用,它实际上是在做:

> r (q 1)

q将在何处应用1并返回另一个函数(根据要求q);然后,此返回的函数将r作为参数传递给3。其结果将是的值9

现在,我们有另外两个功能:

> let s a = 3 * a

> let t a = 4 + a

我们也可以将它们传递给f并获得7或的值15,这取决于我们的论点是s t还是t s。由于这些函数均返回值而不是函数,因此不会在f s t或中进行部分应用f t s

如果我们写了fqr记,我们可能会使用一个lambda(匿名函数),而不是局部的应用,例如:

> let f' a b = b (\x -> a 1 x)

但这将限制的通用性f'f可以使用参数qrs和调用t,但f'只能使用qr- 调用,f' s t并且f' t s两者都会导致错误。

更多

如果f'使用带有两个以上参数的q'/ r'对进行q'调用,则q'最终仍将部分地应用f'

另外,您也可以q在外部f而不是内部进行包装,但这会给您带来讨厌的嵌套lambda:

f (\x -> (\y -> q x y)) r

从本质上讲,这是咖喱q的第一要义!


你睁开我的眼睛。您的回答使我意识到,咖喱/部分应用的函数与创建将参数传递给原始函数的新函数有何不同。1.传递curcured / paed函数(例如f(q.curry(2)))比只为临时使用而不必要地创建单独的函数更整洁(在groovy等函数语言中)
Vigneshwaran 2012年

2.在我的问题中,我说:“我可以用C做同样的事情。” 是的,但是在非功能语言中,您不能将功能作为数据传递,创建单独的功能,将参数转发到原始功能,并不能获得currying / pa的所有好处
Vigneshwaran 2012年

我注意到Groovy不支持Haskell支持的一般化类型。我不得不写def f = { a, b -> b a.curry(1) },使f q, r工作和def f = { a, b -> b a(1) }def f = { a, b -> b a.curry(1)() }用于f s, t工作。您必须传递所有参数或明确表示您正在使用。:(
Vigneshwaran 2012年

2
@Vigneshwaran:是的,可以肯定地说Haskell和curring 相处得很好。;]请注意,在Haskell中,默认情况下f x y会对函数进行正确的定义(使用正确的定义),而空格表示函数的应用程序,因此意味着许多语言将编写f(x)(y)而不是f(x, y)。如果编写代码,那么您的代码可能会在Groovy中工作,q从而使其期望像q(1)(2)
CA McCann 2012年

1
@Vigneshwaran很高兴我可以帮忙!对于必须明确声明您正在执行部分应用程序,我感到很痛苦。在Clojure中,我必须做的(partial f a b ...)-习惯于Haskell,在使用其他语言编程时,我会错过很多适当的技巧(尽管最近我一直在F#中工作,值得庆幸的是,它支持它)。
paul 2012年

3

关于部分应用有两个关键点。首先是句法/便利性-正如@jk所提到的,一些定义变得更容易阅读和编写。(有关更多信息,请查看Pointfree编程!)

正如@telastyn所提到的,第二个关于功能模型,而不仅仅是方便。在Haskell版本中,由于不熟悉具有部分应用程序的其他语言,因此我将从中获取示例,所有函数均采用单个参数。是的,甚至功能如下:

(:) :: a -> [a] -> [a]

争论一个 由于函数类型构造函数的关联性->,以上等效于:

(:) :: a -> ([a] -> [a])

这是一个需要a并返回一个函数的函数[a] -> [a]

这使我们可以编写如下函数:

($) :: (a -> b) -> a -> b

可以将任何函数应用于适当类型的参数。甚至是疯狂的人,例如:

f :: (t, t1) -> t -> t1 -> (t2 -> t3 -> (t, t1)) -> t2 -> t3 -> [(t, t1)]
f q r s t u v = q : (r, s) : [t u v]

f' :: () -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)]
f' = f $ ((), 'a')  -- <== works fine

好的,那是一个人为的例子。但是更有用的是涉及Applicative类型类该类包括以下方法:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

如您所见,类型与$带走Applicative f位类似,实际上,此类描述了上下文中的函数应用程序。因此,代替正常功能的应用程序:

ghci> map (+3) [1..5]  
[4,5,6,7,8]

我们可以在应用上下文中应用函数;例如,在Maybe上下文中,可能存在或缺少某些东西:

ghci> Just map <*> Just (+3) <*> Just [1..5]
Just [4,5,6,7,8]

ghci> Just map <*> Nothing <*> Just [1..5]
Nothing

现在,最酷的部分是Applicative类型类没有提及任何一个以上参数的函数,尽管如此,它可以处理它们,甚至包括6个参数的函数,例如f

fA' :: Maybe (() -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)])
fA' = Just f <*> Just ((), 'a')

据我所知,如果没有部分应用的概念,那么一般类型的Applicative类型类是不可能的。(对于那里的任何编程专家-如果我错了,请纠正我!)当然,如果您的语言缺少部分应用程序,则可以以某种形式构建它,但是...就是不一样,是吗? ?:)


1
Applicative无需招惹或部分申请fzip :: (f a, f b) -> f (a, b)。在具有高阶函数的语言中,这使您可以将currying和部分应用程序提升到函子的上下文中,并且等效于(<*>)。没有高阶函数,您将没有,fmap所以整个事情将毫无用处。
CA McCann 2012年

@CAMcCann感谢您的反馈!我知道我对这个答案感到头疼。那我说错了吗?

1
当然,这是正确的。在“一般形式”,“可能的”和“部分使用的概念”的定义上进行梳理不会改变简单的事实,即迷人的f <$> x <*> y惯用风格很容易工作,因为弯曲和部分使用很容易工作。换句话说,令人愉快的事情比这里可能发生的事情更为重要。
CA McCann 2012年

每当我看到函数式编程的代码示例时,我都会更确信这是一个精心设计的笑话,并且它不存在。
Kieveli 2013年

1
@Kieveli不幸的是你有这种感觉。那里有许多 精美的 教程,可以帮助您入门并充分了解基础知识。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.