Delayed computations

📒

Syntax cheat-sheet

    • Writing a delayed computation
      • myFunction arg = '("single quote" ++ arg)
      • myFunction arg _ = "underscore argument" ++ arg
      • myFunction arg = do "special keyword" ++ arg
    • Calling a delayed computation
      • myFunction "arg" ()
      • !(myFunction "arg")

Values in Unison are not, by default, lazily evaluated. But it's common to want to express that a value or calculation should be delayed until it's absolutely needed.

For example the term longText in the following snippet is evaluated strictly:

longText : Text
longText = "🐵 Imagine infinite monkeys on infinite typewriters 🙊…"
coinflip : Boolean -> Text
coinflip bool = if bool then longText else "Hi"

But you might not actually need to evaluate longText—there are circumstances where the calculation of a value might be very expensive or could introduce surprising behavior if run eagerly.

One way you might solve for this is to create a "thunk": a function with no arguments which returns the desired value when it's called.

longText : () -> Text
longText _ = "🐵 Imagine infinite monkeys on infinite typewriters 🙊…"

Because this is a common pattern, Unison provides the single quote, ', as syntactic sugar for representing a signature with the form () -> a.

We can rewrite the type () -> Text as just 'Text.

longText : 'Text
longText = do "🐵 Imagine infinite monkeys on infinite typewriters 🙊…"

Just as Unison provides syntax for representing delayed computations in a type signature, there are a few ways to delay an expression in Unison.

🧠

These are all valid ways to implement a function for the type 'Text:

  • myFunction = '"delayed with single quote"
  • myFunction _ = "delayed with underscore argument"
  • myFunction = do "delayed with do keyword"

All three are equivalent in meaning.

When we want to run the thunk, we can call the value by adding a () to the end of the function with no spaces, representing the idea: "I'm calling this function with zero arguments".

longText()
"🐵 Imagine infinite monkeys on infinite typewriters 🙊…"

Unison also provides the ! symbol as syntactic sugar for calling a thunk. The ! is prepended to the function name.

!longText

To review: the single quote, ', an underscore _, or the do keyword introduce a thunk and appending () or prepending ! executes it.

Caveat: Delayed computations with multiple arguments

Let's say we have a function that has one argument and returns a delayed value of 'Text.

coinflip : Boolean -> 'Text
coinflip bool = do if bool then longText() else "Hi"

We want to call coinflip with its argument and force the thunk to get a value.

We can always write:

coinflip true ()

But the ! syntax applies to the the value to its immediate right, so we have to surround the function application for coinflip true in parentheses before prepending the ! symbol.

!(coinflip true)

The single quote syntax for delaying a computation has the same gotcha as the ! syntax. You might think you're delaying the result of a function call with 'Nat.increment 5 but you're actually delaying the function, not result of calling it.

You'll need parenthesis around the entire expression to delay the result of a function call with the single quote syntax.

delayed6 = '(Nat.increment 5)

The do keyword is a bit different. It delays the result of everything to its right, so you don't need to wrap the entire expression in parentheses.

delayed6 = do Nat.increment 5