Skip to content

Search content

From F# to Gleam > 6: From Computation Expressions to use

3 min readFrom F# to GleamPart 6
Nested result callbacks unfold into a flat sequence of use bindings on a pale pink background.

If you’ve used let! in an F# computation expression, Gleam’s use may look like familiar binding syntax. Both can flatten code that would otherwise nest. They aren’t the same mechanism.

Table of contents

On this page

Start with Ordinary Result Binding

Let’s parse two positive dimensions and calculate their area. F# has Result.bind, but doesn’t ship a built-in result { ... } computation-expression builder. We can express this without adding one:

let positiveDimension (text: string) =
    match System.Int32.TryParse text with
    | true, value when value > 0 -> Ok value
    | _ -> Error "Expected a positive integer"

let area widthText heightText =
    positiveDimension widthText
    |> Result.bind (fun width ->
        positiveDimension heightText
        |> Result.bind (fun height -> Ok (width * height)))scratch.fsx

The direct Gleam translation uses result.try with ordinary callbacks:

import gleam/int
import gleam/list
import gleam/result

fn positive_dimension(text: String) -> Result(Int, String) {
  case int.parse(text) {
    Ok(value) if value > 0 -> Ok(value)
    _ -> Error("Expected a positive integer")
  }
}

fn area_nested(width_text: String, height_text: String) -> Result(Int, String) {
  result.try(positive_dimension(width_text), fn(width) {
    result.try(positive_dimension(height_text), fn(height) {
      Ok(width * height)
    })
  })
}src/learning_gleam.gleam

result.try calls its callback only for Ok. For Error, it returns the error without running the callback. That explains the short-circuiting behaviour without needing any special return statement.

Flatten the Callbacks with use

Here’s the same function with less indentation:

fn area(width_text: String, height_text: String) -> Result(Int, String) {
  use width <- result.try(positive_dimension(width_text))
  use height <- result.try(positive_dimension(height_text))
  Ok(width * height)
}src/learning_gleam.gleam

The 1st use inserts a callback as the final argument to result.try. Its parameter is width. Everything after that line becomes the callback body, including the 2nd use.

It’s not an unconditional early return or a new error-handling primitive. It’s the same function call as the nested version. The errors must still have compatible types, so map them into a shared error type before chaining when necessary.

It’s Not Just for Results

Any suitable callback-taking function can work with use. For example, list.map calls its callback once per element:

fn greetings(names: List(String)) -> List(String) {
  use name <- list.map(names)
  "Hello, " <> name
}src/learning_gleam.gleam

Here the rest of the block runs twice for two names, rather than once for a successful result. A different callback API might defer execution. Read the receiving function’s contract instead of assuming that use means asynchronous work or error propagation.

Also, Gleam’s use isn’t F#‘s resource-disposal use. It doesn’t automatically provide Dispose, finally, or cleanup. Any such behaviour must come from the function receiving the callback.

Try It

Combine the Gleam blocks in src/learning_gleam.gleam and run gleam run:

pub fn main() -> Nil {
  assert area("3", "4") == Ok(12)
  assert area("bad", "4") == Error("Expected a positive integer")
  assert area("3", "0") == Error("Expected a positive integer")
  assert area("3", "4") == area_nested("3", "4")
  assert area("bad", "4") == area_nested("bad", "4")
  assert area("3", "0") == area_nested("3", "0")
  assert greetings(["Ada", "Grace"]) == ["Hello, Ada", "Hello, Grace"]
  assert greetings([]) == []
}src/learning_gleam.gleam

For F#:

assert (area "3" "4" = Ok 12)
assert (area "bad" "4" = Error "Expected a positive integer")
assert (area "3" "0" = Error "Expected a positive integer")scratch.fsx

Use the flattened version when it makes a chain easier to follow. For a short callback, an ordinary function call is often clearer.

Further reading: use expressions and result.try.



Previous in series
From F# to Gleam > 5: Option, Result, and Typed Errors
Next in series
From F# to Gleam > 7: Modules, Packages, and Tests