Record
Why we need Record
Let's say we're tasked to create a Person
data type to describe the name, age and address, we ended up with some thing like this:
data Person = Person String Int String
There're several challenges with this definition:
❎ Value constructor has several fields and it's not obvious which field is which.
❎ We need strictly follow the order of the fields defined in the value constructors.
❎ Need create additional functions if we need get name, age or address from this data type.
Record type is introduced to resolve these challenges.
Record in Haskell
Let's first look at how it works in Haskell:
data Person = Person { name :: String, age :: Int, address :: String }
✅ Clear definition of fields
✅ No need to follow the strict order of fields as long as the field name is specified.
✅ This would automatically create functions to get each field, or accessor functions:
name :: Person -> String
age :: Person -> Int
address :: Person -> String
Record in PureScript
PureScript encodes JavaScript-style objects directly by using row types, often we might not need a data constructor at all when using object types:
type Person = { name :: String, age :: Int, address :: String }
Record literals are surrounded by braces, which are constructed with syntax similar to that of JavaScript (and the type definition):
p1 :: Person
p1 = {name: "Leo", address: "Fake city", age: 43}
We can access the fields of records by:
- using a dot
- property accessor
Using dot
Instead of introducing accessor functions, fields of records can be accessed using a dot, followed by the label of the field: record.fieldName
> p1
{ address: "Fake City", age: 43, name: "Leo" }
> p1.name
"Leo"
> p1.age
43
Property Accessors
Unfortunately PureScript doesn't provide the accessor functions like Haskell, but it has provide property accessor, which is an underscore followed by a property name: _.fieldName
> p1
{ address: "Fake City", age: 43, name: "Leo" }
> _.name p1
"Leo"
> _.age p1
43
More details can be found from PureScript language reference.
Record Patterns
Pattern matching on record type:
showPerson :: { firstName :: String, lastName :: String } -> String
showPerson { firstName: x, lastName: y } = y <> ", " <> x
It does two things:
- matches any record which contains fields called first and last,
- and binds their values to the names x and y, respectively.
📢 record patterns vs. record literals:
- record literals: value on the right of the colon, like firstName: "Leo"
- record patterns: a binder for each field, like firstName: x
Row Polymorphism
explicit type annotation
Must match exactly :
showPerson :: { firstName :: String, lastName :: String } -> String
showPerson { firstName: x, lastName: y } = y <> ", " <> x
> showPerson {firstName: "Leo", lastName: "Wang", age: 10} ❌
inferred type annotation
showPerson' { firstName: x, lastName: y } = y <> ", " <> x
> showPerson' {firstName: "Leo", lastName: "Wang", age: 10} ✅
And this is the inferred type by PureScript:
> :t showPerson'
forall (t9 :: Row Type).
{ firstName :: String
, lastName :: String
| t9
}
-> String
We can read the new type signature of showPerson as "takes any record with firstName and lastName fields which are Strings and any other fields, and returns a String". This function is polymorphic in the row r of record fields, hence the name row polymorphism.
Record Puns
showPerson :: { firstName :: String, lastName :: String } -> String
showPerson { firstName: x, lastName: y } = y <> ", " <> x 1️⃣
showPerson { firstName, lastName } = lastName <> ", " <> firstName 2️⃣
1️⃣ binding the firstName and lastName fields to values named x and y.
2️⃣ specify the names of the fields, but NOT specify the names of the values we want to bind. This is called a record pun.
It is also possible to use record puns to construct records.
This is very similar to the Property Shorthand
in JavaScript:
const name = "Alice";
const age = 30;
const person = { name, age };
Record update
// TODO: