惯用的 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
如果没有注释,mean
和 stdDev
都不会在这里编译,因为值限制而不会在模块中被调用。即使那样,他们也仅限于他们遇到的第一种实现 System.Collections.Generic.IEnumerable<'T>
.
的类型
另一方面,variance
的定义不适合减少 eta,它不会遇到这些问题。 Composition 用于组合两个函数:从 partially applied 均值中减去,并将一个值乘以自身。
作为一个简单挑战的一部分,我将从头开始编写几个简单的统计函数,并且我正在尝试以最 "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
如果没有注释,mean
和 stdDev
都不会在这里编译,因为值限制而不会在模块中被调用。即使那样,他们也仅限于他们遇到的第一种实现 System.Collections.Generic.IEnumerable<'T>
.
另一方面,variance
的定义不适合减少 eta,它不会遇到这些问题。 Composition 用于组合两个函数:从 partially applied 均值中减去,并将一个值乘以自身。