如何在PureScript中合并记录类型的行? (在PureScript 0.12.0中,Union类型类是否可以替代?)

时间:2018-08-23 13:25:44

标签: union rows records purescript

问题:我有许多常见字段的记录类型不同。如何在记录类型定义中“包括”公用字段?

示例:

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中提到的类型类Union可能正是我想要的...但是自PureScript 0.12.0起似乎已经淘汰了。

有什么建议吗?我有什么想念的吗?

谢谢!

2 个答案:

答案 0 :(得分:9)

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 )

您也可以使用“管道”语法来扩展行:

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即席可扩展记录(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中执行的操作更喜欢“裸”记录(例如上面的示例中的Record1Record2),并且仅在确实需要时才将它们包装在newtype

答案 1 :(得分:4)

费奥多的答案是正确的。但是,如果需要,还有另一种干净的语法可用于组合许多行类型。

通常,如果要合并的记录类型很多,则可以执行以下操作:

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