F# F中的战略模式#

F# F中的战略模式#,f#,functional-programming,strategy-pattern,F#,Functional Programming,Strategy Pattern,在C#中,我有以下代码: 公共类somekindawworker { 公共双重工作(战略战略战略) { int i=4; //一些代码。。。 var s=第1步(i)中的层; //更多的代码。。。 var d=第2步(s)段; //还有更多的代码。。。 返回d; } } 这是一段代码,可以通过使用提供的策略对象填充部分实现来完成某种工作。注:策略对象一般不包含状态;它们只是以多态方式提供各个步骤的实现 strategy类如下所示: 公共抽象类策略 { 公共抽象字符串Step1(inti); 公

在C#中,我有以下代码:

公共类somekindawworker
{
公共双重工作(战略战略战略)
{
int i=4;
//一些代码。。。
var s=第1步(i)中的层;
//更多的代码。。。
var d=第2步(s)段;
//还有更多的代码。。。
返回d;
}
}
这是一段代码,可以通过使用提供的策略对象填充部分实现来完成某种工作。注:策略对象一般不包含状态;它们只是以多态方式提供各个步骤的实现

strategy类如下所示:

公共抽象类策略
{
公共抽象字符串Step1(inti);
公共摘要双步骤2(字符串s);
}
公共课策略A:策略
{
公共重写字符串Step1(inti){返回“whatever”;}
公共重写双步骤2(字符串s){返回0.0;}
}
公共课策略B:策略
{
公共重写字符串Step1(inti){返回“其他内容”;}
公共重写双步骤2(字符串s){return 4.5;}
}
观察:在C#中,通过使用lambdas(并完全去掉strategy对象)可以实现相同的效果,但这种实现的好处是扩展类将它们的Step1和Step2实现放在一起

问题:在F#中,这一思想的惯用实现是什么

想法:

我可以在功函数中注入单独的阶跃函数,类似于观察中的想法

我还可以创建一个收集两个函数的类型,并通过以下方式传递该类型的值:

type Strategy = { Step1: int -> string; Step2: string -> double }
let strategyA = { Step1 = (fun i -> "whatever"); Step2 = fun s -> 0.0 }
let strategyB = { Step1 = (fun i -> "something else"); Step2 = fun s -> 4.5 }
这似乎与我试图实现的目标最接近:它将实现步骤紧密地放在一起,以便可以将它们作为一个整体进行检查。但是这个想法(创建一个只包含函数值的类型)在函数范式中是惯用的吗?还有其他想法吗?

您应该在这里使用:

您可以充分利用这两个方面:继承的灵活性和类似函数的轻量级语法

let strategyB(setupData) =
    let b = 3.0 + setupData

    { new IStrategy with
        member x.Step1 _ = "something else"
        member x.Step2 _ = 4.5 + b }
您使用函数记录的方法很好,但不是最惯用的方法。以下是(第9页)的建议:

在F#中,有许多方法可以表示操作字典,例如使用函数元组或函数记录。通常,我们建议您为此使用接口类型 目的

编辑:

使用带有的
进行记录更新非常好,但如果记录字段是函数,则intellisense无法正常工作。使用接口,您可以通过在对象表达式中传递参数来进一步自定义,例如

let createStrategy label f =
    { new IStrategy with 
        member x.Step1 _ = label
        member x.Step2 s =  f s }

或者,当您需要更多的可扩展性时,可以使用
接口IStrategy with
(这与C方法相同)。

您提到了在C中简单使用lambdas的可能性。对于步骤很少的策略,这通常是惯用的。这真的很方便:

let f step1 step2 = 
    let i = 4
    // ...
    let s = step1 i
    // ...
    let d = step2 s
    //  ...
    d
不需要接口定义或对象表达式;
step1
step2
的推断类型就足够了。在没有高阶函数的语言中(我相信这是发明策略模式的环境),您没有这个选项,而是需要接口

这里的函数
f
可能不关心
step1
step2
是否相关。但是,如果调用方这样做了,没有什么可以阻止他将它们捆绑到数据结构中。例如,使用@pad的答案

let x = f strategyA.Step1 strategyA.Step2
// val it = 0.0 

通常,“惯用方式”取决于您首先考虑策略模式的原因。战略模式是关于将功能拼接在一起;高阶函数通常也很适合这样做

对象表达式一次只支持一个接口。如果需要两个,请使用类型定义

type IStrategy =
    abstract Step1: int -> string
    abstract Step2: string -> double

type strategyA() =
    let mutable observers = []

    interface System.IObservable<string> with
        member observable.Subscribe(observer)  =
            observers <- observer :: observers
            { new System.IDisposable with
                 member this.Dispose() =
                    observers <- observers |> List.filter ((<>) observer)}

    interface IStrategy with
        member x.Step1 _ = 
            let result = "whatever"
            observers |> List.iter (fun observer -> observer.OnNext(result))
            result
        member x.Step2 _ = 0.0

type SomeKindaWorker() =
    member this.Work(strategy : #IStrategy) =
        let i = 4
        // some code ...
        let s = strategy.Step1(i)
        // some more code ...
        let d = strategy.Step2(s)
        // yet more code ...
        d

let strat = strategyA()
let subscription = printfn "Observed: %A" |> strat.Subscribe
SomeKindaWorker().Work(strat) |> printfn "Result: %A"
subscription.Dispose()
这允许您初始化策略

SomeKindaWorker().Work(strategyB(2.0)) |> printfn "%A"

这里有一个更实用的方法来解决这个问题:

type Strategy =
    | StrategyA
    | StrategyB

let step1 i = function
    | StrategyA -> "whatever"
    | StrategyB -> "something else"

let step2 s = function
    | StrategyA -> 0.0
    | StrategyB -> 4.5

let work strategy = 
    let i = 4
    let s = step1 i strategy
    let d = step2 s strategy
    d

这看起来很光滑。尽管有一条评论,strategyA和strategyB值背后的类型现在是匿名的,不能重复使用(即继承);而在记录方法中,您可以通过使用
语法替换其某些步骤,从现有策略中制定新策略。虽然我很喜欢
this
/
x
值在需要时如何使用。整洁,这就像Java中的匿名类,在我开始使用C#时我真的错过了它(不明白为什么它从未在那里实现;如此方便).我认为在这种情况下,我更愿意从
f
的角度保留
step1
step2
函数。我更可能将一对函数捆绑为一个元组,而不是单独提供每一个。反过来,我可能会将它们收集到一个记录类型(我最初的想法)或一个对象表达式(@pad的想法)。我认为这是最佳解决方案的地方,取决于
f
实际上在做什么。在许多情况下,尤其是当战略的组成部分是功能性的(没有副作用)时,它们实际上不必相互关联。但是,当然,这远远不是全部。这同时看起来非常有希望,也非常可怕。一方面,语法轻量级且易于理解;另一方面,任何给定策略的“全貌”都分布在不同功能的case语句中。你能对这一点发表评论吗?这是我必须习惯的不同功能分组吗?@CSJ,在我看来,这是FP与OOP的定义特征之一。我在这里更详细地讨论这个问题:这个答案(以及链接的帖子)非常具有描述性和帮助性。链接文章中突出的一点是“OOP鼓励数据和行为的捆绑,而函数式编程鼓励将它们分开”
type Strategy =
    | StrategyA
    | StrategyB

let step1 i = function
    | StrategyA -> "whatever"
    | StrategyB -> "something else"

let step2 s = function
    | StrategyA -> 0.0
    | StrategyB -> 4.5

let work strategy = 
    let i = 4
    let s = step1 i strategy
    let d = step2 s strategy
    d