Problem: I have different record types with many common fields. How could I "include" the common fields in the record type definitions?
Example:
newtype RecordType1 = RecordType1 { a :: Int, b :: Int, y :: String }
newtype RecordType2 = RecordType2 { a :: Int, b :: Int, z :: Boolean }
How to write the equivalent in PureScript?
newtype RecordType1 = RecordType1 { CommonFields, y :: String }
newtype RecordType2 = RecordType2 { CommonFields, z :: Boolean }
The type class Union
mentioned in An Overview of the PureScript Type System could be what I look for... but it seems to be out since PureScript 0.12.0.
Any recommendations? Is there anything I'm missing?
Thanks!
Fyodor's answer is correct. However, there is another clean syntax for combining many row types if you need to.
normally, if you have many record types that you want to combine you would do this:
type Foo r = ( x :: String | r )
type Bar r = ( y :: Int | r )
type FooBar r = Foo (Bar r)
but this will get cumbersome if you have more than one to combine, or the names are too long:
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))
so you can use a nice syntax for combining them in the Type module:
import Type.Row (type (+))
type ThisIsAFooBarBaz r = ThisIsAFoo + ThisIsABar + ThisIsABaz + r
PureScript has a special syntax for combining records:
type Common = ( a :: Int, b :: Int )
type Record1 = { y :: String | Common }
type Record2 = { z :: Boolean | Common }
newtype RecordType3 = RecordType3 { w :: Number | Common }
Note that the definition of Common
uses parentheses, not curly braces. That is because Common
is a row, not a record. You can make a record out of it though:
type CommonRec = Record Common
-- equivalent to: CommonRec = { a :: Int, b :: Int }
In fact, the curly braces notation is just syntactic sugar for applying Record
to a row. An expression { xyz }
gets desugared to Record ( xyz )
.
You can use the "pipe" syntax to extend rows as well:
type CommonPlusFoo = ( foo :: Bar | Common )
type RecWithFoo = { x :: Int | CommonPlusFoo }
You can also make your record types polymorphic by providing Common
as a type parameter:
type Record1Poly r = { y :: String | r }
type Record1 = Record1Poly Common
This is very handy for writing functions that work with partial records, e.g.:
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" }
In this example, the function can work with any record that has a name
field, regardless of what else it might have.
Finally, to express an empty row, use empty parens:
type Record1Poly r = { y :: String | r }
type Record1 = Record1Poly Common
type OnlyY = Record1Poly ()
On a slightly unrelated topic, note that records in PureScript are not the same as records in Haskell. For example, above Record1
and Record2
are true PureScript ad-hoc extensible records (something that Haskell doesn't have), but RecordType3
is a newtype that has one constructor whose parameter is a record.
One important difference is that, unlike Haskell, this wouldn't work:
x = RecordType3 { w: 42.0, a: 1, b: 2 }
y = w x
The expression w x
(or even expression x.w
) doesn't compile, because RecordType3
is not itself a record, it's a newtype that wraps a record. In order to get w
out of it you need to match on the constructor first:
(RecordType3 k) = x
y = k.w
Or wrap that as an accessor function:
unRecordType3 (RecordType3 k) = k
y = (unRecordType3 x).w
In practice this is really inconvenient if you're approaching records with a Haskell mindset. Instead, what you want to do in PureScript is prefer "naked" records (like Record1
and Record2
in my example above) and only resort to wrapping them in newtype
when you really have to.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With