如何在 F# 或任何函数式语言中柯里化第二个(或第三个、第四个……)参数?

发布于 2024-08-14 08:58:54 字数 118 浏览 17 评论 0原文

我刚刚开始使用 F#,看看如何使用柯里化将第一个参数预加载到函数中。但是如何使用第二个、第三个或任何其他参数来做到这一点呢?命名参数会让这变得更容易吗?是否有任何其他函数式语言具有命名参数或其他方式使柯里化与参数顺序无关?

I'm just starting up with F# and see how you can use currying to pre-load the 1st parameter to a function. But how would one do it with the 2nd, 3rd, or whatever other parameter? Would named parameters to make this easier? Are there any other functional languages that have named parameters or some other way to make currying indifferent to parameter-order?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

恋竹姑娘 2024-08-21 08:58:55

只是为了完整性 - 既然您询问了其他函数语言 - 这就是您在 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 中,您可以硬编码您想要的任何命名参数,只需使用其名称 (<上例中的 code>y)。

正如您所发现的那样,微软选择不实现此功能...以我的拙见,这与“与语言设计的其他方面的交互不良”无关...更有可能是因为这需要额外的努力(在语言实现中)以及它将导致语言走向世界的延迟 - 事实上只有很少人会(a)意识到 OCaml 的“降级”,(b)无论如何使用命名函数参数。

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

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

Just for completeness - and since you asked about other functional languages - this is how you would do it in OCaml, arguably the "mother" of 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

So in OCaml you can hardcode any named parameter you want, just by using its name (y in the example above).

Microsoft chose not to implement this feature, as you found out... In my humble opinion, it's not about "poor interaction with other aspects of the language design"... it is more likely because of the additional effort this would require (in the language implementation) and the delay it would cause in bringing the language to the world - when in fact only few people would (a) be aware of the "stepdown" from OCaml, (b) use named function arguments anyway.

I am in the minority, and do use them - but it is indeed something easily emulated in F# with a local function binding:

let foo x y = x - y
let bar x = foo x 3
bar ...
避讳 2024-08-21 08:58:55

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

In Python, you can use functools.partial, or a lambda. Python has named arguments.
functools.partial can be used to specify the first positional arguments as well as any named argument.

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)
瀟灑尐姊 2024-08-21 08:58:55

可以在不声明任何内容的情况下执行此操作,但我同意 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< 的函数/code> 到 'b -> 'c.它由 ff 组成,它是来自 'b ->; 的函数。 'c'c,这是 arg2 部分应用到前向管道运算符的结果。此函数将为 arg2 传递的特定 'b 值应用于其参数。因此,当我们将 fff 组合时,我们会得到一个从 '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 版本似乎更容易阅读。

It's possible to do this without declaring anything, but I agree with Brian that a lambda or a custom function is probably a better solution.

I find that I most frequently want this for partial application of division or subtraction.

> let halve = (/) >> (|>) 2.0;;
> let halfPi = halve System.Math.PI;;

val halve : (float -> float)
val halfPi : float = 1.570796327

To generalize, we can declare a function applySecond:

> let applySecond f arg2 = f >> (|>) arg2;;
val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)

To follow the logic, it might help to define the function thus:

> let applySecond f arg2 =
-     let ff = (|>) arg2
-     f >> ff;;
val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)

Now f is a function from 'a to 'b -> 'c. This is composed with ff, a function from 'b -> 'c to 'c that results from the partial application of arg2 to the forward pipeline operator. This function applies the specific 'b value passed for arg2 to its argument. So when we compose f with ff, we get a function from 'a to 'c that uses the given value for the 'b argument, which is just what we wanted.

Compare the first example above to the following:

> let halve f = f / 2.0;;
> let halfPi = halve System.Math.PI;;

val halve : f:float -> float
val halfPi : float = 1.570796327

Also compare these:

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)

Alternatively, compare:

let someFloats = [0.0 .. 10.0]
let theFloatsDividedByFour1 = someFloats |> List.map ((/) >> (|>) 4.0)
let theFloatsDividedByFour2 = someFloats |> List.map (fun f -> f / 4.0)

The lambda versions seem to be easier to read.

马蹄踏│碎落叶 2024-08-21 08:58:54

通常你只使用 lambda:

fun x y z -> f x y 42

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

你也可以使用组合器(就像有人在评论中提到 Haskell 的“flip”),它重新排序参数,但我有时发现令人困惑。

请注意,大多数柯里化函数都是这样编写的,即最有可能部分应用的参数排在第一位。

F# 为方法提供了命名参数(不是 let 绑定的函数值),但这些名称适用于“元组”参数。命名的柯里化参数没有多大意义;如果我有一个双参数柯里化函数“f”,我希望给定

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

“g”或“h”可以替代“f”,但“命名”参数使得这不一定成立。也就是说,“命名参数”与语言设计的其他方面的交互效果很差,而且我个人不知道有什么好的设计可以让“命名参数”与“第一类柯里化函数值”交互良好。

Typically you just use a lambda:

fun x y z -> f x y 42

is a function like 'f' but with the third parameter bound to 42.

You can also use combinators (like someone mentioned Haskell's "flip" in a comment), which reorder arguments, but I sometimes find that confusing.

Note that most curried functions are written so that the argument-most-likely-to-be-partially-applied comes first.

F# has named parameters for methods (not let-bound function values), but the names apply to 'tupled' parameters. Named curried parameters do not make much sense; if I have a two-argument curried function 'f', I would expect that given

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

then 'g' or 'h' would be substitutable for 'f', but 'named' parameters make this not necessarily true. That is to say, 'named parameters' can interact poorly with other aspects of the language design, and I personally don't know of a good design offhand for 'named parameters' that interacts well with 'first class curried function values'.

聊慰 2024-08-21 08:58:54

F# 所基于的语言 OCaml 具有可以按任何顺序指定的标记(和可选)参数,并且您可以根据这些参数名称部分应用函数。我不相信 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

OCaml, the language that F# was based on, has labeled (and optional) arguments that can be specified in any order, and you can partially apply a function based on those arguments' names. I don't believe F# has this feature.

You might try creating something like Haskell's flip function. Creating variants that jump the argument further in the argument list shouldn't be too hard.

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文