f# - 使用接口(interface)扩展对象的设计替代方案

标签 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

所以说一个 Sin(x) 可以表示为:
let sin = { new IFunction() with member x.Name = "SIN" }
let sinExpr = FunctionApplication(sin,Var("x"))

到目前为止一切都很好,但我想实现的下一个想法是拥有额外的接口(interface)来表示属性的功能。例如。
type IDifferentiable =
     member Derivative : int -> IFunction // Get the derivative w.r.t a variable index 

我在这里试图实现的想法之一是我实现了一些功能和它们的所有逻辑,然后继续我想要实现的逻辑的下一部分。但是,就目前而言,这意味着对于我添加的每个接口(interface),我都必须重新访问我已实现的所有 IFunction。相反,我宁愿有一个功能:
let makeDifferentiable (f : IFunction) (deriv : int -> IFunction) =
    { f with
        interface IDifferentiable with
            member x.Derivative = deriv }

但正如 this question 中所讨论的那样, 这是不可能的。可能的替代方案不符合我的可扩展性要求。我的问题是什么替代品会很好用?

[编辑] 我被要求扩展“不符合我的可扩展性要求”评论。此功能的工作方式是执行以下操作:
let makeDifferentiable (deriv : int -> IFunction)  (f : IFunction)=
    { new IFunction with
          member x.Name = f.Name
      interface IDifferentiable with
          member x.Derivative = deriv }

但是,理想情况下,我会在添加对象时继续向对象添加其他接口(interface)。因此,如果我现在想添加一个接口(interface)来判断 on 函数是否为偶数:
type IsEven =
    abstract member IsEven : bool with get

那么我希望能够(但没有义务,例如,如果我不进行此更改,一切仍应编译)更改我对正弦的定义
let sin = { new IFunction with ... } >> (makeDifferentiable ...) 


let sin = { new IFunction with ... } >> (makeDifferentiable ...) >> (makeEven false)

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

我现在唯一能想到的就是为我想实现的每个功能创建一个字典,以函数名称作为键,并在运行时构建接口(interface)的详细信息,例如沿线:
let derivative (f : IFunction) =
    match derivativeDictionary.TryGetValue(f.Name) with
    | false, _ -> None
    | true, d  -> d.Derivative

这将要求我为每个特性创建一个这样的函数,除了每个特性一个字典之外,我还添加了一个这样的函数。特别是如果使用代理异步实现,这可能不会那么慢,但仍然感觉有点笨拙。

最佳答案

我认为您在这里尝试解决的问题是所谓的 The Expression Problem .您实际上是在尝试编写可在两个方向上扩展的代码。有区别的联合和面向对象的模型给你一个或另一个:

  • 歧视工会使添加新操作变得容易(只需编写一个带有模式匹配的函数),但很难添加一种新的表达式(您必须扩展 DU 并修改所有代码
    使用它)。
  • 接口(interface) 使添加新类型的表达式变得容易(只需实现接口(interface)),但很难添加新操作(您必须修改接口(interface)并更改创建它的所有代码。

  • 总的来说,我认为尝试提出让您同时兼顾的解决方案并没有多大用处(它们最终会变得非常复杂),因此我的建议是选择您更经常需要的解决方案。

    回到您的问题,我可能会将函数表示为函数名称和参数:
    type Expr =
      | Num of decimal
      | Var of string
      | Application of string * Expr list
    

    真的 - 一个表达就是这样。您可以使用导数这一事实是您要解决的问题的另一部分。现在,为了使导数可扩展,您只需保留一个导数字典:
     let derrivatives = 
       dict [ "sin", (fun [arg] -> Application("cos", [arg])) 
              ... ] 
    

    这样,您就有了 Expr真正模拟表达式的类型,您可以编写微分函数来查找字典中的导数。

    关于f# - 使用接口(interface)扩展对象的设计替代方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24645007/

    相关文章:

    f# - 在 F# 中处理模式

    generics - F# generics 泛型构造要求类型 'struct (Guid * int)' 具有公共(public)默认构造函数

    concurrency - MailboxProcessor 类型是锁的替代品吗?

    scala - 当参数是对象类型时,如何在 F# 中要求多个接口(interface)?

    .net - F# 删除函数处理程序

    .net - 为什么需要在 F# 异步工作流中使用 "do! Async"样式函数?

    F# SAFE 如何处理消息 DU 变体的增长?

    F# 比较两个列表,采取不同的行动

    f# - 在 F# 中,表达式的类型应为 'Nullable<DateTime>',但此处的类型为 'DateTime'

    f# - 编译器无法判断哪个具有重复字段的记录类型应该是函数参数类型