仍在使我的代码在样式和外观上变得越来越实用的过程。
在这里,我有一个函数,希望尽可能地保持通用,将过滤器函数和计算函数作为参数传递。
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
答案 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...
,tryFind
,tryHead
,tryLast
,tryItem
等多个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