用户定义的基于元组的数据构造函数
User-defined tuple-based data constructors
我试图再次理解 The Little MLer。 TLMLer 有这个 SML 代码
datatype a pizza = Bottom | Topping of (a * (a pizza))
datatype fish = Anchovy | Lox | Tuna
我翻译成
data PizzaSh a = CrustSh | ToppingSh a (PizzaSh a)
data FishPSh = AnchovyPSh | LoxPSh | TunaPSh
然后是更接近 TLMLer 的替代方案
data PizzaSh2 a = CrustSh2 | ToppingSh2 (a, PizzaSh2 a)
然后我用每一个做一个披萨
fpizza1 = ToppingSh AnchovyPSh (ToppingSh TunaPSh (ToppingSh LoxPSh CrustSh))
fpizza2 = ToppingSh2 (AnchovyPSh, ToppingSh2 (LoxPSh, ToppingSh2 (TunaPSh, CrustSh2)))
分别是PizzaSh FishPSh
和PizzaSh2 FishPSh
类型。
但第二个版本(可以说更接近原始 ML 版本)似乎“另类”。当我在第二个成员递归扩展的地方“cons”浇头时,就好像我正在创建一个二元组。我可以假设 PizzaSh2
的参数数据构造函数“函数”并没有真正构建元组,它只是借用元组作为缺点策略,对吗? Haskell、PizzaSh
或 PizzaSh2
哪个更好?据我了解,元组(笛卡尔积)数据类型将具有单个构造函数,例如 data Point a b = Pt a b
,而不是 ored-together (|) 构造函数的不相交联合。在 SML 中,“*”表示产品,即元组,但是,这是否只是一个“类似元组的东西”,即,它只是一个看起来像元组的方式来一起制作比萨饼?
在Haskell中,我们比较喜欢这种风格:
data PizzaSh a = CrustSh | ToppingSh a (PizzaSh a)
Haskell 中没有必要在那里使用元组,因为像 ToppingSh
这样的数据构造函数可以接受多个参数。
像
一样使用额外的一对
data PizzaSh2 a = CrustSh2 | ToppingSh2 (a, PizzaSh2 a)
创建了一个几乎与前一个同构的类型,但处理起来更麻烦,因为它需要使用更多的括号。例如
foo (ToppingSh x y)
-- vs
foo (ToppingSh2 (x, y))
bar :: PizzaSh a -> ...
bar (ToppingSh x y) = ....
-- vs
bar (ToppingSh2 (x, y)) = ...
此外,该类型确实只是几乎同构。当使用额外的一对时,由于懒惰,我们多了一个可以在类型中表示的值:我们有一个 correpondence
ToppingSh x y <-> ToppingSh2 (x, y)
在这种情况下发生故障
??? <-> ToppingSh2 undefined
也就是说,ToppinggSh2
可以应用于非终止(或其他异常)对值表达式,并且构造一个不能使用 ToppingSh
表示的值。
在操作上,为了实现这一点,GHC 使用了双重间接寻址(大致是指针到指针,或 thunk-returning-pair-of-thunks),这进一步减慢了代码速度。因此,从性能的角度来看,如果关心此类微优化,这也是一个糟糕的选择。
就Haskell方面而言,它确实是在ToppingSh
构造函数中嵌套了一个(,)
构造函数。不进行您请求的嵌套将违反 Haskell 的非严格语义。如果移除嵌套,您将无法区分 undefined :: PizzaSh2 ()
和 ToppingSh undefined :: PizzaSh2 ()
。是的,大多数时候,这不是您想要的。 PizzaSh
是 Haskell 中更自然的公式,除非您有特殊需要能够在评估过程中引入另一个底部。
我无法解决任何特定 ML 实现中幕后发生的事情。虽然我可以说在严格的求值语义下,没有观察到的行为差异,这意味着编译器可以自由使用更多种类的方法。
我试图再次理解 The Little MLer。 TLMLer 有这个 SML 代码
datatype a pizza = Bottom | Topping of (a * (a pizza))
datatype fish = Anchovy | Lox | Tuna
我翻译成
data PizzaSh a = CrustSh | ToppingSh a (PizzaSh a)
data FishPSh = AnchovyPSh | LoxPSh | TunaPSh
然后是更接近 TLMLer 的替代方案
data PizzaSh2 a = CrustSh2 | ToppingSh2 (a, PizzaSh2 a)
然后我用每一个做一个披萨
fpizza1 = ToppingSh AnchovyPSh (ToppingSh TunaPSh (ToppingSh LoxPSh CrustSh))
fpizza2 = ToppingSh2 (AnchovyPSh, ToppingSh2 (LoxPSh, ToppingSh2 (TunaPSh, CrustSh2)))
分别是PizzaSh FishPSh
和PizzaSh2 FishPSh
类型。
但第二个版本(可以说更接近原始 ML 版本)似乎“另类”。当我在第二个成员递归扩展的地方“cons”浇头时,就好像我正在创建一个二元组。我可以假设 PizzaSh2
的参数数据构造函数“函数”并没有真正构建元组,它只是借用元组作为缺点策略,对吗? Haskell、PizzaSh
或 PizzaSh2
哪个更好?据我了解,元组(笛卡尔积)数据类型将具有单个构造函数,例如 data Point a b = Pt a b
,而不是 ored-together (|) 构造函数的不相交联合。在 SML 中,“*”表示产品,即元组,但是,这是否只是一个“类似元组的东西”,即,它只是一个看起来像元组的方式来一起制作比萨饼?
在Haskell中,我们比较喜欢这种风格:
data PizzaSh a = CrustSh | ToppingSh a (PizzaSh a)
Haskell 中没有必要在那里使用元组,因为像 ToppingSh
这样的数据构造函数可以接受多个参数。
像
一样使用额外的一对data PizzaSh2 a = CrustSh2 | ToppingSh2 (a, PizzaSh2 a)
创建了一个几乎与前一个同构的类型,但处理起来更麻烦,因为它需要使用更多的括号。例如
foo (ToppingSh x y)
-- vs
foo (ToppingSh2 (x, y))
bar :: PizzaSh a -> ...
bar (ToppingSh x y) = ....
-- vs
bar (ToppingSh2 (x, y)) = ...
此外,该类型确实只是几乎同构。当使用额外的一对时,由于懒惰,我们多了一个可以在类型中表示的值:我们有一个 correpondence
ToppingSh x y <-> ToppingSh2 (x, y)
在这种情况下发生故障
??? <-> ToppingSh2 undefined
也就是说,ToppinggSh2
可以应用于非终止(或其他异常)对值表达式,并且构造一个不能使用 ToppingSh
表示的值。
在操作上,为了实现这一点,GHC 使用了双重间接寻址(大致是指针到指针,或 thunk-returning-pair-of-thunks),这进一步减慢了代码速度。因此,从性能的角度来看,如果关心此类微优化,这也是一个糟糕的选择。
就Haskell方面而言,它确实是在ToppingSh
构造函数中嵌套了一个(,)
构造函数。不进行您请求的嵌套将违反 Haskell 的非严格语义。如果移除嵌套,您将无法区分 undefined :: PizzaSh2 ()
和 ToppingSh undefined :: PizzaSh2 ()
。是的,大多数时候,这不是您想要的。 PizzaSh
是 Haskell 中更自然的公式,除非您有特殊需要能够在评估过程中引入另一个底部。
我无法解决任何特定 ML 实现中幕后发生的事情。虽然我可以说在严格的求值语义下,没有观察到的行为差异,这意味着编译器可以自由使用更多种类的方法。