Skip to content

Instantly share code, notes, and snippets.

@chrisdone
Created December 2, 2018 14:07
Show Gist options
  • Select an option

  • Save chrisdone/78b4fede6e668da3defad6c07f68a833 to your computer and use it in GitHub Desktop.

Select an option

Save chrisdone/78b4fede6e668da3defad6c07f68a833 to your computer and use it in GitHub Desktop.
Maybe Not - records in PureScript

In his talk Maybe Not, Rich Hickey explores a way of thinking about data in terms of the functions that work on them, rather than baking in any assumptions into the system directly. He's trying to take his spec system in this direction. I also have a keen interest in moving over to using records more often in typed Haskell-like languages.

An example from the slides was:

(s/select ::user [::id ::addr {::addr [::zip]}])

This expresses that I just want the fields id, addr and from within addr I just want zip. If the data structure has more things, I don't care about that. I just care about the fields I need to do my work.

The way to express this in PureScript (a few years old language like Haskell) is:

forall user addr. { id :: Int, addr :: { zip :: String | addr } | user } -> {}

Which says the same thing, the syntax { f1 :: T1, f2 :: T2 | r } says "for some record r, it has at least fields f1 of type T1 and f2 of type T2. You can pass a function expecting this type something like:

{ id: 1, address: { zip: "M3123", address1: "2 King Street" } }

The fields id, address, and zip must be present in the input.

Rich also mentions having a list of schemas, which can be written as, e.g.

contactFriend :: forall friend.
  Array { name :: String, number :: String | friend }
  -> {}
contactFriend r = {}

I can return the given record, too:

uppercaseName :: forall friend. { name :: String | friend } -> { name :: String | friend }
uppercaseName r = r { name: upperCase r.name }

I can also delete, insert, rename or union/merge or do a disjoint union on two records, via functions from the Record module.

For example, use disjointUnion to combine two records, disallowing duplicate fields:

import Record
person = { name: "Chris", address: { country: "UK" } }
creds = { user: "wibble", pass: "woop" }
combined = disjointUnion person creds

The compiler will issue a helpful warning, offering the following type signature for combined:

No type declaration was provided for the top-level declaration of combined.
It is good practice to provide type declarations as a form of documentation.
The inferred type of combined was:

  { address :: { country :: String
               }
  , name :: String
  , pass :: String
  , user :: String
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment