Right now, monomorphization is done in reverse dependency order of the app entry point. Starting with the app module, we
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
App#main
with known type Bytes
App#encoderGenerator
with type Hello -> (Bytes -> Bytes)
and lambda set [ App#encoderGenerator ]
, and specialize that
#clos1
, of type Bytes -> Bytes
and the appropriate lambda set [ clos1 ]
Encode#toBytes
with type Hello, Hello -[ encoderGenerator ]-> (Bytes -[ clos1 ]-> Bytes) -[ toBytes ]-> Bytes)
(lambda sets inlined in the type).App
and continue to…Encode
, namely Encode#toBytes
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.