In your case, the terms "functional" and "F # idiomatic" consist of two things: immutable data and separation of data from code.
Immutable data: you will have one data structure representing the filter parameters (i.e. A , H , Q and R ) and another structure representing the filter current (i.e. X , K and P ). Both are immutable. Instead of mutating a state, you should create a new one.
Separation of data from the code: the filter itself would consist of one function that takes parameters, the current state, the next observation value and produces the next state. Then this next state will be returned to the function along with the next observation value, thus creating the next state + 1, etc. Parameters always remain constant, so they can be transferred only once using a partial application (see below).
As soon as you have such a function, you can "apply" it to the list of observations as a "rolling", as described above, each observation and its supply to the function together with the last state, creating the next state. This rolling projection operation is very common in functional programming and is usually called scan . F # provides a scan implementation for all standard collections - list , seq , etc.
As a result of the scan , you will have a list of successive filter states. Now all that remains to be done is to catch the value of X from each state.
Here is the complete solution:
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 = sX 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
Design Note
Pay attention to the initState function declared in the module. This function may seem silly on the surface, but it is important: it allows me to specify state fields by name without an open module, which avoids pollution of the namespace. In addition, consumer code now looks more readable: it says what it does, no comments are required.
Another common approach to this is to declare a βbaseβ state in a module, which can then be changed using the with syntax:
module ScalarKalman = ... let zeroState = { K = 0.; X = 0.; P = 0. } ... let initialState = { ScalarKalman.zeroState with X = 6.; P = 4. }
Collection Note
F # lists are great for small data volumes and small process pipelines, but become expensive as these two dimensions grow. If you work with a lot of streaming data and / or if you apply several filters in a row, you might be better off using lazy sequences - seq . To do this, simply replace List.scan and List.map with Seq.scan and Seq.map respectively. If you do this, you will get a lazy sequence as the final result, which then you will need to somehow absorb - either convert to a list, print, send it to the next component, or regardless of your wider context.