如何在 PureScript 中组合记录类型的行? (在 PureScript 0.12.0 中是否有 Union 类型类的替代方案?)

How to combine rows of record types in PureScript? (Is there any alternative to the Union typeclass in PureScript 0.12.0?)

问题:我有不同的记录类型,其中有许多公共字段。我如何 "include" 记录类型定义中的公共字段?

示例:

newtype RecordType1 = RecordType1 { a :: Int, b :: Int, y :: String }
newtype RecordType2 = RecordType2 { a :: Int, b :: Int, z :: Boolean } 

如何在 PureScript 中编写等价物?

newtype RecordType1 = RecordType1 { CommonFields, y :: String }
newtype RecordType2 = RecordType2 { CommonFields, z :: Boolean }

An Overview of the PureScript Type System 中提到的 class Union 类型可能是我要找的...但它似乎从 PureScript 0.12.0 开始就没有了。

有什么建议吗?有什么我遗漏的吗?

谢谢!

PureScript 具有用于组合记录的特殊语法:

type Common = ( a :: Int, b :: Int )
type Record1 = { y :: String | Common }
type Record2 = { z :: Boolean | Common }
newtype RecordType3 = RecordType3 { w :: Number | Common }

请注意,Common 的定义使用圆括号,而不是大括号。那是因为 Common,而不是记录。你可以用它做一个记录:

type CommonRec = Record Common 
-- equivalent to:  CommonRec = { a :: Int, b :: Int }

事实上,花括号符号只是将 Record 应用于一行的语法糖。表达式 { xyz } 被脱糖为 Record ( xyz ).

您也可以使用 "pipe" 语法来扩展行:

type CommonPlusFoo = ( foo :: Bar | Common )
type RecWithFoo = { x :: Int | CommonPlusFoo }

您还可以通过提供 Common 作为类型参数来使您的记录类型多态:

type Record1Poly r = { y :: String | r }
type Record1 = Record1Poly Common

这对于编写处理部分记录的函数非常方便,例如:

updateName :: forall r. { name :: String | r } -> { name :: String | r }
updateName x = x { name = "Mr. " <> x.name }

jones = { name: "Jones", occupation: "Plumber" }
mrJones = updateName jones  -- mrJones = { name: "Mr. Jones", occupation: "Plumber" }

在此示例中,该函数可以处理任何具有 name 字段的记录,无论它可能还有什么。


最后,要表示空行,请使用空括号:

type Record1Poly r = { y :: String | r }
type Record1 = Record1Poly Common
type OnlyY = Record1Poly ()

关于一个稍微不相关的话题,请注意 PureScript 中的记录与 Haskell 中的记录不同。例如,上面的 Record1Record2 是真正的 PureScript ad-hoc 可扩展记录(Haskell 没有的东西),但是 RecordType3 是一种只有一个构造函数的新类型其参数是一条记录。

一个重要的区别是,与 Haskell 不同,这是行不通的:

 x = RecordType3 { w: 42.0, a: 1, b: 2 }
 y = w x

表达式w x(甚至表达式x.w)无法编译,因为RecordType3本身不是记录,它是包装的新类型一个记录。为了从中得到 w 你需要先匹配构造函数:

 (RecordType3 k) = x
 y = k.w

或者将其包装为访问函数:

 unRecordType3 (RecordType3 k) = k
 y = (unRecordType3 x).w

实际上,如果您以 Haskell 的心态处理记录,这真的很不方便。相反,你想在 PureScript 中做的是更喜欢 "naked" 记录(如我上面示例中的 Record1Record2)并且只在你真正需要时才将它们包装在 newtype 中不得不。

费奥多尔的回答是正确的。但是,如果需要,还有另一种简洁的语法可以组合多种行类型。

通常,如果您有许多要合并的记录类型,您会这样做:

type Foo r = ( x :: String | r )
type Bar r = ( y :: Int | r )
type FooBar r = Foo (Bar r)

但是如果要组合多个,或者名称太长,这会变得很麻烦:

type ThisIsAFoo r = ( x :: String | r )
type ThisIsABar r = ( y :: Int | r )
type ThisIsABaz r = ( z :: Number | r )
type ThisIsAFooBarBaz r = ThisIsAFoo (ThisIsABar (ThisIsABaz r))

所以你可以使用一个很好的语法在 Type 模块中组合它们:

import Type.Row (type (+))
type ThisIsAFooBarBaz r = ThisIsAFoo + ThisIsABar + ThisIsABaz + r