【问题标题】:F# limitations of discriminated unions可区分联合的 F# 限制
【发布时间】:2013-11-12 13:11:54
【问题描述】:

我正在尝试将小型编译器从 C# 移植到 F#,以利用模式匹配和可区分联合等功能。目前,我正在使用基于 System.Linq.Expressions 的模式对 AST 进行建模:一个抽象基“表达式”类、每个表达式类型的派生类和一个 NodeType 枚举,允许在不进行大量转换的情况下切换表达式。我曾希望使用 F# 可区分联合大大减少这种情况,但我遇到了几个看似限制:

  • 强制公共默认构造函数(我想对表达式构造进行类型检查和参数验证,就像 System.Linq.Expressions 对其静态工厂方法所做的那样)
  • 缺少命名属性(似乎这在 F# 3.1 中已修复)
  • 无法直接引用案例类型。例如,似乎我不能声明一个仅从联合中接受一种类型的函数(例如,let f (x : TYPE) = x 编译为 Expression(联合类型)但不适用于 AddExpression.Add。这似乎以牺牲我的 C# 方法的一些类型安全性。

对于这些或设计模式是否有很好的解决方法可以让它们不那么令人沮丧?

【问题讨论】:

  • 带你去一个不同的方向,你看过parser combinators,尤其是FParsec吗?
  • "好像我不能声明一个只从联合中接受一种类型的函数" 是的,因为 case 不是类型。您可以在鉴别器/大小写标记上“仅”进行模式匹配。

标签: f# discriminated-union


【解决方案1】:

我认为,您对 DU 是一个类层次结构的想法有点过于执着了。真的,将其视为数据更有帮助。因此:

  • 强制公共默认构造函数(我想对表达式构造进行类型检查和参数验证,如 System.Linq.Expressions 使用它的静态工厂方法)

DU 只是数据,很像字符串或数字,而不是功能。为什么不创建一个返回Expression option 的函数来表示您的数据可能无效。

  • 缺少命名属性(似乎这在 F# 3.1 中已修复)

如果你觉得你需要命名属性,你可能有一个不合适的类型,比如string * string * string * int * float 作为Expression 的数据。最好做一个记录,比如AddInfo,并让你的DU案例使用它,比如| Add of AddInfo。这样您就可以在模式匹配、智能感知等方面拥有属性。

  • 无法直接引用案例类型。例如,似乎我不能声明一个只接受一种类型的函数 union (例如 let f (x : TYPE) = x 编译为 Expression (联合 type) 但不适用于 Add 或 Expression.Add。这似乎牺牲了一些 类型安全优于我的 C# 方法。

你不能要求 Add 的情况,但你绝对可以写一个函数,它接受 AddInfo。另外,您始终可以以单子方式执行此操作,并且具有接受任何Expression 并且仅返回option 的函数。在这种情况下,您可以进行模式匹配,即您的输入是适当的类型,如果不是,则返回 None。然后在调用站点,您可以使用 Option.bind 之类的函数“使用”好的情况下的值。

基本上尽量不要将 DU 视为一组类,而实际上只是数据的案例。有点像枚举。

【讨论】:

  • “仅数据”方法令人沮丧,因为这意味着无论我在哪里使用这些类型,我都必须假设它们处于有效状态。使用更详细的方法,我可以保证这一点。我喜欢使用记录类型进行属性命名的想法,但这似乎也消除了使用 DU 的大部分简洁性。我不喜欢让我的所有函数都采用 Expression 并在其中匹配的选项,因为即使调用者知道它具有正确的类型,这也会对调用者(解包选项)和实现(匹配)施加额外的冗长。
  • 那么,您要确保哪些类型的保证?可能至少其中一些可以作为 DU 自己编码到类型系统中。这样你甚至可能不需要检查,而是让它们由编译器强制执行。当涉及到记录时,您也可以只匹配某些属性而不关心其他属性。这使得模式匹配非常简洁。拥有一个只接受一个案例的具体数据的函数有什么问题?
  • @DanielFabian 我会更进一步,使用Choice 而不是Option——因此验证输入并创建案例实例的函数可以返回错误结果( Choice2Of2) 帮助查明函数失败的位置和原因。
【解决方案2】:

您可以将实现设为私有。这允许您在实现中充分发挥 DU 的功能,但向 API 的使用者提供有限的视图。有关记录的相关问题,请参阅this answer(尽管它也适用于 DU)。

编辑

我在 MSDN 上找不到语法,但这里是:

type T =
  private
  | A
  | B

private 这里的意思是“模块私有”。

【讨论】:

  • 您介意展示使用私有成员和公共构造函数创建 DU 的语法吗?
  • @ChaseMedallion:我用语法更新了我的答案。
猜你喜欢
  • 2011-11-19
  • 1970-01-01
  • 1970-01-01
  • 2017-12-27
  • 2017-05-12
  • 1970-01-01
  • 2013-08-04
  • 1970-01-01
  • 2014-09-13
相关资源
最近更新 更多