设计用接口扩展对象的替代方案

Bra*_*ram 4 f#

在再次使用Expert F#时,我决定实现用于操作代数表达式的应用程序.这很顺利,现在我决定通过构建更高级的应用程序来扩展它.

我的第一个想法是有一个设置,允许更加可扩展的方式创建函数,而无需重新编译.为此,我有类似的东西:

type IFunction =
    member x.Name : string with get
    /// additional members omitted

type Expr =
    | Num of decimal
    | Var of string
    ///... omitting some types here that don't matter
    | FunctionApplication of IFunction * Expr list
Run Code Online (Sandbox Code Playgroud)

所以说Sin(x)可以表示为:

let sin = { new IFunction() with member x.Name = "SIN" }
let sinExpr = FunctionApplication(sin,Var("x"))
Run Code Online (Sandbox Code Playgroud)

到目前为止一切都很好,但我想要实现的下一个想法是有额外的接口来表示属性的功能.例如

type IDifferentiable =
     member Derivative : int -> IFunction // Get the derivative w.r.t a variable index 
Run Code Online (Sandbox Code Playgroud)

我试图在这里实现的一个想法是,我实现了一些函数和它们的所有逻辑,然后转到我想要实现的逻辑的下一部分.但是,就目前而言,这意味着我添加的每个接口都必须重新审视我实现的所有IFunction.相反,我宁愿有一个功能:

let makeDifferentiable (f : IFunction) (deriv : int -> IFunction) =
    { f with
        interface IDifferentiable with
            member x.Derivative = deriv }
Run Code Online (Sandbox Code Playgroud)

但正如这个问题所讨论的那样,这是不可能的.可能的替代方案不符合我的可扩展性要求.我的问题是哪些替代品能够很好地运作?

[编辑]我被要求扩展"不符合我的可扩展性要求"的评论.这个函数的工作方式是做类似的事情:

let makeDifferentiable (deriv : int -> IFunction)  (f : IFunction)=
    { new IFunction with
          member x.Name = f.Name
      interface IDifferentiable with
          member x.Derivative = deriv }
Run Code Online (Sandbox Code Playgroud)

但是,理想情况下,我会在添加对象时继续向对象添加其他接口.所以如果我现在想要添加一个接口来判断函数是否是偶数:

type IsEven =
    abstract member IsEven : bool with get
Run Code Online (Sandbox Code Playgroud)

然后我希望能够(但没有义务,如果我不做这个改变,一切都应该仍然编译)来改变我对正弦的定义

let sin = { new IFunction with ... } >> (makeDifferentiable ...) 
Run Code Online (Sandbox Code Playgroud)

let sin = { new IFunction with ... } >> (makeDifferentiable ...) >> (makeEven false)
Run Code Online (Sandbox Code Playgroud)

其结果是我可以创建一个实现IFunction接口的对象以及潜在的,但不一定是很多不同的其他接口; 然后我将在它们上定义的操作可能能够根据某个函数是否实现接口来优化它们正在做的事情.这也允许我首先添加其他功能/接口/操作,而不必更改我定义的功能(虽然他们不会利用其他功能,但事情也不会被破坏.[/ EDIT]

我现在唯一能想到的就是为我想要实现的每个功能创建一个字典,功能名称作为键,以及动态构建接口的细节,例如:

let derivative (f : IFunction) =
    match derivativeDictionary.TryGetValue(f.Name) with
    | false, _ -> None
    | true, d  -> d.Derivative
Run Code Online (Sandbox Code Playgroud)

这需要我为每个功能创建一个这样的功能,除了每个功能一个字典外,我还添加了这个功能.特别是如果与代理异步实现,这可能不是那么慢,但它仍然感觉有点笨重.

Tom*_*cek 5

我认为你在这里要解决的问题是所谓的表达问题.您实际上是在尝试编写可在两个方向上扩展的代码.受歧视的工会和面向对象的模型为您提供了一个或另一个:

  • 区分联合可以轻松添加新操作(只需编写具有模式匹配的函数),但很难添加新类型的表达式(您必须扩展DU并修改
    使用它的所有代码).

  • 接口可以很容易地添加新类型的表达式(只是实现接口),但很难添加新的操作(您必须修改接口并更改创建它的所有代码.

总的来说,我认为尝试提供可以同时执行这两项工作的解决方案(它们最终会非常复杂)并不是很有用,所以我的建议是选择一个您需要更频繁的解决方案.

回到你的问题,我可能将函数表示为函数名和参数:

type Expr =
  | Num of decimal
  | Var of string
  | Application of string * Expr list
Run Code Online (Sandbox Code Playgroud)

真的 - 表达就是这样.您可以采用衍生工具这一事实是您正在解决的问题的另一部分.现在,为了使衍生产品具有可扩展性,您可以保留衍生词典:

 let derrivatives = 
   dict [ "sin", (fun [arg] -> Application("cos", [arg])) 
          ... ] 
Run Code Online (Sandbox Code Playgroud)

这样一来,你有一个Expr类型,真正的模型正是一种表达,你可以写分化功能,将寻找在字典中的衍生物.