If we want to give the fields in adata constructortheir own names, we can use Unison's syntax for record types. When you define a record type, Unison will automatically generate functions which allow you to set a value of the data type, to get a value from the data type, and modify a value in the data type.
Let's add a record type to model the idea of a volunteer.
unique type Volunteer
= { preferredName : Text,
age : Nat,
desiredStartDate : Text,
endDate : Optional Text,
daysAvailable : Set DaysOfWeek }
Record types are started with the name of the type, but instead of a data constructor, you'll see open curly braces to the right of the equals sign. Each field name is named and given a type in a comma-separated list. Record types are supported for types which have asingledata constructor. Unison creates a data constructor for you with the same name as the overall type.
Imagine if we had to remember what each field meant forVolunteer
without these names! 😵 Record types can be useful when the data constructor for a type gets exceptionally long, and therefore component parts would benefit from receiving a human readable name.
Upon saving the scratch.u file, we can see that Unison has derived a series of functions for each of our named fields.
⍟ These new definitions are ok to `add`:
type DaysOfWeek
type Volunteer
Volunteer.age : Volunteer -> Nat
Volunteer.age.modify : (Nat ->{g} Nat) -> Volunteer ->{g} Volunteer
Volunteer.age.set : Nat -> Volunteer -> Volunteer
Volunteer.daysAvailable : Volunteer -> Set DaysOfWeek
Volunteer.daysAvailable.modify : (Set DaysOfWeek ->{g} Set DaysOfWeek)
-> Volunteer
->{g} Volunteer
Volunteer.daysAvailable.set : Set DaysOfWeek -> Volunteer -> Volunteer
Volunteer.desiredStartDate : Volunteer -> Text
Volunteer.desiredStartDate.modify : (Text ->{g} Text) -> Volunteer ->{g} Volunteer
Volunteer.desiredStartDate.set : Text -> Volunteer -> Volunteer
Volunteer.endDate : Volunteer -> Optional Text
Volunteer.endDate.modify : (Optional Text ->{g} Optional Text)
-> Volunteer
->{g} Volunteer
Volunteer.endDate.set : Optional Text -> Volunteer -> Volunteer
Volunteer.preferredName : Volunteer -> Text
Volunteer.preferredName.modify : (Text ->{g} Text) -> Volunteer ->{g} Volunteer
Volunteer.preferredName.set : Text -> Volunteer -> Volunteer
Creating a value for aVolunteer
doesn't involve any additional overhead. There's no requirement for mentioning the field names.
Volunteer.example⧨Volunteer
"Carl Sagan"
40
"July 24th, 1989"
None
(internal.Set
(internal.Bin
3
DaysOfWeek.Tues
()
(internal.Bin 1 DaysOfWeek.Mon () internal.Tip internal.Tip)
(internal.Bin 1 DaysOfWeek.Wed () internal.Tip internal.Tip)))
But with these programmatically generated methods, we can easily change the value of one field:
Volunteer.age.set 500 Volunteer.example⧨Volunteer
"Carl Sagan"
500
"July 24th, 1989"
None
(internal.Set
(internal.Bin
3
DaysOfWeek.Tues
()
(internal.Bin 1 DaysOfWeek.Mon () internal.Tip internal.Tip)
(internal.Bin 1 DaysOfWeek.Wed () internal.Tip internal.Tip)))
Here we've simply overwritten the original value for our example'sage
field. The first argument toVolunteer.age.set
is your desired value of the field and the second argument is the originalVolunteer
.The return type of ourset
function isVolunteer
,and remember, Unison values are immutable, so this is a copy of the originalVolunteer
.
If we didn't have a specific value to set, we can provide a function which describeshowone field should be transformed using themodify
function which is generated for each field in a record.
Volunteer.age.modify (age -> age Nat.+ 1) Volunteer.example⧨Volunteer
"Carl Sagan"
41
"July 24th, 1989"
None
(internal.Set
(internal.Bin
3
DaysOfWeek.Tues
()
(internal.Bin 1 DaysOfWeek.Mon () internal.Tip internal.Tip)
(internal.Bin 1 DaysOfWeek.Wed () internal.Tip internal.Tip)))
Here we're using theVolunteer.age.modify
function created for the record'sage.
Checkout the signatureVolunteer.age.modify : (Nat ->{g} Nat) -> Volunteer ->{g} Volunteer
and note that you couldn't, for example, use the modify function to change from anage
expressed in terms ofNat
to an age expressed in terms of some type representing Geologic Time. 🦕
Using the record type syntax we can also streamline the process of getting the value of one field. Here's what getting the value for our example's age looks like using theVolunteer.age
function that Unison provides for us.
Compare this to having to pattern match on the value to get a value:
getAge : Volunteer -> Nat
getAge = cases Volunteer _ age _ _ _ -> age
getAge Volunteer.example⧨40
Of course, you can still pattern match on a record type--you do not need to mention the field names in your pattern match cases--those conventions don't change.