我一直在观察一个有趣的video,其中Haskell中的类型类用于解决所谓的“表达式问题”。大约15分钟后,它显示了类型类如何用于“打开”基于区分联合扩展的数据类型 - 可以单独添加其他区分符,而无需修改/重建原始定义。
我知道F#中没有类型类,但有没有办法使用其他语言功能来实现这种可扩展性?如果没有,我们在F#中解决表达问题的距离有多近?
澄清:我假设问题的定义如previous video中所述 本系列中 - 数据类型的可扩展性和数据类型上的操作具有代码级模块化和单独编译的功能(扩展可以作为单独的模块部署,无需修改或重新编译原始代码)以及静态类型安全性。
答案 0 :(得分:3)
请参阅Vesa Karvonen对一个SML解决方案的评论here(尽管很麻烦),可以轻松转换为F#。
答案 1 :(得分:2)
正如Jörg在评论中指出的那样,这取决于 solve 的含义。如果你的意思是 solve 包括某种形式的类型检查,你在某些情况下没有错过某个函数的实现,那么F#不会给你任何优雅的方式(我不确定Haskell解决方案是否优雅)。您可以使用kvb提到的SML解决方案对其进行编码,也可以使用OO based solutions之一进行编码。
实际上,如果我正在开发一个需要解决问题的真实系统,我会选择一种不能完全检查的解决方案,但更容易使用。
草图将使用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)
这不会为您提供任何类型检查,但它为您提供了一个相当优雅的解决方案,该解决方案易于使用且不易实现(使用反射)。检查你没有错过一个案例不是在编译时完成的,但是你可以轻松地为它编写单元测试。
答案 2 :(得分:2)
我知道F#中没有类型类,但有没有办法使用其他语言功能来实现这种可扩展性?
我不相信,不。
如果没有,我们在F#中解决表达问题的距离有多近?
表达式问题是允许用户使用新函数和新类型扩充库代码,而无需重新编译库。在F#中,联合类型可以轻松添加新函数(但不可能将新的联合案例添加到现有的联合类型),类类型可以轻松派生新的类类型(但不可能将新方法添加到现有的类层次结构中) 。这些是实践中所需的两种可扩展性形式。在不牺牲静态类型安全的情况下同时向两个方向延伸的能力只是学术上的好奇心,IME。
顺便说一句,提供这种可扩展性的最优雅的方法是牺牲类型安全性并使用所谓的“基于规则的编程”。 Mathematica这样做。例如,计算表达式的符号导数的函数是整数文字,变量或加法,可以在Mathematica中编写,如下所示:
D[_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等语言编写的任何解决方案都要优雅,但当然,它不是类型安全的。