开发者

F# how to pass a function variable to a member function

开发者 https://www.devze.com 2023-01-14 05:46 出处:网络
Given a type (Candidate) which has muiltiple fields one may score (here one concrete example with _scoreXXX) and calculate the total percentage score:

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:

  1. 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.

  2. 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.

  3. 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.

0

精彩评论

暂无评论...
验证码 换一张
取 消