在再次使用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"))
到目前为止一切都很好,但我想要实现的下一个想法是使用额外的接口来表示属性的功能。 E.g。
type IDifferentiable =
member Derivative : int -> IFunction // Get the derivative w.r.t a variable index
我在这里尝试实现的一个想法是,我实现了一些函数和它们的所有逻辑,然后转到我想要实现的逻辑的下一部分。但是,就目前情况而言,这意味着我添加的每个接口都需要重新审视我实现的所有IF功能。相反,我宁愿有一个功能:
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 }
但是,理想情况下,我会在添加对象时继续向对象添加其他接口。所以,如果我现在想要添加一个界面来判断函数是否是偶数:
type IsEven =
abstract member IsEven : bool with get
然后我希望能够(但没有义务,如果我不做这个改变,一切都应该编译)来改变我对正弦的定义
let sin = { new IFunction with ... } >> (makeDifferentiable ...)
到
let sin = { new IFunction with ... } >> (makeDifferentiable ...) >> (makeEven false)
其结果是我可以创建一个实现IFunction接口的对象以及潜在的,但不一定是很多不同的其他接口;然后我将在它们上定义的操作可能能够根据某个函数是否实现接口来优化它们正在做的事情。这也将允许我首先添加其他功能/接口/操作,而不必更改我定义的功能(虽然他们不会利用其他功能,但事情也不会被破坏。[/ EDIT] < / p>
我现在唯一能想到的就是为我想要实现的每个功能创建一个字典,功能名称作为键,以及动态构建接口的细节,例如:沿着这条线:
let derivative (f : IFunction) =
match derivativeDictionary.TryGetValue(f.Name) with
| false, _ -> None
| true, d -> d.Derivative
这需要我为每个功能创建一个这样的功能,除了每个功能一个字典外,我还添加了这个功能。特别是如果与代理异步实现,这可能不会那么慢,但它仍然感觉有点笨拙。
答案 0 :(得分:5)
我认为您在此尝试解决的问题是所谓的The Expression Problem。您实际上是在尝试编写可在两个方向上扩展的代码。歧视的工会和面向对象的模型为您提供了一个或另一个:
歧视联盟可以轻松添加新操作(只需编写一个带模式匹配的函数),但很难添加一种新的表达式(你必须扩展DU)并修改所有代码
使用它)。
接口可以轻松添加新类型的表达式(只需实现接口),但很难添加新操作(您必须修改接口并更改所有代码创造它。
总的来说,我不认为尝试提出可以让你做到这两点的解决方案(它们最终变得非常复杂)是非常有用的,所以我的建议就是选择你那个&# 39;需要更多时间。
回到你的问题,我可能只是将函数与参数一起表示为函数名称:
type Expr =
| Num of decimal
| Var of string
| Application of string * Expr list
真的 - 表达式就是就是这样。您可以采用衍生工具这一事实是您正在解决的问题的另一部分。现在,为了使衍生产品具有可扩展性,您可以保留衍生词典:
let derrivatives =
dict [ "sin", (fun [arg] -> Application("cos", [arg]))
... ]
这样,你有一个Expr
类型,它真正模拟表达式是,你可以编写区分函数来查找字典中的派生词。