【问题标题】:How to write clearer functional-style code?如何编写更清晰的函数式代码?
【发布时间】:2019-02-15 03:22:07
【问题描述】:

仍在使我的代码在样式和外观上越来越实用的过程中。

这里我有一个函数,我尽量保持通用,传递一个过滤函数和一个计算函数作为参数。

let calcError filter (fcalc:'a -> float) (arr:'a array) =
        arr |> Array.filter filter
            |> Array.map fcalc
            |> Array.average

签名是:

val calcError : filter:('a -> bool) -> fcalc:('a -> float) -> arr:'a array -> float

我相信这是相当标准的,在部分应用程序中使用 calcError。

但是 Array.average 会引发异常,如果数组大小为 0 或如果为 null(在我的情况下不会发生)。

我不太喜欢 F# 中的异常,我更喜欢使用(浮点输出)或结果。

然后我会考虑以这种方式编写代码,但我不确定这是在功能性思维方式(我正在尝试获取)内做的正确方式。当然欢迎任何其他解决方案,我可能能够适应其他类似问题。

谢谢大家

我想到的解决方案:

let calcError2 filter (fcalc:'a -> float) (arr:'a array) =
    let subarr = arr |> Array.filter filter
    match subarr.Length with
    | 0 -> Result.Error "array length 0"
    | _ -> subarr |> Array.map fcalc
                  |> Array.average
                  |> Result.Ok

【问题讨论】:

  • 这对我来说看起来不错。我可能会尝试将错误案例的消息更改为更详细的内容(例如“过滤后数组为空”),但我认为很多人不会对您提出的解决方案有疑问。
  • @BentTranberg 我喜欢 tryCatch 方法。我使用这本圣经研究了 ROP,但没有想到 tryCatch 函数。谢谢!

标签: functional-programming f#


【解决方案1】:

这是另一个带有辅助功能的版本。

let calcError filter (fcalc:'a -> float) (arr:'a array) =
    let safeAverage ar = if Array.isEmpty ar then None else Some(Array.average ar)
    arr |> Array.filter filter
            |> Array.map fcalc
            |> safeAverage

此外,您可以将数组转换为选项以将其与任何其他不安全的数组函数一起使用。

let nat arr = if Array.isEmpty arr then None else Some(arr)


let calcError filter (fcalc:'a -> float) (arr:'a array) =
        arr |> Array.filter filter
                |> Array.map fcalc
                |> nat
                |> Option.bind (Some << Array.average )

这是一个使用无点样式的更紧凑和高效的版本

let calcError filter (fcalc:'a -> float)   =
       Option.bind (Some << (Array.averageBy fcalc)) << nat << Array.filter filter  

我花了一段时间才真正体会到创建大量小功能的价值。希望能帮助到你。

【讨论】:

  • 我发现这是从功能上解决这个问题的一种优雅方式。
【解决方案2】:

这是一种方法:

let tryCalcError filter (fcalc:'a -> float) (arr:'a array) =
    arr |> Array.filter filter
        |> Array.map fcalc
        |> function
        | [||] -> None
        | arr  -> Array.average arr |> Some

它遵循前缀try 的约定,表示返回值是一个选项。您可以在多个 Seq.try... 函数中看到该约定,例如 tryFind、tryHead、tryLast、tryItem、tryPick。

【讨论】:

  • 其实我去创建一个额外的函数Array.tryAverage,感谢输入
  • 一个更好的解决方案。
【解决方案3】:

您的代码对我来说看起来不错。唯一不同的是,我不会使用 match 来测试数组是否为空 - 你没有绑定任何变量,你只有两种情况,所以你真的可以只使用 if 表达式这里。

另外两个小调整是我使用Array.isEmpty 来查看数组是否为空(这可能在这里没有效果,但如果你使用序列,它会比检查长度更快)而且我也使用averageBy 而不是map 后跟average:

let calcError2 filter (fcalc:'a -> float) (arr:'a array) =
    let subarr = arr |> Array.filter filter
    if Array.isEmpty subarr then Result.Error "array length 0" 
    else subarr |> Array.averageBy fcalc |> Result.Ok

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-07
    • 1970-01-01
    • 2019-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多