您如何在 F# 或任何函数式语言中对第二个(或第三个、第四个、...)参数进行 curry?
Posted
技术标签:
【中文标题】您如何在 F# 或任何函数式语言中对第二个(或第三个、第四个、...)参数进行 curry?【英文标题】:How do you curry the 2nd (or 3rd, 4th, ...) parameter in F# or any functional language? 【发布时间】:2010-12-27 00:49:57 【问题描述】:我刚刚开始使用 F#,看看如何使用 currying 将第一个参数预加载到函数中。但是如何使用第二个、第三个或任何其他参数来做到这一点?命名参数会使这更容易吗?是否有任何其他函数式语言具有命名参数或其他方式使柯里化对参数顺序无动于衷?
【问题讨论】:
在 Haskell 中有一个flip
函数,它可以反转函数参数的顺序。有了它,你可以在第二个参数上做到这一点。
flip
和 (.)
的混合可以让您将其扩展到任何参数。
【参考方案1】:
通常你只使用一个 lambda:
fun x y z -> f x y 42
是一个类似 'f' 的函数,但第三个参数绑定到 42。
您也可以使用组合器(就像有人在评论中提到 Haskell 的“翻转”),它重新排列参数,但我有时会觉得这很混乱。
请注意,大多数柯里化函数都是这样编写的,因此参数最有可能被部分应用。
F# 为方法(不是 let-bound 函数值)命名参数,但名称适用于“元组”参数。命名的柯里化参数没有多大意义;如果我有一个两个参数的柯里化函数'f',我希望给定
let g = f
let h x y = f x y
那么 'g' 或 'h' 可以替代 'f',但 'named' 参数使得这不一定正确。也就是说,“命名参数”与语言设计的其他方面的交互很差,我个人不知道有什么好的设计可以让“命名参数”与“一流的柯里化函数值”很好地交互。
【讨论】:
【参考方案2】:OCaml 是 F# 所基于的语言,它具有可以按任何顺序指定的标记(和可选)参数,并且您可以根据这些参数的名称部分应用函数。我不相信 F# 有这个功能。
您可以尝试创建类似 Haskell 的 flip
函数。创建在参数列表中进一步跳转参数的变体应该不会太难。
let flip f a b = f b a
let flip2 f a b c = f b c a
let flip3 f a b c d = f b c d a
【讨论】:
F# 具有可选的命名参数,但它们仅适用于member
定义(不适用于 let
定义),并且它们不能以这种方式进行柯里化(您未指定的所有参数都将采用它们的默认值,你不能省略非可选的)。
@Pavel:看看我的回答。【参考方案3】:
为了完整起见——既然你问过其他函数式语言——这就是你在 OCaml 中的做法,可以说是 F# 的“母亲”:
$ ocaml
# let foo ~x ~y = x - y ;;
val foo : x:int -> y:int -> int = <fun>
# foo 5 3;;
- : int = 2
# let bar = foo ~y:3;;
val bar : x:int -> int = <fun>
# bar 5;;
- : int = 2
因此,在 OCaml 中,您可以硬编码您想要的任何命名参数,只需使用其名称(上例中的y
)。
Microsoft 选择不实现此功能,正如您发现的那样...在我看来,这不是“与语言设计的其他方面的交互不佳”...更有可能是因为额外的努力将需要(在语言实现中)以及将语言引入世界所导致的延迟 - 实际上只有少数人会(a)意识到 OCaml 的“降级”,(b)无论如何都使用命名函数参数.
我是少数,并且确实使用它们 - 但它确实很容易在 F# 中通过本地函数绑定进行模拟:
let foo x y = x - y
let bar x = foo x 3
bar ...
【讨论】:
【参考方案4】:可以在不声明任何内容的情况下执行此操作,但我同意Brian 即a lambda or a custom function is probably a better solution。
我发现我最常希望使用它来部分应用除法或减法。
> let halve = (/) >> (|>) 2.0;;
> let halfPi = halve System.Math.PI;;
val halve : (float -> float)
val halfPi : float = 1.570796327
概括地说,我们可以声明一个函数applySecond
:
> let applySecond f arg2 = f >> (|>) arg2;;
val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)
为了遵循逻辑,这样定义函数可能会有所帮助:
> let applySecond f arg2 =
- let ff = (|>) arg2
- f >> ff;;
val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)
现在f
是一个从'a
到'b -> 'c
的函数。这是由ff
组成的,这是一个从'b -> 'c
到'c
的函数,它是arg2
部分应用到正向管道运算符的结果。此函数将传递给arg2
的特定'b
值应用于其参数。因此,当我们将f
与ff
组合在一起时,我们会得到一个从'a
到'c
的函数,它使用'b
参数的给定值,这正是我们想要的。
将上面的第一个示例与以下示例进行比较:
> let halve f = f / 2.0;;
> let halfPi = halve System.Math.PI;;
val halve : f:float -> float
val halfPi : float = 1.570796327
还比较这些:
let filterTwoDigitInts = List.filter >> (|>) [10 .. 99]
let oddTwoDigitInts = filterTwoDigitInts ((&&&) 1 >> (=) 1)
let evenTwoDigitInts = filterTwoDigitInts ((&&&) 1 >> (=) 0)
let filterTwoDigitInts f = List.filter f [10 .. 99]
let oddTwoDigitInts = filterTwoDigitInts (fun i -> i &&& 1 = 1)
let evenTwoDigitInts = filterTwoDigitInts (fun i -> i &&& 1 = 0)
或者,比较:
let someFloats = [0.0 .. 10.0]
let theFloatsDividedByFour1 = someFloats |> List.map ((/) >> (|>) 4.0)
let theFloatsDividedByFour2 = someFloats |> List.map (fun f -> f / 4.0)
lambda 版本似乎更易于阅读。
【讨论】:
【参考方案5】:在 Python 中,您可以使用 functools.partial
或 lambda。 Python 有命名参数。
functools.partial
可用于指定第一个位置参数以及任何命名参数。
from functools import partial
def foo(a, b, bar=None):
...
f = partial(foo, bar='wzzz') # f(1, 2) ~ foo(1, 2, bar='wzzz')
f2 = partial(foo, 3) # f2(5) ~ foo(3, 5)
f3 = lambda a: foo(a, 7) # f3(9) ~ foo(9, 7)
【讨论】:
问题根本与 Python 无关。 问题标题写着:“...或任何函数式语言”。 无论有多少人尝试并争论它,Python 都无法正常工作 来自***:Python [...] 支持多种编程范式,包括面向对象、命令式、函数式编程和过程风格。以上是关于您如何在 F# 或任何函数式语言中对第二个(或第三个、第四个、...)参数进行 curry?的主要内容,如果未能解决你的问题,请参考以下文章
MYSQL SELECT:如何获取一个额外的列,指示是不是满足 WHERE 子句中的第一个或第二个条件?
C语言编写函数,对浮点数保留两位小数,对第三位四舍五入。程序如下 #include<stdio.h
如何在不使用css调整第一个图像大小的情况下在Wordpress页面上调整第二个图像的大小?