你如何在F#或任何函数式语言中讨论第二个(或第三个,第四个......)参数?

时间:2009-12-15 23:04:22

标签: f# functional-programming currying named-parameters

我刚刚开始使用F#,看看如何使用currying将第一个参数预加载到函数中。但是如何用第二,第三或其他任何参数来做呢?命名参数会使这更容易吗?是否还有其他任何具有命名参数的函数式语言或其他一些使得currying对参数顺序无关紧要的方法?

5 个答案:

答案 0 :(得分:19)

通常你只使用lambda:

fun x y z -> f x y 42

是类似'f'的函数,但第三个参数绑定到42。

你也可以使用组合器(就像有人在评论中提到Haskell的“翻转”),它会重新排序参数,但我有时会觉得这很混乱。

请注意,大多数curried函数都是这样编写的,因此最有可能部分应用的参数首先出现。

F#命名了方法的参数(不是let-bound函数值),但这些名称适用于'tupled'参数。命名的咖喱参数没有多大意义;如果我有两个参数curried函数'f',我会期望给出

let g = f
let h x y = f x y

然后'g'或'h'可替代'f',但'named'参数使得这不一定是真的。也就是说,“命名参数”与语言设计的其他方面的交互性很差,而且我个人并不知道“命名参数”的良好设计与“一流的curried函数值”相互作用。 / p>

答案 1 :(得分:6)

只是为了完整 - 而且因为你问过其他功能语言 - 这就是你如何在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)对您想要的任何命名参数进行硬编码。

微软选择不实施此功能,正如您所发现的那样......在我的拙见中,它并不是关于与语言设计的其他方面的不良交互&#34; ...它更有可能是因为这需要额外的努力(在语言实现中)以及它将语言带到世界的延迟 - 实际上只有少数人会(a)意识到&#34; stepdown& #34;来自OCaml,(b)无论如何都使用命名函数参数。

我是少数,并且确实使用它们 - 但它确实可以在F#中使用本地函数绑定轻松模拟:

let foo x y = x - y
let bar x = foo x 3
bar ...

答案 2 :(得分:5)

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

答案 3 :(得分:4)

在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)

答案 4 :(得分:3)

可以在不声明任何内容的情况下执行此操作,但是我同意Brian认为a lambda or a custom function is probably a better solution

我发现我最经常希望将其用于除法或减法的部分应用。

lags

为概括起见,我们可以声明一个函数> 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传递的特定'b值应用于其参数。因此,当我们将arg2f组合在一起时,我们得到一个从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)

lambda版本似乎更易于阅读。