我正在寻找一种在F#中编程过滤器的惯用方法。为清楚起见,我将过滤器称为一种功能,它随着时间的推移使用一系列测量值并产生不断变化的估计值。这意味着该功能能够维持状态。例如,在Python中,可以使用协同程序以非常干净的方式维护状态。
我正在寻找的是用F#编程过滤器的惯用方法。鉴于我的思想受到OOP和程序原则的彻底污染,我自然而然地想出了表达它们的课程。在F#中有一种更惯用的过滤方法,这可能会开启功能范式的其他好处吗?
open System
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.Random
open MathNet.Numerics.Distributions
open MathNet.Numerics.Statistics
open FSharp.Charting
type ScalarKalman (A : float, H : float, Q : float, R : float) = class
let mutable A = A
let mutable H = H
let mutable Q = Q
let mutable R = R
let mutable p = 0.
let mutable x = 0.
let mutable k = 0.
let mutable result = 0.
member this.X
with get() = x
and set(value) = x <- value
member this.P
with get() = p
and set(value) = p <- value
member this.K
with get() = k
and set(value) = k <- value
member this.update(newVal : float) =
let xp = A * this.X
let Pp = A * this.P * A + Q
this.K <- Pp * H / (H * Pp * H + R)
this.X <- xp + this.K * (newVal - H * xp)
this.P <- Pp - this.K * H * Pp
end
let n = 100
let obsv = [|for i in 0 .. n do yield 0.|]
let smv = [|for i in 0 .. n do yield 0.|]
let kal = new ScalarKalman(1., 1., 0., 5.)
kal.P <- 4.
kal.X <- 6.
for i in 0 .. n do
obsv.[i] <- Normal.Sample(10., 5.)
kal.update(obsv.[i])
smv.[i] <- kal.X
Chart.Combine([obsv |> Chart.FastLine
smv |> Chart.FastLine]) |> Chart.Show
答案 0 :(得分:8)
在您的情况下,术语&#34; functional&#34;和&#34; F#惯用&#34;将由两部分组成:不可变数据和代码中的数据分离。
不可变数据:您将拥有一个表示过滤器参数的数据结构(即A
,H
,Q
和R
) ,以及表示过滤器当前状态的另一个结构(即X
,K
和P
)。两者都是不变的。而不是改变状态,你会产生一个新的状态。
从代码中分离数据:过滤器本身将由一个函数组成,该函数接受参数,当前状态,下一个观察值,并产生下一个状态。然后,下一个状态将与下一个观察值一起反馈到函数中,从而产生下一个+ 1状态,依此类推。参数始终保持不变,因此可以使用部分应用程序将它们传递一次(见下文)。
一旦你有了这样的功能,你可以&#34;申请&#34;它作为&#34;滚动投影&#34;的观察列表, - 如上所述, - 将每个观察结果与最后一个状态一起送入函数,产生下一个状态。这个&#34;滚动投影&#34;在函数式编程中,操作是很常见的,通常称为scan
。 F#确实为所有标准集合提供scan
的实现 - list
,seq
等。
作为scan
的结果,您将获得过滤器的连续状态列表。现在剩下要做的就是从每个州中取出X
值。
以下是完整的解决方案:
module ScalarKalman =
type Parameters = { A : float; H : float; Q : float; R : float }
type State = { K: float; X: float; P: float }
let initState (s: State) = s
let getX s = s.X
let update parms state newVal =
let xp = parms.A * state.X
let Pp = parms.A * state.P * parms.A + parms.Q
let newK = Pp * parms.H / (parms.H * Pp * parms.H + parms.R)
{ K = newK
X = xp + newK * (newVal - parms.H * xp)
P = Pp - newK * parms.H * Pp }
let n = 100
let obsv = [for i in 0 .. n -> Normal.Sample(10., 5.)]
let kal = ScalarKalman.update { A = 1.; H = 1.; Q = 0.; R = 5. }
let initialState = ScalarKalman.initState { X = 6.; P = 4.; K = 0. }
let smv =
obsv
|> List.scan kal initialState
|> List.map ScalarKalman.getX
关于设计的说明
请注意模块中声明的initState
函数。这个函数表面看起来很愚蠢,但它有重要意义:它允许我按名称指定状态字段,而不用open
模块,从而避免命名空间污染。此外,消费代码现在看起来更具可读性:它说它做了什么,不需要评论。
另一个常见的方法是声明一个&#34; base&#34;模块中的状态,消耗代码然后可以通过with
语法修改:
module ScalarKalman =
...
let zeroState = { K = 0.; X = 0.; P = 0. }
...
let initialState = { ScalarKalman.zeroState with X = 6.; P = 4. }
关于馆藏的说明
F#列表适用于少量数据和小型处理流水线,但随着这两个维度的增长而变得昂贵。如果您正在处理大量流数据,并且/或者如果您连续应用多个过滤器,那么最好使用延迟序列 - seq
。为此,只需将List.scan
和List.map
分别替换为Seq.scan
和Seq.map
。如果你这样做,你会得到一个懒惰的序列作为最终结果,然后你需要以某种方式消费 - 要么将其转换为列表,将其打印出来,发送到下一个组件,或者更大的上下文所暗示的。 / p>