【问题标题】:Reverse currying?反向柯里化?
【发布时间】:2011-08-18 11:01:36
【问题描述】:

我想以某种方式组合函数。请在伪代码(不是 F#)中考虑这 2 个函数

F1 = x + y
F2 = F1 * 10 // note I did not specify arguments for F1, 'reverse curry' for lack of a better word

我想让 F# 做的是弄清楚,因为

let F1 x y = x + y
//val F1 : int -> int -> int

代码 let F2 = F1 * 10 会给我与 F1 相同的签名:val F2 : int -> int -> int,而调用 F2 2 3 将导致 50:(2 + 3) * 10。那会相当聪明...

发生的事情完全不同。第一行按预期进行:

let F1 x y = x + y
//val F1 : int -> int -> int

但是当我添加第二行 let F2 = F1 * 10 时,它会抛出 F#。它抱怨 the type int does not match the type 'a -> 'b -> 'c 和 F1 现在 requires member ( + )

我当然可以这样拼出来:

let F1(x, y) = x + y
let F2(x, y) = F1(x, y) * 10

但现在我还不如使用 C#,我们不再那么遥远了。元组参数破坏了 F# 的许多优雅。此外,我的真实函数 F1 和 F2 的参数远不止 2 个,所以这让我眼花缭乱,这正是我想通过使用 F# 来躲避的。这样说会更自然:

let F1 x y = x + y
let F2 = F1 * 10

有什么方法可以(几乎)做到这一点?

额外积分:这些错误消息到底是怎么回事?为什么第二行let F2 = F1 * 10会改变第一行的打字?

提前感谢您的想法,

格特-简

更新 两种方法(几乎)可以实现所描述的功能。

一个使用元组。第二行看起来有点古怪,工作正常。小缺点是我现在不能使用柯里化,否则我将不得不添加更多古怪的代码。

let F1 (a, b) = a + b
let F2 = F1 >> (*) 10

F2(2, 3) // returns 50

另一种方法是使用记录。乍一看更直接,更容易理解,但需要更多的代码和仪式。确实去掉了 F# 的一些优雅,看起来更像 C#。

type Arg (a, b) =
    member this.A = a
    member this.B = b

let F1 (a:Arg) = a.A + a.B
let F2 (a:Arg) = F1(a) * 10

F2 (Arg(2, 3)) // returns 50

【问题讨论】:

  • 查看this recent question 的 Haskell 组合。这本质上是同一个问题,但有一些有趣的解决方案可能会在 F# 中实现(我不是母语人士)。
  • 嗨,哈马尔,谢谢,Haskell 让我觉得自己有阅读障碍。我想这就是他们对“浓缩汤”的意思。稍后我会试一试,也许所有的 Haskell 看起来确实是一个很好的“心灵健身房”。谢谢!
  • 如果F1 取一个元组,那么F2 可以拼写为let F2 = F1 >> (*) 10
  • @ildjarn 感谢这个例子,我一开始没有得到元组的方法。我也用这种方法更新了 OP。

标签: f# functional-programming currying higher-order-functions


【解决方案1】:

顺便说一句,使用无点(或者在这种情况下毫无意义)方法可以通过以下方式定义这些函数:

let F1 = (+)
let F2 = (<<)((*)10) << F1

【讨论】:

    【解决方案2】:

    这样会比较聪明……

    太聪明了,它可以击败类型系统。你想要的是 APL 中的数组编程。

    有什么办法可以(几乎)做到这一点?

    我不会说 F#,但在 Haskell 中,你会 uncurry F1,然后用 *10 组合,然后 curry:

    f2 = curry ((*10) . uncurry f1)
    

    在 F# 等 ML 方言中变成这样:

    let curry f x y = f (x,y)
    let uncurry f (x,y) = f x y
    
    let mult x y = x * y
    
    let F1 x y = x + y
    let F2 = curry (uncurry F1 >> mult 10)
    

    (我不确定curryuncurry 是否在F# 标准库中,所以我定义了它们。还有一种更漂亮的方法可以在不定义mult 的情况下部分应用*。 )

    【讨论】:

    • 'clever' LOL 我想是的。。而且当我描述我真正想做的事情时,APL 或 haskell 往往会弹出。这么多漂亮的语言,这么少的时间。让我试着理解你和托马斯所说的话,然后回到这里。
    • 我喜欢这种方法的抽象,其中函数是柯里化和非柯里化的,尽管这些也是特定于 2 个参数的。在 F# 中也没有找到任何通用运算符,Haskell 可以为任何函数做到这一点?那很好。然而,该技术确实引入了相当多的“技术噪音”,其中有很多代码是关于拼写“如何”而不仅仅是“什么”。理想情况下,我希望得到的代码读起来就像你在白板上为非编程序写的一样,但我想你是对的,在解析器/类型系统上很难解决所有不同的情况。
    • @gjvdkamp:一定数量的类型类魔法可能会在 Haskell 中发挥作用。我不知道 F# 是否支持类型类。
    • 我必须调查一下。我切换到 F# 的主要原因是能够更“直观”地编写代码,而无需 C# 所具有的所有花括号、返回语句和可变变量等。因此,尽管 F# 和 Haskell 可能会提供工具来完成我所描述的工作,但它会引入很多看起来像“魔法咒语”的东西。那会破坏我切换到 F# 的主要目的。感谢您的宝贵时间!
    【解决方案3】:

    一般来说没有这种模式。使用 larsmans 建议的组合器(如 curryuncurry)是一种选择,但我认为结果比显式版本更不可读且更长。

    如果你经常使用这个特定的模式,你可以定义一个运算符来将一个函数(有两个参数)乘以一个标量:

    let ( ** ) f x = fun a b -> (f a b) * x
    
    let F1 x y = x + y
    let F2 = F1 ** 10
    

    很遗憾,您无法将标准数字运算符(* 等)的实现添加到现有类型(例如 'a -&gt; 'b -&gt; int)。但是,这是非常频繁的请求(它对其他事情很有用)。或者,您可以将函数包装到提供重载数字运算符的某个对象中(并包含一些用于运行该函数的 Invoke 方法)。

    我认为一个合适的名称应该是提升 - 您将 * 运算符(处理整数)提升到一个适用于返回整数的函数的版本。当您使用 * 处理可空类型时,它类似于在 C# 编译器中完成的提升。

    解释错误消息 - 它抱怨表达式F1 * 10

    错误 FS0001:类型 'int' 与类型 ''a -> 'b -> 'c' 不匹配

    我认为这意味着编译器正在尝试为* 运算符查找实例化。从右边看,它应该是int,所以它认为左边也应该是int——但它实际上是两个参数的函数——比如'a -&gt; 'b -&gt; c'

    【讨论】:

    • 嗨,我想简短的回答是否定的,你不能有一个 HO 函数可以将任何函数与任何标量相乘。因为 F# 是在后台使用泛型进行静态类型的,所以它是否需要知道所述函数的参数数量?这样总结正确吗?那么现在没有办法让参数的数量保持打开状态,解决方法就是将 HO 函数创建为运算符,它可以一次性完成 uncurry 和乘法运算,但它对于具有 2 个 args 的函数是特定的吗?跨度>
    • 是的,没错。让函数的参数数量保持打开状态的唯一方法是使用一些讨厌的反射技巧(但这并不能完全满足您的需求)。但是,您可以在元组中传递参数(使用单参数函数)。
    • 嗯..我想我会去记录一下,这样我就可以命名参数以避免混淆的可能性。实际上,这将是一个很好的设计,然后 Id 只需将那条记录传递下去。如果有语言功能(如所述)来支持这一点,那就太好了,但我想记录会很好用。
    猜你喜欢
    • 2011-05-26
    • 2016-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    • 2015-08-17
    • 2017-01-20
    • 2018-01-24
    相关资源
    最近更新 更多