eval

Types

A Eval represents a computation to be run given some context. That “to be run” part turns out to be quite powerful. By combining Evals, using some of the functions in this module, we can build up a computation that has access to a sort of mutable state that is updated as the computations are run.

There are three type parameters here, not just two, because an Eval also represents a computation that can fail. In many ways, an Eval is just a superpowered Result!

pub opaque type Eval(a, e, ctx)

Functions

pub fn all(evals: List(Eval(a, b, c))) -> Eval(List(a), b, c)

Run a list of Evals in sequence and then combine their results into a list. If any of the Evals fail, the whole sequence fails.

📝 Note: you might find this called sequence in some other languages like Haskell or PureScript.

pub fn apply(eval_f: Eval(fn(a) -> b, c, d), to eval_a: Eval(
    a,
    c,
    d,
  )) -> Eval(b, c, d)

Intended to be used in combination with the succeed{N} functions. This runs an Eval and then applies it to the result of the second argument.

case expr {
  Add(lhs, rhs) ->
    succeed2(fn (x, y) { x + y })
      |> apply(eval(lhs))
      |> apply(eval(rhs))

  ...
}

📝 Note: you might find this called ap or <*> in some other languages like Haskell or PureScript. In this context, the Eval type would be known as an applicative functor.

pub fn attempt(eval: Eval(a, b, c), catch f: fn(b, c) ->
    Eval(a, b, c)) -> Eval(a, b, c)

Run an Eval and then attempt to recover from an error by applying a function that takes the error value and returns another Eval.

pub fn from(eval: fn(a) -> #(a, Result(b, c))) -> Eval(b, c, a)

Construct an Eval from a function that takes some context and returns a pair of a new context and some Result value. This is provided as a fallback if none of the functions here or in eval/context are getting you where you need to go: generally you should avoid using this in favour of combining the other functions in this module!

pub fn from_option(value: Option(a), error: b) -> Eval(a, b, c)

Construct an Eval from an optional value and an error to throw if that value is None. This is useful for situations where you have some function or value that returns an Option but is not dependent on the context.

pub fn from_result(value: Result(a, b)) -> Eval(a, b, c)

Construct an Eval from a result. This is useful for situations where you have some function or value that returns a Result but is not dependent on the context.

pub fn map(eval: Eval(a, b, c), by f: fn(a) -> d) -> Eval(d, b, c)

Transform the value produced by an Eval using the given function.

📝 Note: you might find this called fmap or <$> in some other languages like Haskell or PureScript. In this context, the Eval type would be known as a functor.

pub fn map2(eval_a: Eval(a, b, c), eval_b: Eval(d, b, c), by f: fn(
    a,
    d,
  ) -> e) -> Eval(e, b, c)

📝 Note: you might find this called liftA2 or liftM2 in some other languages like Haskell or PureScript.

pub fn map_error(eval: Eval(a, b, c), by f: fn(b) -> d) -> Eval(
  a,
  d,
  c,
)
pub fn replace(eval: Eval(a, b, c), with replacement: d) -> Eval(
  d,
  b,
  c,
)
pub fn replace_error(eval: Eval(a, b, c), with replacement: d) -> Eval(
  a,
  d,
  c,
)
pub fn run(eval: Eval(a, b, c), with context: c) -> Result(a, b)

Given an Eval, actuall perform the computation by also providing the context that the computation is running in.

pub fn succeed(value: a) -> Eval(a, b, c)

Construct an Eval that always succeeds with the given value, regardless of context.

📝 Note: you might find this called pure or return in some other languages like Haskell or PureScript.

pub fn succeed2(f: fn(a, b) -> c) -> Eval(
  fn(a) -> fn(b) -> c,
  d,
  e,
)

Like succeed, but used specifically with a function that takes two arguments. This is most commonly used with apply to run a series of Evals in a pipeline to build up some more complex value.

📝 Note: when used this way, this is often known as “applicative programming”. In this context, the Eval type would be known as an applicative functor.

❓ Why are these succeedN functions necessary? In other functional programming languages, like Elm or Haskell, functions are curried which means all functions are actually just a series of single-argument functions that return other functions. We can achieve this in Gleam by using the function.curryN functions.

We need the functions passed to succeed to be curried to work properly with apply, and so we provide a handful of these succeedN functions that do the currying for you.

pub fn succeed3(f: fn(a, b, c) -> d) -> Eval(
  fn(a) -> fn(b) -> fn(c) -> d,
  e,
  f,
)

Like succeed, but used specifically with a function that takes three arguments. This is most commonly used with apply to run a series of Evals in a pipeline to build up some more complex value.

pub fn succeed4(f: fn(a, b, c, d) -> e) -> Eval(
  fn(a) -> fn(b) -> fn(c) -> fn(d) -> e,
  e,
  f,
)

Like succeed, but used specifically with a function that takes four arguments. This is most commonly used with apply to run a series of Evals in a pipeline to build up some more complex value.

pub fn succeed5(f: fn(a, b, c, d, e) -> f) -> Eval(
  fn(a) -> fn(b) -> fn(c) -> fn(d) -> fn(e) -> f,
  e,
  g,
)

Like succeed, but used specifically with a function that takes five arguments. This is most commonly used with apply to run a series of Evals in a pipeline to build up some more complex value.

pub fn succeed6(f: fn(a, b, c, d, e, f) -> g) -> Eval(
  fn(a) -> fn(b) -> fn(c) -> fn(d) -> fn(e) -> fn(f) -> g,
  e,
  h,
)

Like succeed, but used specifically with a function that takes six arguments. This is most commonly used with apply to run a series of Evals in a pipeline to build up some more complex value.

pub fn then(eval: Eval(a, b, c), do f: fn(a) -> Eval(d, b, c)) -> Eval(
  d,
  b,
  c,
)

Run an Eval and then apply a function that returns another Eval to the result. This can be useful for chaining together multiple Evals.

📝 Note: you might find this called bind, >>=, flatMap, or andThen in some other languages like Haskell, Elm, or PureScript. In this context, the Eval type would be known as a monad.

pub fn throw(error: a) -> Eval(b, a, c)

Construct an Eval that always fails with the given error, regardless of context. Often used in combination with then to run some Eval and then potentially fail based on the result of that computation.

eval(expr) |> then(fn (y) {
  case y == 0.0 {
    True ->
      throw(DivisionByZero)

    False ->
      succeed(y)
  }
})