【问题标题】:Naming the arguments of functions that are themselves arguments in F#在 F# 中命名本身就是参数的函数的参数
【发布时间】:2021-01-19 06:05:02
【问题描述】:

给函数参数命名是记录我们代码的一种方式;如果没有这样的需要,那么我们可以给它们命名,例如“arg1”和“arg2”,这样也不会更糟——但实际上这当然会使我们的代码更难理解。在 F# 中,我们经常将函数作为参数传递给其他函数,在这种情况下,它们的参数不再命名:

let foo<'T> (bar : 'T -> 'T -> float -> 'T) =
    // do something

这个函数 foo 接受一个“bar”作为参数,它本身接受两个 'T 和一个浮点数并返回一个 'T,但很难说它应该做什么。如果我们可以将“bar”的参数命名为:

let foo<'T> (bar : (startVal : 'T) -> (endVal : 'T) -> (interpolationAmt : float) -> 'T) =
    // do something

现在“bar”的用途很明显了:它在两个 'T 类型的值之间进行插值。此外,'T 参数的顺序以及应用它们的顺序也很重要。但不幸的是,这段代码无法编译。

所以我的问题是,是否有一种我错过的方法可以做到这一点,如果没有,人们通常如何处理这个问题?

【问题讨论】:

    标签: f# naming code-documentation


    【解决方案1】:

    很遗憾,无法将参数名称添加到函数签名中。使函数签名易于理解的最好方法是自定义类型。但我建议只为本身具有一定意义的真实域类型创建自定义类型。通常,此类类型有一些限制 - 非空列表、最大长度为 42 的字符串、负整数等。因此,对于您的函数,我会为插值量创建一个类型:

    type InterpolationAmount = private InterpolationAmount of float
    
    module InterpolationAmount =
        let create (amount: float) : InterpolationAmount =
            if amount < 0. then failwith "blah-blah"
            else InterpolationAmount amount
    

    至少现在清楚float参数是什么,并且可以控制插值量值。

    现在关于插值开始和结束。为确保用户将正确的参数传递给此函数,您可以将其设为具有单个参数的函数。您当然会失去部分应用选项,但所有参数都有名称,在这里很难搞砸:

    type InterpolationArgs<'T> = {
        startValue: 'T
        endValue: 'T
        amount: InterpolationAmount
    }
    

    当然,使用bar以外的名字也会给用户一个提示

    let foo<'T> (interpolate : InterpolationArgs<'T> -> 'T) =
         interpolate {
            startValue = a
            endValue = b
            amount = InterpolationAmount.create 1.2
         }
    

    【讨论】:

      【解决方案2】:

      如果 bar 进行插值,则可以将其称为 interpolate

      您还可以使用简单的类型别名作为标记来帮助交流。但是,从foo 来看,并不清楚StartValue&lt;'T&gt; 只是一个'T

      type StartValue<'T> = 'T
      type EndValue<'T> = 'T
      type InterpolationAmount<'T> = 'T
      
      let foo<'T> (interpolate : StartValue<'T> -> EndValue<'T> -> InterpolationAmount<float> -> 'T) =
          ()
      

      使用更面向对象的风格的另一种方法是使用接口。

      type IInterpolate<'T> =
          abstract member Interpolate : startVal:'T -> endVal:'T -> interpolationAmount:float -> 'T
      
      let foo<'T> (interpolate : IInterpolate<'T>) =
          ()
      

      编辑:另一种选择是使用匿名记录类型:

      let foo<'T> (interpolate : {| StartVal:'T; EndVal:'T; InterpolationAmount:float |} -> 'T) =
          ()
      

      【讨论】:

      • 感谢您的回答,但我不能接受它,因为这两种选择都不能使代码更易于阅读:对于第一个,在概念上将“StartValue”和“ EndValue”作为类型,这使得函数签名很难一目了然。面向对象的替代方案在我看来并不像惯用的 F#,并且使函数的使用更加复杂。我很难想象这两种方法中的任何一种都是 F# 中一种既定的做事方式?
      • @Bent 我刚刚添加了第三个使用匿名记录的选项。我认为没有一种官方惯用的方法。这些都用于此类问题,并且它们有不同的权衡。 Sergey 的回答也有一些不错的选择。
      猜你喜欢
      • 2015-10-27
      • 2013-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-06
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      相关资源
      最近更新 更多