惯用的 F# - 简单的统计函数

Idiomatic F# - Simple Statistical Functions

作为一个简单挑战的一部分,我将从头开始编写几个简单的统计函数,并且我正在尝试以最 "idiomatic F#" 的方式编写它们。我是函数式编程的新手,所以我想学习如何从头开始创建简单的东西。

这是我目前的情况:

let mean (x : float list) : float =
    (List.sum x) / (float (List.length x))

let variance (x : float list) : float =
    x
    |> List.map (fun a -> pown (a - (mean x)) 2)
    |> mean

let stdDev =
    variance >> Math.Sqrt

我喜欢 stdDev 函数是如何使用组合定义的,但我感觉可能有一些更漂亮、更惯用的方式来定义前两个函数。

有什么建议吗?

您的代码非常好而且符合地道。

就我个人而言,只要有可能,我都更喜欢衬垫。这样我就可以对齐代码以突出功能之间的异同。模式就是那样跳到你身上。

let mean     x = (Seq.sum x) / (float (Seq.length x))
let variance x = let m = mean x
                 x |> Seq.map (fun a -> pown (a - m) 2) |> mean
let stdDev   x = x |> variance |> Math.Sqrt

我也更喜欢 seq 而不是 list,因为它们可以与列表、数组、集合或任何其他序列一起使用。

do  [| 5. ; 6. ; 7. |] |> stdDev |> printfn "%A"
do  [  5. ; 6. ; 7.  ] |> stdDev |> printfn "%A"    
Set [  5. ; 6. ; 7.  ] |> stdDev |> printfn "%A"
seq [  5. ; 6. ; 7.  ] |> stdDev |> printfn "%A"        
seq {  5.   ..   7.  } |> stdDev |> printfn "%A"        

在 F# 中,最好避免使用 >> 组合运算符,而改用竖线 |>。 像这样组合函数有很多问题。例如,上面的代码是不可能的(使用不同的类型,如列表和数组)。

一个可能值得做的小改动是从 variance 函数中的 lambda 函数主体中提取 mean x 调用。 F# 编译器可能不会自动为您执行此操作,因此您最终将再次为列表中的每个元素重新计算平均值:

let variance (x : float list) : float =
    let mx = mean x
    x
    |> List.map (fun a -> pown (a - mx) 2)
    |> mean

正如 AMieres 在另一个回复中提到的,您还可以考虑使用与列表不同的类型。列表很好用,但 Seq 将使代码适用于任何集合。或者,如果您使用更大的数据进行计算,Array 可能会更快一些。

函数组合运算符并不像它的名声那么糟糕,只是需要稍微注意不要将 运行 变成 value restriction 或相关问题之一。同名错误 FS0030 说:

Either make the arguments to 'stdDev' explicit or, if you do not intend for it to be generic, add a type annotation.

我们还可以添加类型注释,使 let-bound 值比其他方式推断的值更通用。

let mean : seq<_> -> _ = 
    Seq.fold (fun (s, l) t -> s + t, l + 1) (0., 0) >> function
    | _, 0 -> failwith "empty collection"
    | s, l -> s / float l
let variance x = x |> Seq.map (x |> mean |> (-) >> fun a -> a * a) |> mean
let stdDev : seq<_> -> _ = variance >> sqrt
[5. ; 6. ; 7.] |> stdDev |> printfn "%A"    // prints 0.8164965809
{5.   ..   7.} |> stdDev |> printfn "%A"    // prints 0.8164965809

如果没有注释,meanstdDev 都不会在这里编译,因为值限制而不会在模块中被调用。即使那样,他们也仅限于他们遇到的第一种实现 System.Collections.Generic.IEnumerable<'T>.

的类型

另一方面,variance 的定义不适合减少 eta,它不会遇到这些问题。 Composition 用于组合两个函数:从 partially applied 均值中减去,并将一个值乘以自身。