如何编写更清晰的功能样式代码?

时间:2019-02-15 03:22:08

标签: functional-programming f#

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

在这里,我有一个函数,希望尽可能地保持通用,将过滤器函数和计算函数作为参数传递。

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#中异常的忠实拥护者,我宁愿使用(float输出)还是Result。

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

谢谢

我想到的解决方案:

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

3 个答案:

答案 0 :(得分:3)

这是一种实现方法:

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...tryFindtryHeadtryLasttryItem等多个tryPick函数中看到该约定。

答案 1 :(得分:3)

这是另一个具有帮助功能的版本。

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 :(得分:1)

您的代码对我来说看起来不错。我要做的唯一不同的事情是,我不会使用match来测试数组是否为空-您没有绑定任何变量,并且只有两种情况,因此您真的可以使用{{ 1}}表达式。

另外两个小调整是我正在使用if来查看数组是否为空(这在这里可能没有效果,但是如果您使用序列,那将比检查长度更快),并且我还使用Array.isEmpty而不是averageBy,然后使用map

average