I am looking for an idiomatic approach to programming filters in F#. For clarity, I refer to a filter as a function that uses a series of measurements over time and produces evolving estimates. This implies that the function be able to maintain state. For example, in Python one could use coroutines to maintain state in a very clean way.
What I'm looking for is an idiomatic approach to programming filters in F#. Given that my mind is thoroughly polluted with OOP and procedural principles, naturally I came up with classes to express them. Is there a more idiomatic approach to filtering in F#, one that could perhaps open up other benefits of the functional paradigm?
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
In your case, the terms "functional" and "F# idiomatic" would consist of two things: immutable data and separation of data from code.
Immutable data: you would have one data structure representing the filter parameters (i.e. A, H, Q, and R), and another structure representing the filter's current state (i.e. X, K, and P). Both immutable. Instead of mutating the state, you would produce a new one.
Separation of data from code: the filter itself would consist of a single function that takes parameters, current state, next observation value, and produces next state. This next state will then be fed back into the function along with the next observation value, thus producing next+1 state, and so on. The parameters always stay constant, so they can be passed in just once, using partial application (see below).
Once you have such function, you can "apply" it to the list of observations as a "rolling projection", - as described above, - taking each observation and feeding it into the function along with the last state, producing the next state. This "rolling projection" operation is a very common thing in functional programming, and is usually called scan. F# does provide implementations of scan for all standard collections - list, seq, etc.
As a result of scan, you will have a list of filter's successive states. Now all that's left to do is to fish the X value out of 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 = 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
A note on design
Note the initState function declared in the module. This function may seem silly on the surface, but it has important meaning: it lets me specify state fields by name without opening the module, thus avoiding namespace pollution. Plus, the consuming code now looks more readable: it says what it does, no comments required.
Another common approach to this is to declare a "base" state in the module, which consuming code could then amend via the with syntax:
module ScalarKalman =
...
let zeroState = { K = 0.; X = 0.; P = 0. }
...
let initialState = { ScalarKalman.zeroState with X = 6.; P = 4. }
A note on collections
F# lists are fine on small amounts of data and small processing pipelines, but become expensive as these two dimensions grow. If you're working with a lot of streaming data, and/or if you're applying multiple filters in succession, you might be better off using lazy sequences - seq. To do so, simply replace List.scan and List.map with Seq.scan and Seq.map respectively. If you do, you will get a lazy sequence as the ultimate result, which you will then need to somehow consume - either convert it to a list, print it out, send it to the next component, or whatever your larger context implies.
Related
I've just started learning F# very recently. I have a function which counts the coefficients of the linear equation: y = ax + b, based on coordinates of two points P1(x1, y1), P2(x1, y2). The function looks like this:
module LinearFit
let generate(x1 : double, y1 : double, x2 : double, y2 : double) =
let w = x1 * 1.0 - x2 * 1.0
let wa = y1 * 1.0 - y2 * 1.0
let wb = x1 * y2 - x2 * y1
printfn "w: %g" w
printfn "wa: %g" wa
printfn "wb: %g" wb
let a = wa/w
let b = wb/w
printfn "a: %g" a
printfn "b: %g" b
printfn "%g %g" a b
(a, b)
I'm trying to somehow return founded coefficients as a tuple result and then assign the result to the new variables so later I can use the result to do some other operations. The trivial thing, for now, would be just displayed a result like:
The generated function is y = 2.5x - 6.5
So far I was trying to do sth like this
open System
let main() =
printf "Linear fit"
(a: double, b: double) <- LinearFit.generate(5.0, 6.0, 7.0, 11.0)
printfn "The generated functi..."
main()
Console.ReadKey() |> ignore
This is only a concept as I'm not even able to compile the project as im getting errors:
"Unexpected symbol ',' in expression"
"Unexpected symbol ')' in binding."
I tried to find some similar approach to C#...
For now what I want to achieve is just to assing the result of generate function to some variables. In C# it would look just like
public (double a, double b) Generate(some params here)
{
// some logic here
return (a, b);
}
(var a, var b) = Generate(...);
Any ideas?
You're making several syntactic mistakes.
First, the arrow-left operator <- is destructive update. It takes a mutable variable on the right and an expression on the left, and pushes the value of the expression into the variable. For example:
let mutable x = 5
x <- 42
In your example, neither a nor b are mutable variables that exist by the time you're trying to use the <- operator. Plus, the operator expects a single mutable variable, not a pattern.
Second, the way to declare new variables in F# is with let. It is roughly equivalent to var in C#, except you can declare multiple variables at once by putting them in a pattern. For example:
let x = 42
let pair = (1, 5)
let a, b = pair
Here, on the last line, I'm declaring two variables a and b by destructuring the pair.
In your example, you're trying to introduce the two new variables a and b without a let keyword. This is not allowed.
So, putting all of the above together, this is the right way to do what you're trying to do:
let main() =
printf "Linear fit"
let a, b = LinearFit.generate(5.0, 6.0, 7.0, 11.0)
printfn "The generated functi..."
P.S. Your question betrays a misunderstanding of some pretty basic principles of F# syntax. Because of this, I would recommend that you read through tutorials, examples, and other articles on F# to familiarize yourself with the syntax before attempting to venture farther.
My coworker and I were comparing the speed of C# functions when passing in lambdas to do work compared to inlined functions with regards to time spent doing work. We found that you incurred a cost when passing in a lambda projection to a C# select function (for example) and wanted to see if F# had the same issues, or if it did something different.
Regardless of our original intent, we stumbled onto something that we can't figure out. In the following example we sum a list 3 different ways
Reduce
Sum
SumBy
module fs
open NUnit.Framework
open FsUnit
open System
open System.Diagnostics;
[<Test>]
let sumTest() =
let nums = [0..1000]
let repeat = 100000
let stopWatch = new Stopwatch()
stopWatch.Start()
let sumsReduce =
[
for i in [0..repeat] do
yield List.reduce (+) nums
]
Console.WriteLine("reduce = {0} - Time = {1}", List.head sumsReduce, stopWatch.Elapsed.TotalSeconds);
stopWatch.Restart()
let sumsSum =
[
for i in [0..repeat] do
yield List.sum nums
]
Console.WriteLine("sum = {0} - Time = {1}", List.head sumsSum, stopWatch.Elapsed.TotalSeconds);
stopWatch.Restart()
let sumsSumBy =
[
for i in [0..repeat] do
yield List.sumBy id nums
]
Console.WriteLine("sumBy = {0} - Time = {1}", List.head sumsSumBy, stopWatch.Elapsed.TotalSeconds);
stopWatch.Restart()
The output to this looks like:
reduce = 500500 - Time = 0.2725156
sum = 500500 - Time = 1.1183165
sumBy = 500500 - Time = 1.1126781
So clearly reduce is the big winner here. In the decompilation, I can see that reduce gets boiled down
[Serializable]
internal class sumsReduce\u004021\u002D1 : OptimizedClosures.FSharpFunc<int, int, int>
{
internal sumsReduce\u004021\u002D1()
{
base.\u002Ector();
}
public override int Invoke(int x, int y)
{
return x + y;
}
}
But I'm having a hard time figuring out what sum and sumBy are doing. Where is the timing discrepancy from?
The current answer suggested that reduce is 5 times faster because originally I was giving reduce an unchecked operator. However, updating the test to use a checked operator (from the Checked module) and I still get the same result
let sumsReduce =
[
for i in [0..repeat] do
yield List.reduce (Checked.(+)) nums
]
Notice the time discrepancy still exists
reduce = 500500 - Time = 0.274697
sum = 500500 - Time = 1.1126796
sumBy = 500500 - Time = 1.1370642
Sum and SumBy use an enumerator:
while e.MoveNext() do
acc <- Checked.(+) acc e.Current
acc
whereas reduce uses an recursive loop with an optimised closure: (reduce uses fold under the cover's - fold f head tail)
let fold<'T,'State> f (s:'State) (list: 'T list) =
match list with
| [] -> s
| _ ->
let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(f)
let rec loop s xs =
match xs with
| [] -> s
| h::t -> loop (f.Invoke(s,h)) t
loop s list
Using optimised closures can often yield a performance boost.
sum and sumBy use checked arithmetic, but you're passing unchecked operator + to reduce – not exactly apples to apples.
IE,
What am I doing wrong here? Does it have to to with lists, sequences and arrays and the way the limitations work?
So here is the setup: I'm trying to generate some primes. I see that there are a billion text files of a billion primes. The question isn't why...the question is how are the guys using python calculating all of the primes below 1,000,000 in milliseconds on this post...and what am I doing wrong with the following F# code?
let sieve_primes2 top_number =
let numbers = [ for i in 2 .. top_number do yield i ]
let sieve (n:int list) =
match n with
| [x] -> x,[]
| hd :: tl -> hd, List.choose(fun x -> if x%hd = 0 then None else Some(x)) tl
| _ -> failwith "Pernicious list error."
let rec sieve_prime (p:int list) (n:int list) =
match (sieve n) with
| i,[] -> i::p
| i,n' -> sieve_prime (i::p) n'
sieve_prime [1;0] numbers
With the timer on in FSI, I get 4.33 seconds worth of CPU for 100000... after that, it all just blows up.
Your sieve function is slow because you tried to filter out composite numbers up to top_number. With Sieve of Eratosthenes, you only need to do so until sqrt(top_number) and remaining numbers are inherently prime. Suppose we havetop_number = 1,000,000, your function does 78498 rounds of filtering (the number of primes until 1,000,000) while the original sieve only does so 168 times (the number of primes until 1,000).
You can avoid generating even numbers except 2 which cannot be prime from the beginning. Moreover, sieve and sieve_prime can be merged into a recursive function. And you could use lightweight List.filter instead of List.choose.
Incorporating above suggestions:
let sieve_primes top_number =
let numbers = [ yield 2
for i in 3..2..top_number -> i ]
let rec sieve ns =
match ns with
| [] -> []
| x::xs when x*x > top_number -> ns
| x::xs -> x::sieve (List.filter(fun y -> y%x <> 0) xs)
sieve numbers
In my machine, the updated version is very fast and it completes within 0.6s for top_number = 1,000,000.
Based on my code here: stackoverflow.com/a/8371684/124259
Gets the first 1 million primes in 22 milliseconds in fsi - a significant part is probably compiling the code at this point.
#time "on"
let limit = 1000000
//returns an array of all the primes up to limit
let table =
let table = Array.create limit true //use bools in the table to save on memory
let tlimit = int (sqrt (float limit)) //max test no for table, ints should be fine
let mutable curfactor = 1;
while curfactor < tlimit-2 do
curfactor <- curfactor+2
if table.[curfactor] then //simple optimisation
let mutable v = curfactor*2
while v < limit do
table.[v] <- false
v <- v + curfactor
let out = Array.create (100000) 0 //this needs to be greater than pi(limit)
let mutable idx = 1
out.[0]<-2
let mutable curx=1
while curx < limit-2 do
curx <- curx + 2
if table.[curx] then
out.[idx]<-curx
idx <- idx+1
out
There have been several good answers both as to general trial division algorithm using lists (#pad) and in choice of an array for a sieving data structure using the Sieve of Eratosthenes (SoE) (#John Palmer and #Jon Harrop). However, #pad's list algorithm isn't particularly fast and will "blow up" for larger sieving ranges and #John Palmer's array solution is somewhat more complex, uses more memory than necessary, and uses external mutable state so is not different than if the program were written in an imperative language such as C#.
EDIT_ADD: I've edited the below code (old code with line comments) modifying the sequence expression to avoid some function calls so as to reflect more of an "iterator style" and while it saved 20% of the speed it still doesn't come close to that of a true C# iterator which is about the same speed as the "roll your own enumerator" final F# code. I've modified the timing information below accordingly. END_EDIT
The following true SoE program only uses 64 KBytes of memory to sieve primes up to a million (due to only considering odd numbers and using the packed bit BitArray) and still is almost as fast as #John Palmer's program at about 40 milliseconds to sieve to one million on a i7 2700K (3.5 GHz), with only a few lines of code:
open System.Collections
let primesSoE top_number=
let BFLMT = int((top_number-3u)/2u) in let buf = BitArray(BFLMT+1,true)
let SQRTLMT = (int(sqrt (double top_number))-3)/2
let rec cullp i p = if i <= BFLMT then (buf.[i] <- false; cullp (i+p) p)
for i = 0 to SQRTLMT do if buf.[i] then let p = i+i+3 in cullp (p*(i+1)+i) p
seq { for i = -1 to BFLMT do if i<0 then yield 2u
elif buf.[i] then yield uint32(3+i+i) }
// seq { yield 2u; yield! seq { 0..BFLMT } |> Seq.filter (fun i->buf.[i])
// |> Seq.map (fun i->uint32 (i+i+3)) }
primesSOE 1000000u |> Seq.length;;
Almost all of the elapsed time is spent in the last two lines enumerating the found primes due to the inefficiency of the sequence run time library as well as the cost of enumerating itself at about 28 clock cycles per function call and return with about 16 function calls per iteration. This could be reduced to only a few function calls per iteration by rolling our own iterator, but the code is not as concise; note that in the following code there is no mutable state exposed other than the contents of the sieving array and the reference variable necessary for the iterator implementation using object expressions:
open System
open System.Collections
open System.Collections.Generic
let primesSoE top_number=
let BFLMT = int((top_number-3u)/2u) in let buf = BitArray(BFLMT+1,true)
let SQRTLMT = (int(sqrt (double top_number))-3)/2
let rec cullp i p = if i <= BFLMT then (buf.[i] <- false; cullp (i+p) p)
for i = 0 to SQRTLMT do if buf.[i] then let p = i+i+3 in cullp (p*(i+1)+i) p
let nmrtr() =
let i = ref -2
let rec nxti() = i:=!i+1;if !i<=BFLMT && not buf.[!i] then nxti() else !i<=BFLMT
let inline curr() = if !i<0 then (if !i= -1 then 2u else failwith "Enumeration not started!!!")
else let v = uint32 !i in v+v+3u
{ new IEnumerator<_> with
member this.Current = curr()
interface IEnumerator with
member this.Current = box (curr())
member this.MoveNext() = if !i< -1 then i:=!i+1;true else nxti()
member this.Reset() = failwith "IEnumerator.Reset() not implemented!!!"
interface IDisposable with
member this.Dispose() = () }
{ new IEnumerable<_> with
member this.GetEnumerator() = nmrtr()
interface IEnumerable with
member this.GetEnumerator() = nmrtr() :> IEnumerator }
primesSOE 1000000u |> Seq.length;;
The above code takes about 8.5 milliseconds to sieve the primes to a million on the same machine due to greatly reducing the number of function calls per iteration to about three from about 16. This is about the same speed as C# code written in the same style. It's too bad that F#'s iterator style as I used in the first example doesn't automatically generate the IEnumerable boiler plate code as C# iterators do, but I guess that is the intention of sequences - just that they are so damned inefficient as to speed performance due to being implemented as sequence computation expressions.
Now, less than half of the time is expended in enumerating the prime results for a much better use of CPU time.
What am I doing wrong here?
You've implemented a different algorithm that goes through each possible value and uses % to determine if it needs to be removed. What you're supposed to be doing is stepping through with a fixed increment removing multiples. That would be asymptotically.
You cannot step through lists efficiently because they don't support random access so use arrays.
I am playing with a toy problem (Convex hull identification) and needed lexicographic sorting twice already. One of the cases was given a list of type Point = { X: float; Y: float }, I would like to sort by X coordinate, and in case of equality, by Y coordinate.
I ended up writing the following:
let rec lexiCompare comparers a b =
match comparers with
[ ] -> 0
| head :: tail ->
if not (head a b = 0) then head a b else
lexiCompare tail a b
let xComparer p1 p2 =
if p1.X > p2.X then 1 else
if p1.X < p2.X then -1 else
0
let yComparer p1 p2 =
if p1.Y > p2.Y then 1 else
if p1.Y < p2.Y then -1 else
0
let coordCompare =
lexiCompare [ yComparer; xComparer ]
Which allows me to do
let lowest (points: Point list) =
List.sortWith coordCompare points
|> List.head
So far, so good. However, this feels a bit heavy-handed. I have to create specific comparers returning -1, 0 or 1, and so far I can't see a straightforward way to use this in cases like List.minBy. Ideally, I would like to do something along the lines of providing a list of functions that can be compared (like [(fun p -> p.X); (fun p -> p.Y)]) and do something like lexicographic min of a list of items supporting that list of functions.
Is there a way to achieve this in F#? Or am I thinking about this incorrectly?
Is there a way to achieve this in F#? Or am I thinking about this incorrectly?
F# does this for you automatically when you define a record type like yours:
> type Point = { X: float; Y: float };;
type Point =
{X: float;
Y: float;}
You can immediately start comparing values. For example, defining a 3-element list of points and sorting it into lexicographic order using the built-in List.sort:
> [ { X = 2.0; Y = 3.0 }
{ X = 2.0; Y = 2.0 }
{ X = 1.0; Y = 3.0 } ]
|> List.sort;;
val it : Point list = [{X = 1.0;
Y = 3.0;}; {X = 2.0;
Y = 2.0;}; {X = 2.0;
Y = 3.0;}]
Note that the results were sorted first by X and then by Y.
You can compare two values of any comparable type using the built-in compare function.
If you want to use a custom ordering then you have two options. If you want to do all of your operations using your custom total order then it belongs in the type definition as an implementation of IComparable and friends. If you want to use a custom ordering for a few operations then you can use higher-order functions like List.sortBy and List.sortWith. For example, List.sortBy (fun p -> p.Y, p.X) will sort by Y and then X because F# generates the lexicographic comparison over 2-tuples for you (!).
This is one of the big advantages of F#.
Well, to start with, you can rely on F#'s built-in compare function:
let xComparer p1 p2 = compare p1.X p2.X
let yComparer p1 p2 = compare p1.Y p2.Y
Alternatively, you can clearly abstract this a bit if desired:
let compareWith f a b = compare (f a) (f b)
let xComparer = compareWith (fun p -> p.X)
let yComparer = compareWith (fun p -> p.Y)
Or, as you note, you could build this approach directly into the list handling function:
let rec lexiCompareWith l a b =
match l with
| [] -> 0
| f::fs ->
match compare (f a) (f b) with
| 0 -> lexiCompareWith fs a b
| n -> n
One important limitation here is that since you're putting them into a list, the functions must all have identical return types. This isn't a problem in your Point example (since both functions have type Point -> float), but it would prevent you from sorting two Person objects by name and then age (since the first projection would have type Person -> string but the second would have type Person -> int).
I don't think I understand your question correctly, but doesn't the following code work fine?
let lowest (points : Point list) = List.sort points |> List.head
It seems that F# performs implicit comparison on record data types. And my little experiment indicates that the comparison happens to be lexicographic. But I could not find any evidence to support that result.
So I'm not yet sure F# compares records lexicographically. I can still write in the following manner using tuple instead:
let lowest (points : Point list) =
let tuple = List.map (fun pt -> (pt.X, pt.Y)) points |> List.sort |> List.head
{ X = fst tuple; Y = snd tuple }
I hope this post could help.
Given a type (Candidate) which has muiltiple fields one may score (here one concrete example with _scoreXXX) and calculate the total percentage score:
type ScorableCandidate() =
let mutable _scoreXXX: int = 0 ;
let mutable _percentXXX: float = 0. ;
member this.scoreXXX
with get() = _scoreXXX
and set(v) =
_scoreXXX<- v
propertyChanged.Trigger(this, new PropertyChangedEventArgs("scoreXXX"))
member this.percentXXX
with get() = _percentXXX
and set(v) =
_percentXXX <- v
propertyChanged.Trigger(this, new PropertyChangedEventArgs("percentXXX"))
I would like to absctract the calculation of the percentages so I don't need to redefine the function everytime. Unfortunately I am lost on how to do this with member functions. The template of the code I am abstracting looks is:
let scoreXXXs () =
let totalXXXScore = List.fold (fun acc (candidate: ScorableCandidate) -> acc + candidate.scoreXXXs) 0 brokers
let score (candidate: ScorableCandidate) =
candidate.percentXXXs <- (float candidate.scoreXXXs) / float totalXXXScore * 100.
if totalXXXScore > 0 then
List.iter score <| brokers
I guess I would like to be able to define the function as "score" and pass in the aapropriate member accessors. Such that i could write
score XXX // where xxx is some property in the class that needs to be scored
score YYY // where yyy is some property in the class that needs to be scored
One thought I had was passing in functions to get access, and that is easy enough to do for the getter but I can't seem to figure out the setter. I realize I could defer to reflection for this - but I feel that might not be the best (f#) way... AT this point I am at:
let scoreField (accessF : (ScorableCandidate -> int)) (setF : (float -> unit)) =
let totalScore = List.fold (fun acc (candidate: ScorableCandidate) -> acc + accessF candidate) 0 brokers
let score (candidate: ScorableCandidate) =
setF <| (accessF candidate |> float) / float totalScore * 100.
if totalScore > 0 then
List.iter score <| brokers
scoreField (fun c -> c.scoreXXX) (fun f -> ())
But I don't know how (or if it is possible) to represent the setter as a lambda function on the type (where maybe I can pass the instace as paraneter to lambda function and invoke it somehow).
Thoughts? Thanks ahead.
Update Found this approach (thoughts):
http://laurent.le-brun.eu/site/index.php/2009/10/17/52-dynamic-lookup-operator-aka-duck-typing-in-fsharp
You're on the right track, but your settor is missing the object you're going to set the field on. So the type of setF should be:
setF : (ScorableCandidate -> float -> unit)
so then you'd use it like:
let score (candidate: ScorableCandidate) =
(setF candidate) <| (accessF candidate |> float) / float totalScore * 100.
and call your scoreField thus:
scoreField (fun c -> c.scoreXXX) (fun c f -> c.percentXXX <- f)
So, if I understand it correctly, you need to store several scores (for various different things) in a single candidate and then perform calculations over these scores.
In that case, I would consider making Score a separate type that would be used by the candidate - you can then easily add multiple scores. If you need to expose score & percent as direct properties of candidate & notify using IPropertyChange, then you should be able to write something like this:
/// Takes a name of this score item (e.g. XXX) and a
/// function to trigger the property changed event
type Score(name:string, triggerChanged) =
let mutable score = 0
let mutable percent = 0.0
member x.Score
with get() = score
and set(v) =
score <- v
triggerChanged("Score" + name)
member x.Percent
with get() = percent
and set(v) =
percent <- v
triggerChanged("Percent" + name)
Now you can simply use the Score object as many times you need in the candidate (because we also want to expose direct properties, there is some boilerplate, but it should be reasonable amount):
type ScorableCandidate() as this =
// Create trigger function & pass it to Score objects
let trigger n = propertyChanged.Trigger(this, n)
let infoXxx = new Score("Xxx", trigger)
member this.InfoXxx = scoreXxx // Exposes the whole Score object
member this.ScoreXxx = scoreXxx.Score // & individual...
member this.PercentXxx = scoreXxx.Percent // ...properties
Now, your parameterized function can simply take a function that takes ScorableCandidate and returns a Score object:
let scores f =
let totalScore = List.fold (fun acc (candidate: ScorableCandidate) ->
let so = f candidate // Get 'Score' object
acc + so.Score) 0 brokers
let score (candidate: ScorableCandidate) =
let so = f candidate // Get 'Score' object
so.Percent <- (float so.Score) / float totalScore * 100.0
if totalScore > 0 then
List.iter score <| brokers
Example call would be simply:
scores (fun (c:ScorableCandidate) -> c.InfoXxx)
This makes the call to the scores function as easy as it can get and it is also scalable solution that makes it easy to add other calculations to the Score object. The disadvantage (e.g. when compared with Pavel's straightforward solution) is that there is a bit more work with designing the Score type (however, declaring new scores in ScorableCandidate is then arguably easier if you only need to expose readable property directly in the class - which I think should be enough).
To make the API simpler, you might want to consider one of the following options:
Use reflection. Then you could do scoreField "XXX", and your method could explicitly translate "XXX" into MethodInfos for your get_scoreXXX and set_percentXXX methods. This has the disadvantage that it doesn't check the method names at compile time and it has the performance penalty that comes along with reflection.
Use quotations. Then you could do scoreField <# fun c -> c.scoreXXX, c.percentXXX #>. This would otherwise work similarly to the reflection example, except that you'd have a bit more compile-time checking.
Create a type which represents score/percent combinations, and create properties of that type rather than separate properties for each. Your ScorePercent class could have getters and setters for the score and percent (and raise its own change notifications). Then, you could do scoreField (fun c -> c.XXX), where the XXX member is of type ScorePercent.