难道是“表达问题”?可以用 F# 解决吗?
我一直在观看一个有趣的 视频,其中使用 Haskell 中的类型类来解决所谓的“表达式问题”。大约 15 分钟后,它展示了如何使用类型类来“打开”基于可区分联合的数据类型以进行扩展——可以单独添加额外的区分器,而无需修改/重建原始定义。
我知道类型类在 F# 中不可用,但是有没有办法使用其他语言功能来实现这种可扩展性?如果不是,我们距离解决 F# 中的表达式问题还有多远?
澄清:我假设问题的定义如 上一个视频 系列中——数据类型的可扩展性以及对数据类型的操作,具有代码级模块化和单独编译(扩展可以部署为单独的模块,无需修改或重新编译原始代码)以及静态类型安全的特点。
I've been watching an interesting video in which type classes in Haskell are used to solve the so-called "expression problem". About 15 minutes in, it shows how type classes can be used to "open up" a datatype based on a discriminated union for extension -- additional discriminators can be added separately without modifying / rebuilding the original definition.
I know type classes aren't available in F#, but is there a way using other language features to achieve this kind of extensibility? If not, how close can we come to solving the expression problem in F#?
Clarification: I'm assuming the problem is defined as described in the previous video
in the series -- extensibility of the datatype and operations on the datatype with the features of code-level modularization and separate compilation (extensions can be deployed as separate modules without needing to modify or recompile the original code) as well as static type safety.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
正如 Jörg 在评论中指出的那样,这取决于您所说的“解决”的含义。如果您的意思是解决,包括某种形式的类型检查,确保您在某些情况下不会缺少某些函数的实现,那么 F# 不会给您任何优雅方式(我不确定 Haskell 解决方案是否优雅)。您可以使用 kvb 提到的 SML 解决方案对其进行编码,或者使用 基于面向对象的解决方案。
实际上,如果我正在开发一个需要解决问题的现实系统,我会选择一个不提供全面检查但更易于使用的解决方案。
一个草图是使用 obj 作为类型的表示,并使用反射来定位为个别情况提供实现的函数。我可能会使用某些属性来标记所有部分,以便于检查。将应用程序添加到表达式的模块可能如下所示:
这不会为您提供任何类型检查,但它为您提供了一个相当优雅的解决方案,该解决方案易于使用且实现起来并不困难(使用反射)。检查是否缺少案例并不是在编译时完成的,但您可以轻松地为此编写单元测试。
As Jörg pointed out in a comment, it depends on what you mean by solve. If you mean solve including some form of type-checking that the you're not missing an implementation of some function for some case, then F# doesn't give you any elegant way (and I'm not sure if the Haskell solution is elegant). You may be able to encode it using the SML solution mentioned by kvb or maybe using one of the OO based solutions.
In reality, if I was developing a real-world system that needs to solve the problem, I would choose a solution that doesn't give you full checking, but is much easier to use.
A sketch would be to use
obj
as the representation of a type and use reflection to locate functions that provide implementation for individual cases. I would probably mark all parts using some attribute to make checking easier. A module adding application to an expression might look like this:This does not give you any type-checking, but it gives you a fairly elegant solution that is easy to use and not that difficult to implement (using reflection). Checking that you're not missing a case is not done at compile-time, but you can easily write unit tests for that.
请参阅 此处 Vesa Karvonen 的评论,了解一种 SML 解决方案(尽管很麻烦),可以轻松转换为 F#。
See Vesa Karvonen's comment here for one SML solution (albeit cumbersome), which can easily be translated to F#.
我不相信,不。
表达式问题是允许用户使用新函数和新类型来扩充库代码,而无需重新编译库。在 F# 中,联合类型可以轻松添加新函数(但不可能向现有联合类型添加新联合案例),而类类型可以轻松派生新类类型(但不可能向现有类层次结构添加新方法) 。这是实践中所需的两种形式的可扩展性。在不牺牲静态类型安全性的情况下同时向两个方向扩展的能力只是一种学术好奇心,IME。
顺便说一句,我所见过的提供这种可扩展性的最优雅的方法是牺牲类型安全性并使用所谓的“基于规则的编程”。 Mathematica 就是这么做的。例如,计算整数、变量或加法表达式的符号导数的函数可以在 Mathematica 中编写,如下所示:
我们可以像这样改进对乘法的支持:
并且我们可以添加一个新函数来计算表达式像这样:
对我来说,这比用 OCaml、Haskell 和 Scala 等语言编写的任何解决方案都要优雅得多,但当然,它不是类型安全的。
I do not believe so, no.
The expression problem is about allowing the user to augment your library code with both new functions and new types without having to recompile your library. In F#, union types make it easy to add new functions (but impossible to add new union cases to an existing union type) and class types make it easy to derive new class types (but impossible to add new methods to an existing class hierarchy). These are the two forms of extensibility required in practice. The ability to extend in both directions simultaneously without sacrificing static type safety is just an academic curiosity, IME.
Incidentally, the most elegant way to provide this kind of extensibility that I have seen is to sacrifice type safety and use so-called "rule-based programming". Mathematica does this. For example, a function to compute the symbolic derivative of an expression that is an integer literal, variable or addition may be written in Mathematica like this:
We can retrofit support for multiplication like this:
and we can add a new function to evaluate an expression like this:
To me, this is far more elegant than any of the solutions written in languages like OCaml, Haskell and Scala but, of course, it is not type safe.