Sum and Product Types

There are generally two data types in FP languages. These are otherwise known as Algebraic Data Types (ADTs):

  • Sum types (corresponds to addition)
  • Product types (corresponds to multiplication)

These are better explained in this video as to how they get their names.

The simplest form of them are Either and Tuple

-- sum
data Either a b   -- a value of htis type is an `a` value OR  a `b` value
  = Left a
  | Right b

-- product        -- a value of htis type is an `a` value AND a `b` value
data Tuple a b
  = Tuple a b

-- both           --  a value of this type is one of the following:
data These a b
  = This a        --  - an `a` value
  | That b        --  - a `b` value
  | Both a b      --  - an `a` value AND a `b` value

-- For example, These could be rewritten to
-- use a combination of Either and Tuple:
type These_ a b = Either a (Either b (Tuple a b))

However, these types can also be 'open' or 'closed':

SumProductSum and Product
ClosedEither a b

Variant (a :: A, b :: B)
Tuple a b

Record (a :: A, b :: B)
(e.g. { a :: A, b :: B }
These a b
OpenVariant (a :: A | allOtherRows)Record (a :: A | allOtherRows)
(e.g. { a :: b | allOtherRows })
-

What does 'Open' mean?

Using this example from the Syntax folder...

-- the 'r' means, 'all other fields in the record'
function :: forall r. { fst :: String, snd :: String | r } -> String
function record = record.fst <> record.snd

-- so calling the function with both record arguments below works
function { fst: "hello", snd: "world" }
function { fst: "hello", snd: "world", unrelatedField: 0 } -- works!
-- If this function used Tuple instead of Record,
--    the first argument would work, but not the second one.

Here's another way to think about this:

  • Records are 'nested Tuples'
  • Variants are 'nested Eithers'
-- We could write
Tuple a (Tuple b (Tuple c (Tuple d e)))
-- or we could write
{ a :: A, b :: B, c :: C, d :: D, e :: E }
-- which desugars to
Record ( a :: A, b :: B, c :: C, d :: D, e :: E )

-- We could write
Either a (Either b (Either c (Either d e)))
-- or we could write
Variant ( a :: A, b :: B, c :: C, d :: D, e :: E)

Keep in mind that records/variants can be but do not necessarily have to be open. If we changed the above function's type signature to remove the r, it would restrict its arguments to a closed Record:

closed :: { fst :: String, snd :: String } -> String
closed record = record.fst <> record.snd

closed { fst: "hello", snd: "world" } -- compiles
closed { fst: "hello", snd: "world", unrelatedField: 0 } -- compiler error

The Types

Tuple

data Tuple a b = Tuple a b
PackageType name"Plain English" name
purescript-tuplesTuple a b2-value Box
UsageValues & their Usage
Stores two ordered unnamed values of the same/different types.
Can be used to return or pass in multiple unnamed values from or into a function.
Tuple a b

Record

forall r. { a :: A, b :: B, {- ... -} | r } -- open record
          { a :: A, b :: B, {- ... -}     } -- closed record
PackageType name"Plain English" name
prim{ field :: ValueType }an N-value Box
UsageValues & their Usage
Stores N ordered named values of the same/different types.
Can be used to return or pass in multiple unnamed values from or into a function.
{ field :: ValueType }

Either

data Either a b
  = Left a
  | Right b
PackageType name"Plain English" name
purescript-eitherEither a bChoice of 2 types
UsageValues & their Usage
Used to indicate one type or a second type
  • Left a - a value of a
  • Right b - a value of b
Error handing (when we care about the error)
  • Left a - the error type that is returned when a computation fails
  • Right b - the output type when a computation succeeds

Maybe

data Maybe a
  = Nothing
  | Just a

Maybe a is the same as Either unimportantType a

PackageType name"Plain English" name
purescript-maybeMaybe aA full or empty box
UsageValues' Representation
Indicates an optional value
  • Nothing - value does not exist
  • Just a - value does exist
Used for error-handling when we don't care about the error (replaces null)
  • Nothing - An error occurred during computation
  • Just a - successful computation returned output.

Variant

This is an advanced type that will be covered in the Hello World/Application Structure folder.

PackageType name"Plain English" name
purescript-variantVariant (a :: A, b :: B)Choice of N types
UsageValues & their Usage
Used to indicate one type among many typesSee docs

These

data These a b
  = This a      -- Left  a
  | That b      -- Right b
  | Both a b    -- Tuple a b
PackageType name"Plain English" name
purescript-theseThese a bSame as Either a (Either b (Tuple a b))

Concluding Thoughts

Performance-wise, it's generally better to use Record instead of Tuple, and it's definitely better to use Record instead of a nested Tuple.

Similarly, it's better to use Variant instead of a nested Either. However, sometimes Either is all one needs and Variant is overkill.

For people new to the language and algebraic data types (ADTs) in general, stick with Tuple, Either, and closed Records.