Record types

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 forVolunteerwithout 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 aVolunteerdoesn't involve any additional overhead. There's no requirement for mentioning the field names.

But with these programmatically generated methods, we can easily change the value of one field:

Here we've simply overwritten the original value for our example'sagefield. The first argument toVolunteer.age.setis your desired value of the field and the second argument is the originalVolunteer.The return type of oursetfunction 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 themodifyfunction which is generated for each field in a record.

Here we're using theVolunteer.age.modifyfunction created for the record'sage.Checkout the signatureVolunteer.age.modify : (Nat ->{g} Nat) -> Volunteer ->{g} Volunteerand note that you couldn't, for example, use the modify function to change from anageexpressed in terms ofNatto 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.agefunction 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 example

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.

🌻

Summary:

  • Record types allow you to define a type with individually named field names
  • Record types are for types with a single data constructor
  • Record types automatically generate functions which get, set, and modify values for the record