You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently Motoko has no means of splitting an actor into parts, so all methods of an actor (class) must go into a monolithic source file. This is in conflict with established engineering practices of reusability and separation of concerns, thus introduces accidental complexity and duplication into Motoko projects.
The users survey has [ref?] has also shown that this is perceived as an act problem across the community. A code review performed on a major DEX has encountered a humongous source file giving home to a monolithic actor.
Which approaches were considered?
In this section we'll give three concept ideas and try to flesh out their merits and drawbacks.
Syntactic concatenation
Extending the compiler with syntactic forms for actor fragments that are type-checked separately and an actor { … } and <exp> construct that merges AST fragments to a final actor. Here the <exp> part is a syntactic expression, which can be formally a call to a function, passing some parameters in, and results in an actor-typed fragment. But we are actually interested in the resulting AST, and not a value. This could be accomplished by recording all ASTs (of mixin aspect) that come from imports in a special environment and looking them up to obtain the expansion of the <exp> part. Unfortunately this is quite an alien concept to moc, which was not designed with this type of metaprogramming in mind. All kinds of name shadowing issues can arise.
Also, the lexical environment (with eventual values living in there) need to be transplanted from the mixin defining site to the mixin usage site. One could do this using an ad-hoc synthesised function (of record result type) and call that behind the scenes (in the and part), pattern matching the obtained record in each fragment method (as a preamble). This also means that we need an analysis of which bindings the mixin closes over.
Similar ideas
partial classes in C# appear similar, as the compiler has to collect all fragments based on name before the complete entity can be built. I am not sure whether the parts allow forward references to other parts and whether separate type checking is supported.
Pros
no need for changes beyond desugaring
handles non-shared aspects effortlessly (stable, var, system)
there is a single point of initialisation
Cons
can be possibly hacked up by some clever Makefile and perl manipulations
type checker needs to perform AST plumbing
environment needs to track AST of mixins
wonky way of capturing lexical environment in the mixing definition and injecting it into the mixin use
the interpreter needs to do its own work to obtain the AST of the mixin
Fixpoint formation over functions resulting in actor { … } type
Mixins are plain functions of type actor { <needed-bindings> } -> actor { <provided-bindings> }. These appear in the and <exp> portion where the final actor is weaved. When there are several mixins all <needed-bindings> must be joined and checked that they can be satisfied by <provided-bindings>. This is also rather ad-hoc.
Pros
writing mixins as functions is not at all unintuitive
parametrising mixins is just currying
Cons
unclear how to include stabilisation (which needs a central endpoint for names and types)
initialisation needs it own mechanism
needs ad-hoc changes to the type checker to find the fixpoint
endpoints from mixins are not manifest functions, possibly need IR-level trampolines to avoid context-switching delay
non-intuitive (asymmetrically typed) and
endpoints in mixins need realisation in the final actor (IR?)
Actor fragments of fractional type
This idea introduces actor { … } types with declared missing information (import fields). Similarly value fields can be declared with their resolution happening at actor build time.
The approach is fleshed out in #4385, and thus described here in a compressed version only.
Pros
semantically clean approach with some real theory behind it
no need for changes beyond desugaring
handles non-shared aspects effortlessly (stable, var, system)
Cons
unclear how to include stabilisation (which needs a central endpoint for names and types)
initialisation needs it own mechanism
considerable changes to the type system and checker
endpoints in mixins need realisation in the final actor (IR?)
heavier type algebra for objects
Open questions
Should imports be restricted to shared functions, functions, let-bound values or vars? This is important for recursion can lead to bottoms.
Common stumbling blocks
It is not an exaggeration to say that the compiler was not written with this type of mixin-modularity in mind. There are a few hacks in the compiler which should be rectified first, e.g. the wrapping of everyactor { <block> } in await (async _) by the parser.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Modularisation of Object Blocks
Which problem do we want to solve?
Currently Motoko has no means of splitting an actor into parts, so all methods of an
actor
(class
) must go into a monolithic source file. This is in conflict with established engineering practices of reusability and separation of concerns, thus introduces accidental complexity and duplication into Motoko projects.The users survey has [ref?] has also shown that this is perceived as an act problem across the community. A code review performed on a major DEX has encountered a humongous source file giving home to a monolithic actor.
Which approaches were considered?
In this section we'll give three concept ideas and try to flesh out their merits and drawbacks.
Syntactic concatenation
Extending the compiler with syntactic forms for
actor
fragments that are type-checked separately and anactor { … } and <exp>
construct that merges AST fragments to a finalactor
. Here the<exp>
part is a syntactic expression, which can be formally a call to a function, passing some parameters in, and results in an actor-typed fragment. But we are actually interested in the resulting AST, and not a value. This could be accomplished by recording all ASTs (of mixin aspect) that come from imports in a special environment and looking them up to obtain the expansion of the<exp>
part. Unfortunately this is quite an alien concept tomoc
, which was not designed with this type of metaprogramming in mind. All kinds of name shadowing issues can arise.Also, the lexical environment (with eventual values living in there) need to be transplanted from the mixin defining site to the mixin usage site. One could do this using an ad-hoc synthesised function (of record result type) and call that behind the scenes (in the
and
part), pattern matching the obtained record in each fragment method (as a preamble). This also means that we need an analysis of which bindings the mixin closes over.Similar ideas
partial
classes in C# appear similar, as the compiler has to collect all fragments based on name before the complete entity can be built. I am not sure whether the parts allow forward references to other parts and whether separate type checking is supported.Pros
stable
,var
,system
)Cons
Makefile
andperl
manipulationsFixpoint formation over functions resulting in
actor { … }
typeMixins are plain functions of type
actor { <needed-bindings> } -> actor { <provided-bindings> }
. These appear in theand <exp>
portion where the final actor is weaved. When there are several mixins all<needed-bindings>
must be joined and checked that they can be satisfied by<provided-bindings>
. This is also rather ad-hoc.Pros
Cons
func
tions, possibly need IR-level trampolines to avoid context-switching delayand
Actor fragments of fractional type
This idea introduces
actor { … }
types with declared missing information (import
fields). Similarly value fields can be declared with their resolution happening atactor
build time.The approach is fleshed out in #4385, and thus described here in a compressed version only.
Pros
stable
,var
,system
)Cons
Open questions
shared
functions, functions,let
-bound values orvars
? This is important for recursion can lead to bottoms.Common stumbling blocks
It is not an exaggeration to say that the compiler was not written with this type of mixin-modularity in mind. There are a few hacks in the compiler which should be rectified first, e.g. the wrapping of every
actor { <block> }
inawait (async _)
by the parser.Beta Was this translation helpful? Give feedback.
All reactions