F# how to pass a function variable to a member function
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 ac开发者_StackOverflowcess, 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" intoMethodInfo
s for yourget_scoreXXX
andset_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 doscoreField (fun c -> c.XXX)
, where theXXX
member is of typeScorePercent
.
精彩评论