obj
作为类型的表示,并使用反射来定位为单个情况提供实现的函数。我可能会标记所有部分,以使检查更容易。添加表达式应用程序的模块可能看起来像这样:[<Extends("Expr")>] // Specifies that this type should be treated as a case of 'Expr'
type App = App of obj * obj
module AppModule =
[<Implements("format")>] // Specifies that this extends function 'format'
let format (App(e1, e2)) =
// We don't make recursive calls directly, but instead use `invoke` function
// and some representation of the function named `formatFunc`. Alternatively
// you could support 'e1?format' using dynamic invoke.
sprintfn "(%s %s)" (invoke formatFunc e1) (invoke formatFunc e2)
请查看Vesa Karvonen在此处的评论,其中提供了一个SML解决方案(虽然有点繁琐),但可以轻松地转换为F#。
Pow
到示例中? - J Dnum
类型添加一个新案例并在numEval
和numToString
函数中处理它(其他所有内容都不变),要么直接向t
类型添加一个新案例并在eval
和toString
函数中处理它(同样保持其他所有内容不变)。 - kvbtype 'e lam = ABS of string * 'e | APP of 'e * 'e | VAR of string
是行得通的。Vesa 的方法允许将 lam
、num
和 cons
类型分别编译,然后用少量样板代码将它们绑定到类型 t
上。在我看来,这似乎很明显地解决了表达式问题。 - kvblamEval
函数的代码eval ((s, x)::e) b
处遇到了类型错误。这段SML代码是否可以编译通过?表达式问题是关于如何扩展现有类型A以创建一个新类型B,使您可以直接使用类型B的值重用A上的现有函数。Garrigue的OCaml使用多态变体来实现这一点,例如var
是'a lambda
的子类型。Karvonen的SML没有做到这一点,在我看来。每次添加新的类型构造器时,都必须包装每个函数,即编写渐近更多的代码。 - J Deval1
具有类型(string * ('a lambda as 'a)) list -> 'a -> 'a
。这是一个递归类型(又称为rectype)。OCaml支持这些类型。据我所知,Standard ML和F#不支持。我从F#得到的类型错误告诉我它正在尝试创建一个递归类型(将'a
与'a lam
统一)。 - J DD[_Integer, _] := 0
D[x_Symbol, x_] := 1
D[_Symbol, _] := 0
D[f_ + g_, x_] := D[f, x] + D[g, x]
D[f_ g_, x_] := f D[g, x] + g D[f, x]
我们可以添加一个新的函数来评估这样的表达式:
E[n_Integer] := n
E[f_ + g_] = E[f] + E[g]
对我来说,这比使用OCaml、Haskell和Scala等语言编写的任何解决方案都更加优雅,但是它当然不是类型安全的。