在 Purescript 中使用 ADT 的构造函数作为类型
Using an ADT's constrctor as a type in Purescript
这是来自 PureScript by Example
的 ADT 示例
data Shape
= Circle Point Number
| Rectangle Point Number Number
| Line Point Point
| Text Point String
type Point =
{ x :: Number
, y :: Number
}
想做这样的事情有意义吗?
boundingCircle :: Shape -> Circle
boundingBox :: Shape -> Rectangle
目前不允许这样做,因为 Circle 不是一种类型。我也许可以让 Shape 成为 class 类型,而 Circle、Rectangle 等都有 Shape 的实例。然后我失去了 Shape 恰好是 4 个构造函数的联合的语义(模式匹配变得更加混乱)。
将 ADT 构造函数本身视为类型真的值得避免吗?
是否将 ADP 构造函数视为类型值得避免是一个毫无意义的问题,因为那是不可能的。 ADT 构造函数不能是类型,到此为止。
但是如果你想让 Circle
和 Rectangle
作为它们自己的类型可用,但仍将 Shape
作为 ADT,那没有什么不可能的:
type Circle = { center :: Point, radius :: Number }
type Rectangle = { center :: Point, width :: Number, height :: Number }
data Shape
= Circle Circle
| Rectangle Rectangle
| Line Point Point
| Text Point String
boundingCircle :: Shape -> Circle
boundingBox :: Shape -> Rectangle
I could perhaps have Shape be a type class and Circle, Rectangle, ect
have instances of Shape. Then I lose the semantics that a Shape is the
union of exactly 4 constructors (pattern matching becomes messier).
您实际上可以同时拥有 4 种不同的类型和简单的模式匹配。
首先是原始示例中构造函数对应的各个类型:
data Circle = Circle Point Number
data Rectangle = Rectangle Point Number Number
data Line = Line Point Point
data Text = Text Point String
然后 Shape
类型可以是 4 个单独类型的简单“联合”(或“和”):
data Shape = CircleShape Circle | RectangleShape Rectangle | LineShape Line | TextShape Text
此设置将允许您做您似乎想做的两件事。您提到的函数的形式大致如下:
boundingCircle :: Shape -> Circle
boundingCircle (CircleShape (Circle center radius)) = Circle (...) (...)
boundingCircle (CircleRectangle (Rectangle corner length width)) = Circle (...) (...)
其余情况依此类推,这也表明您可以在 Shape
上进行模式匹配并知道它是四个特定变体之一。
虽然这不是“免费获胜”。一切都有利有弊,决定如何设计数据类型并不总是那么简单。特别是,通过上面的内容,您会注意到 Shape
上的模式匹配虽然仍然可行,但已变得更加冗长。通常,您实际上并不希望将 Circle
之类的东西作为它们自己的类型。或者您可能对类型类方法感到满意 - 虽然这会阻止您进行模式匹配,但您仍然可以使用 boundingCircle
等作为该类型类的方法,或者作为可以轻松从类型类方法派生的其他函数.
事实上,我建议我在这里最喜欢的方法可能正是类型类方法——如果您正在设计类型类,您可以完全输入您需要的方法,仅此而已。但在这些情况下从来没有任何一个“正确答案”——您需要权衡利弊,然后做最适合您的事情。
这是来自 PureScript by Example
的 ADT 示例data Shape
= Circle Point Number
| Rectangle Point Number Number
| Line Point Point
| Text Point String
type Point =
{ x :: Number
, y :: Number
}
想做这样的事情有意义吗?
boundingCircle :: Shape -> Circle
boundingBox :: Shape -> Rectangle
目前不允许这样做,因为 Circle 不是一种类型。我也许可以让 Shape 成为 class 类型,而 Circle、Rectangle 等都有 Shape 的实例。然后我失去了 Shape 恰好是 4 个构造函数的联合的语义(模式匹配变得更加混乱)。
将 ADT 构造函数本身视为类型真的值得避免吗?
是否将 ADP 构造函数视为类型值得避免是一个毫无意义的问题,因为那是不可能的。 ADT 构造函数不能是类型,到此为止。
但是如果你想让 Circle
和 Rectangle
作为它们自己的类型可用,但仍将 Shape
作为 ADT,那没有什么不可能的:
type Circle = { center :: Point, radius :: Number }
type Rectangle = { center :: Point, width :: Number, height :: Number }
data Shape
= Circle Circle
| Rectangle Rectangle
| Line Point Point
| Text Point String
boundingCircle :: Shape -> Circle
boundingBox :: Shape -> Rectangle
I could perhaps have Shape be a type class and Circle, Rectangle, ect have instances of Shape. Then I lose the semantics that a Shape is the union of exactly 4 constructors (pattern matching becomes messier).
您实际上可以同时拥有 4 种不同的类型和简单的模式匹配。
首先是原始示例中构造函数对应的各个类型:
data Circle = Circle Point Number
data Rectangle = Rectangle Point Number Number
data Line = Line Point Point
data Text = Text Point String
然后 Shape
类型可以是 4 个单独类型的简单“联合”(或“和”):
data Shape = CircleShape Circle | RectangleShape Rectangle | LineShape Line | TextShape Text
此设置将允许您做您似乎想做的两件事。您提到的函数的形式大致如下:
boundingCircle :: Shape -> Circle
boundingCircle (CircleShape (Circle center radius)) = Circle (...) (...)
boundingCircle (CircleRectangle (Rectangle corner length width)) = Circle (...) (...)
其余情况依此类推,这也表明您可以在 Shape
上进行模式匹配并知道它是四个特定变体之一。
虽然这不是“免费获胜”。一切都有利有弊,决定如何设计数据类型并不总是那么简单。特别是,通过上面的内容,您会注意到 Shape
上的模式匹配虽然仍然可行,但已变得更加冗长。通常,您实际上并不希望将 Circle
之类的东西作为它们自己的类型。或者您可能对类型类方法感到满意 - 虽然这会阻止您进行模式匹配,但您仍然可以使用 boundingCircle
等作为该类型类的方法,或者作为可以轻松从类型类方法派生的其他函数.
事实上,我建议我在这里最喜欢的方法可能正是类型类方法——如果您正在设计类型类,您可以完全输入您需要的方法,仅此而已。但在这些情况下从来没有任何一个“正确答案”——您需要权衡利弊,然后做最适合您的事情。