NovahGithubSource

Computation expressions

Computation expressions streamlines some types of repetitive code, similar to do notation in Haskell or computation expressions in F#.

Begin code:

module compexp

import novah.computation

// without computation expressions
uglyFindUser : String -> Result User String
uglyFindUser name =
  case fetchUser name of
    Ok user ->
      case fetchAddress user of
        Ok address -> Ok { address | user }
        Err err -> Err err
    Err err -> Err err

// using computation expressions
prettyFindUser : String -> Result User String
prettyFindUser name = do.result
  let! user = fetchUser name
  let! address = fetchAddress user
  return { address | user }

// all permutations of x and y where x and y are not equals
uglyPairs : List (Tuple Int Int)
uglyPairs =
  [1 .. 10] |> List.flatMap \x ->
    [1 .. 10] |> List.mapSome \y ->
      if x != y then
        Some (x ; y)
      else
        None

// same but with computation expressions
prettyPairs : List (Tuple Int Int)
prettyPairs = do.list
  for x in [1 .. 10] do
    for y in [1 .. 10] do
      if x != y then yield x ; y

End code.

Defining your own

Computation expressions are just a syntactic sugar for direct function calls. Defining your own is pretty simple and only requires that you choose which functions to support.

Function nameTypical typeDescription
bindm a -> (a -> m b) -> m bDefines the let bang (let!) construct
form a -> (a -> m b) -> m bDefines the for construct which works the same as bind.
Only one of for or bind should be implemented.
returna -> m aDefines the return construct
yielda -> m aDefines the yield construct which works the same as return.
Only one of return or yield should be implemented.
zerom a`zero` will be used for `if`s without an else and expressions that do not `return` or `yield`
combinem a -> m a -> m a`combine` will be used to join line-separated expressions

Begin code:

module compexp

// define a computation expression for lists
pub
list :
  { "for" : List a -> (a -> List b) -> List b
  , "yield" : a -> List a
  , combine : List a -> List a -> List a
  , zero : List a
  }
list =
  { "for": flip List.flatMap
  , "yield": \x -> [x]
  , combine: \(l1 : List a) l2 -> l1 ++ l2
  , zero: []
  }

// using the computation expression
squares : List Int
squares = do.list
  for x in [1 .. 10] do yield x * x

End code.