我刚刚开始使用 F#,看看如何使用 currying 将第一个参数预加载到函数中。但是如何使用第二个、第三个或任何其他参数来做到这一点?命名参数会使这更容易吗?是否有任何其他函数式语言具有命名参数或其他方式使柯里化对参数顺序无动于衷?
5 回答
通常你只使用一个 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' 参数使得这不一定正确。也就是说,“命名参数”与语言设计的其他方面的交互效果很差,我个人不知道有什么好的设计可以让“命名参数”与“一流的柯里化函数值”很好地交互。
OCaml 是 F# 所基于的语言,它具有可按任何顺序指定的标记(和可选)参数,您可以根据这些参数的名称部分应用函数。我不相信 F# 有这个功能。
您可以尝试创建类似 Haskellflip
函数的东西。创建在参数列表中进一步跳转参数的变体应该不会太难。
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
只是为了完整起见——既然你问过其他函数式语言——这就是你在 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 ...
可以在不声明任何内容的情况下执行此操作,但我同意Brian的观点,即lambda 或自定义函数可能是更好的解决方案。
我发现我最常希望将其用于除法或减法的部分应用。
> 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
,一个函数 from 'b -> 'c
to组成,该函数由对正向管道运算符'c
的部分应用产生。arg2
此函数将'b
传递的特定值应用于arg2
其参数。所以当我们用 组合f
时ff
,我们得到一个函数 from 'a
to'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 版本似乎更容易阅读。
在 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)