【问题标题】:F# how to pass a function variable to a member functionF#如何将函数变量传递给成员函数
【发布时间】:2010-08-30 18:42:45
【问题描述】:

给定具有多个字段的类型(候选人),可以得分(这里是一个带有 _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 函数(也许我可以将实例作为参数传递给 lambda 函数并以某种方式调用它)。

想法?先谢谢了。

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

【问题讨论】:

    标签: f#


    【解决方案1】:

    您在正确的轨道上,但您的设置器缺少您要设置字段的对象。所以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)
    

    【讨论】:

    • 选择这个解决方案 - 因为我只是在开发一个原型,而且这很有效。我认为它们都是有效的方法,正如 Tomas 所提到的,重新设计对象层可能是一个更好的计划,但我受到现有 RPC 层的限制,这最终会阻止我这样做。谢谢大家
    【解决方案2】:

    因此,如果我理解正确,您需要在一个候选人中存储多个分数(针对各种不同的事物),然后对这些分数进行计算。

    在这种情况下,我会考虑让Score 成为候选人使用的单独类型 - 然后您可以轻松添加多个分数。如果您需要将分数和百分比公开为候选人的直接属性并使用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 类型需要做更多的工作(但是,如果您只需要直接在类 - 我认为应该足够了)。

    【讨论】:

      【解决方案3】:

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

      1. 使用反射。然后你可以做scoreField "XXX",你的方法可以为你的get_scoreXXX和set_percentXXX方法显式地将“XXX”翻译成MethodInfos。这样做的缺点是它不会在编译时检查方法名称,并且会伴随反射带来性能损失。

      2. 使用引号。然后你可以做scoreField &lt;@ fun c -&gt; c.scoreXXX, c.percentXXX @&gt;。否则,这将与反射示例类似,只是您需要进行更多的编译时检查。

      3. 创建一个表示分数/百分比组合的类型,并创建该类型的属性,而不是为每个单独的属性。你的ScorePercent 类可以有得分和百分比的获取器和设置器(并提出自己的更改通知)。然后,您可以使用scoreField (fun c -&gt; c.XXX),其中XXX 成员的类型为ScorePercent。

      【讨论】:

      • 也许我不是很清楚,我花了一段时间才决定如何表达这个问题,如果我有误导性,我很抱歉。我希望能够对此进行参数化,以便 scoreXXX 只是选项之一 - 那里的逻辑(用于评分)可以在类的多个属性上传播,而无需专门为其编写代码。 scoreXXX or scoreYYY or scoreZZZ or scoreXYZ etc are all different memebers I may want to run this on...
      • @akaphenom - 我已经编辑了我的答案给你一些其他的选择。
      • 谢谢。与 Pavel Minaev 提供的解决方案相比,使用 Quotations 的优势是什么?
      • @akaphenom - 只是打字少了一点。
      • @akaphenom:虽然写得有点短,但它让我知道如何重写我自己的答案:)
      猜你喜欢
      • 2019-09-27
      • 2021-11-29
      • 1970-01-01
      • 2021-06-24
      • 2017-05-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多