Context

Right now, monomorphization is done in reverse dependency order of the app entry point. Starting with the app module, we

  1. monomorphize a module
  2. pick up any specializations it needs from its dependencies
  3. monomorphize the module’s direct dependencies, continuing from (1)

This is all well and good when the symbols in an application are statically resolvable - being so, we know exactly what dependencies a def has before we go to specialize.

For example, given the sets of modules

# module Encode
appendWith : Bytes, (Bytes -> Bytes) -> Bytes

toBytes val encoderGenerator = appendWith [] (encoderGenerator val)

# module App
Hello := {}

encoderGenerator : Hello -> (Bytes -> Bytes)
encoderGenerator = \\@Hello _ -> ...

main = toBytes (@Hello {}) encoderGenerator

The dependency graph here is App#main -> App#encoderGenerator, App#main -> Encode#toBytes. We

  1. monomorphize App#main with known type Bytes
  2. find the needed specialization App#encoderGenerator with type Hello -> (Bytes -> Bytes) and lambda set [ App#encoderGenerator ], and specialize that
    1. During that specialization, we specialize a closure, say of name #clos1, of type Bytes -> Bytes and the appropriate lambda set [ clos1 ]
  3. find the needed specialization Encode#toBytes with type Hello, Hello -[ encoderGenerator ]-> (Bytes -[ clos1 ]-> Bytes) -[ toBytes ]-> Bytes) (lambda sets inlined in the type).
  4. finish monomorphization of App and continue to…
  5. …monomorphize Encode, namely Encode#toBytes

Abilities cause problems across modules

When you use abilities within a module, all is fine here. But if you try to use abilities between modules, problems begin to arise.

Abilities can introduce implicit dependencies between modules, which can’t be resolved until monomorphization of the module in question. Here’s an example:

# module Encode
Encode has toEncoder : a -> Encoder | a has Encode

toBytes val = appendWith [] (toEncoder val)

# module App
Hello := {}

toEncoder = \\@Hello _ -> ...

main = toBytes (@Hello {})

The resolved dependency graph here is App#main -> Encode#toBytes -> App#Hello#toEncoder. Without abilities, this dependency chain is not even possible, as Roc does not allow (explicit) cyclic dependencies.