unison/unison-src/transcripts/blocks.md
2024-06-25 11:11:07 -07:00

3.9 KiB

Blocks and scoping

scratch/main> builtins.merge

Names introduced by a block shadow names introduced in outer scopes

For example:

ex thing =
  thing y = y
  -- refers to `thing` in this block
  -- not the argument to `ex`
  bar x = thing x + 1
  bar 42

> ex "hello"

Whether a block shadows outer names doesn't depend on the order of bindings in the block

The thing reference in bar refers to the one declared locally in the block that bar is part of. This is true even if the declaration which shadows the outer name appears later in the block, for instance:

ex thing =
  bar x = thing x + 1
  thing y = y
  bar 42

> ex "hello"

Blocks use lexical scoping and can only reference definitions in parent scopes or in the same block

This is just the normal lexical scoping behavior. For example:

ex thing =
  bar x = thing x + 1 -- references outer `thing`
  baz z =
    thing y = y -- shadows the outer `thing`
    thing z     -- references the inner `thing`
  bar 42

> ex (x -> x * 100)

Here's another example, showing that bindings cannot reference bindings declared in blocks nested in the body (the final expression) of a block:

ex thing =
  bar x = thing x + 1 -- refers to outer thing
  let
    thing y = y
    bar 42

> ex (x -> x * 100)

Blocks can define one or more functions which are recursive or mutually recursive

We call these groups of definitions that reference each other in a block cycles. For instance:

sumTo n =
  -- A recursive function, defined inside a block
  go acc n =
    if n == 0 then acc
    else go (acc + n) (drop n 1)
  go 0 n

ex n =
  -- Two mutually recursive functions, defined in a block
  ping x = pong (x + 1)
  pong x = ping (x + 2)
  ping 42

The go function is a one-element cycle (it reference itself), and ping and pong form a two-element cycle.

Cyclic references or forward reference must be guarded

For instance, this works:

ex n =
  ping x = pong + 1 + x
  pong = 42
  ping 0

Since the forward reference to pong appears inside ping.

This, however, will not compile:

ex n =
  pong = ping + 1
  ping = 42
  pong

This also won't compile; it's a cyclic reference that isn't guarded:

ex n =
  loop = loop
  loop

This, however, will compile. This also shows that 'expr is another way of guarding a definition.

ex n =
  loop = '(!loop)
  !loop

Just don't try to run it as it's an infinite loop!

Cyclic definitions in a block don't have access to any abilities

The reason is it's unclear what the order should be of any requests that are made. It can also be viewed of a special case of the restriction that elements of a cycle must all be guarded. Here's an example:

structural ability SpaceAttack where
  launchMissiles : Text -> Nat

ex n =
  zap1 = launchMissiles "neptune" + zap2
  zap2 = launchMissiles "pluto" + zap1
  zap1

The body of recursive functions can certainly access abilities

For instance, this works fine:

structural ability SpaceAttack where
  launchMissiles : Text -> Nat

ex n =
  zap1 planet = launchMissiles planet + zap2 planet
  zap2 planet = launchMissiles planet + zap1 planet
  zap1 "pluto"

Unrelated definitions not part of a cycle and are moved after the cycle

For instance, zap here isn't considered part of the cycle (it doesn't reference ping or pong), so this typechecks fine:

structural ability SpaceAttack where
  launchMissiles : Text -> Nat

ex n =
  ping x = pong (x + 1)
  zap = launchMissiles "neptune"
  pong x = ping (x + 2)
  ping 42

This is actually parsed as if you moved zap after the cycle it find itself a part of:

structural ability SpaceAttack where
  launchMissiles : Text -> Nat

ex n =
  ping x = pong (x + 1)
  pong x = ping (x + 2)
  zap = launchMissiles "neptune"
  ping 42