F#如何将函数变量传递给成员函数

时间:2010-08-30 18:42:45

标签: f#

给定一个具有多个字段的类型(候选者)可以得分(这里是一个具体的例子,用_scoreXXX)并计算总百分比得分:

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"))

我想抽取百分比的计算,所以我不需要每次都重新定义函数。不幸的是,我对如何使用成员函数做错了。我抽象的代码模板是:

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

我想我希望能够将该功能定义为“得分”并传入适当的成员访问者。这样我就可以写了

    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

我有一个想法是传递函数来获取访问权限,这对于getter来说很容易,但我似乎无法弄清楚setter。我意识到我可以推迟对此进行反思 - 但我觉得这可能不是最好的(f#)方式......此时我处于:

    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 -> ())

但我不知道如何(或者是否可能)将setter表示为类型上的lambda函数(也许我可以将实例作为paraneter传递给lambda函数并以某种方式调用它。)

思考?谢谢。

更新发现这种方法(想法): http://laurent.le-brun.eu/site/index.php/2009/10/17/52-dynamic-lookup-operator-aka-duck-typing-in-fsharp

3 个答案:

答案 0 :(得分:2)

你在正确的轨道上,但是你的定位器缺少你要设置该字段的对象。所以setF的类型应为:

setF : (ScorableCandidate -> float -> unit)

所以你就像使用它一样:

let score (candidate: ScorableCandidate) =
    (setF candidate) <| (accessF candidate |> float) / float totalScore  * 100.

然后致电scoreField

scoreField (fun c -> c.scoreXXX) (fun c f -> c.percentXXX <- f)

答案 1 :(得分:2)

所以,如果我理解正确,你需要在一个候选人中存储几个分数(针对各种不同的东西),然后对这些分数进行计算。

在这种情况下,我会考虑将Score作为候选人使用的单独类型 - 然后您可以轻松添加多个分数。如果你需要公开得分&amp;作为候选人的直接属性的百分比&amp;使用IPropertyChange通知,然后您应该能够写出这样的内容:

/// 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)

现在你可以在候选者中多次使用Score对象(因为我们也希望公开直接属性,有一些样板,但它应该是合理的数量):

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

现在,您的参数化函数可以简单地使用一个带ScorableCandidate并返回Score对象的函数:

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    

示例调用只是:

scores (fun (c:ScorableCandidate) -> c.InfoXxx)

这使得对scores函数的调用尽可能简单,并且它也是可扩展的解决方案,可以轻松地将其他计算添加到Score对象。缺点(例如,与Pavel的直接解决方案相比)是设计Score类型还有一些工作(但是,如果您只需要公开,则在ScorableCandidate中声明新分数可能会更容易直接在类中的可读属性 - 我认为应该足够了。)

答案 2 :(得分:0)

为了简化API,您可能需要考虑以下选项之一:

  1. 使用反射。然后,您可以执行scoreField "XXX",并且您的方法可以为您的MethodInfoget_scoreXXX方法明确地将“XXX”翻译为set_percentXXX s。这样做的缺点是它不会在编译时检查方法名称,并且它会带来反射带来的性能损失。

  2. 使用引文。然后你可以做scoreField <@ fun c -> c.scoreXXX, c.percentXXX @>。否则,这将与反射示例类似,除了您需要更多的编译时检查。

  3. 创建一个表示分数/百分比组合的类型,并创建该类型的属性,而不是为每个类型创建单独的属性。您的ScorePercent课程可能有得分和百分比的getter和setter(并提出自己的更改通知)。然后,您可以执行scoreField (fun c -> c.XXX),其中XXX成员的类型为ScorePercent