【问题标题】:How to invoke function using named parameter arguments in F#如何在 F# 中使用命名参数参数调用函数
【发布时间】:2021-05-10 18:50:57
【问题描述】:

如何通过在调用点指定参数名称来调用F#函数?

我尝试了以下方法:

let add x y =
    x + y

add (x = 10) (y = 10) // How to specify the name x and y when calling add?

但它给出了这个错误:

错误 FS0039:未定义值或构造函数“x”。

【问题讨论】:

    标签: f#


    【解决方案1】:

    您不能使用命名参数调用let-bound 函数。仅允许用于类中的方法

    命名参数只允许用于方法,不允许用于 let-bound 函数、函数值或 lambda 表达式。

    Documentation


    从技术上讲,您可以声明静态类并从中使用方法,但我认为这是错误的。只是错了。不要这样做

    [<AbstractClass; Sealed>]
    type MathOperations =
        static member Add (x, y) = x + y
    
    open type MathOperations
    
    [<EntryPoint>]
    let main argv =
        Add(x = 3, y = 4)
        |> printfn "%d"
        0
    

    【讨论】:

      【解决方案2】:

      这个问题一开始也让我很困惑。函数和方法不一样。

      • 默认情况下,函数被柯里化以支持部分应用程序。它们不支持命名参数调用。
      • 方法支持对所有参数使用单个元组的命名参数。
        • (在Bent Tranberg 的评论后编辑) 方法还支持柯里化声明 member _.Add x y = x + y 甚至混合样式声明 member _.Add3 (x: int, y: int) (z: int) = x + y + z 但这些方法不能使用命名参数调用 o.Add3 (y=2, x=1) 3 ?

      在 JavaScript 中,可以使用对象字面量作为参数来模拟命名参数。让我们尝试在 F# 中做同样的事情,使用元组或记录:

      1.元组: 方法参数通过一个元组一次性提供。我们可以使用元组作为函数参数并使用命名参数调用函数吗?没有❌

      let add (x, y) = x + y
      
      add (x = 1, y = 2) // ?
      //   ~ Error FS0039: The value or constructor 'x' is not defined
      

      2。匿名记录:由于匿名记录解构的实际限制,不可能 ❌

      let add {| X = x; Y = y |} = x + y
      //      ~~ Error FS0010: Unexpected symbol '{|' in pattern
      

      3。命名记录:⚠️

      type AddParam = { x: int; y: int }
      
      let add { x = x; y = y } = x + y
      
      add { x = 1; y = 2 }
      

      这样,我们可以命名参数并选择它们的顺序({ y = 2; x = 1 },但我们失去了部分应用程序。所以它不是惯用的→应该避免,除非在特定情况下。

      4.单例DU:?

      如果我们只关心参数命名而不关心参数重新排序,我们可以使用可区分联合,尤其是单例 DU:

      type XParam = X of int
      type YParam = Y of int
      
      let add (X x) (Y y) = x + y
      
      add (X 1) (Y 2)
      

      这样,参数显示为已命名并保留部分应用程序。

      --

      编辑

      ☝ 我不建议创建单例 DU 只是为了模拟命名参数!反之亦然:当我们的域模型中有单例 DU 时,我们知道它们会将其语义用于记录代码并帮助推理代码,在这种情况下或多或少类似于命名参数。

      【讨论】:

      • 错了。方法还支持柯里化参数和部分应用。您是正确的,我们不能同时拥有这两个功能,但不正确的是如何应用。您只能将命名参数用于不使用柯里化参数的方法。例如。 member _.AddTupled (x: int, y: int) (z: int) = 不能像 .AddTupled (y=2, x=1) 3 那样调用
      • @BentTranberg :我不知道这个功能实际上有点奇怪,我猜它并不常用。我编辑并修复了我的答案。谢谢?
      • 在我发现这一点之前,我已经进入 F# 一两年了,所以我很同情。更多信息:如果方法重载,则所有版本都必须具有元组形式。最重要的是,我发现你不能将一个真正的元组作为参数传递给一个重载方法,该方法需要多个元组形式的参数。如果方法没有被重载,那么你可以传递一个真正的元组,就像函数一样。我认为您的说法“默认情况下函数是 curried”有点奇怪,但您认为这里有两个世界在起作用显然是正确的。
      【解决方案3】:

      您必须了解 F# 中的函数调用,这与 C# 等传统命令式语言不同。

      let f x y = x + y // defines a function with this signature:
      val f : x:int -> y:int -> int
      
      // this function is full or partially applicable:
      
      let g = f 4 // partial application
      val g : (int -> int) // a new function that evaluates f 4 y
      g 10
      val it : int = 14
      

      在传统语言中,函数只有一组参数。在 F# 中,您可以将此类具有多个参数的传统函数表达为具有元组参数的函数:

      let f (x, y) = x + y
      val f : x:int * y:int -> int
      // partial application is no longer possible, since the arguments have been packed into a single tuple
      

      这种传统的元组参数函数不允许使用命名参数调用:

      f (x=3, y=5) // interpreted as = test expressions
      

      F# 语言设计在这里是合理的,(x=3, y=5) 没有表达想要的元组。

      F# 已经命名参数 已经有一段时间了(这是在过去几年的某个时候添加的)。但是,这仅限于成员函数。一旦你把你的函数翻译成一个成员,它就可以工作了:

      type A() =
          static member f (x,y) = x + y
      
      A.f(y=3, x=4) // yes, this works!
      

      【讨论】:

        猜你喜欢
        • 2018-12-21
        • 2015-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-25
        • 2017-11-02
        相关资源
        最近更新 更多