Skip to content

Functional MX

Kenneth Tilton edited this page Jul 4, 2023 · 3 revisions

A new branch, functional-mx, holds the product of eleven days of refactoring. The goal was to make f/mx widget factories such as material-app and scaffold into functions instead of macros. The original reason for this was that the macros expand into quite a bit of code for each widget coded.

A benefit, we noticed, was that the two maps accepted by widgets, one for native Flutter attributes, one for custom application attributes, no longer needed to be literal maps. We could then have a custom widget which could be tailored by other widgets, which would merge their inputs with a custom widget's. In a word, widget composition. This was never anything we aspired to, but it seemed a natural win.

Why the macros in the first place?

In the past, macros had seemed necessary. Matrix children must be created knowing their parents, because MX make brings a model to life before returning. This so-called "awakening" entails forcing the evaluation of all formulaic cells, and these may need to navigate to other models to get the information they need. Navigation often passes through the parent. But if we build a Flutter component with (center (text "Hi, Mom.")), the natural evaluation order would try to make the text before making the center.

Fortunately, macros let us expand the above snippet into:

(make 'center
   :kids (cFkids (make 'text 
                    :content (cF "Hi, Mom."))))

Now the manufacture of the text widget is deferred until the center widget has been created, because cFkids itself defers the creation of the children. When those children are created, the parent center can be passed to the text constructor. If the text needed to navigate, it could: (text (mget (fmu :counter) :value)).

The overarching goal is to eliminate boilerplate, the noise that slows down coding and makes code harder to understand and modify.

Plan B: deferred widgets

It occurred to us that all factories such as scaffold and center could be more like Cells. They could be functions that defer their product by returning "factory" functions which would be called JIT to actually create a widget. These functions would not run until the parent existed, and would be passed the parent as their only parameter. (Other effective parameters would come from lexical scope and by navigating to the Matrix for additional state, as usual.)

Results

The branch functional-mx holds the result of that effort. In brief:

  • The line count of apps is 20% or so of what it was with macros;
  • The resulting binary size of release builds was marginally different;
  • It became necessary, in cases where a code built some widgets conditionally, it was necessary to add (cFkids...) as a wrapper;
  • Text widgets especially often required an (as-content....) wrapper where the text was computed; and
  • Some extra type hinting became necessary because inferencing cues were lost.

Conclusion

For now, functional-mx remains a possibility, but the loss of transparency seemed greater than the sum of the supporting source changes described. The binding of me, a vital MX cornerstone, became less certain. MX suddenly felt unintuitive.

At the same time, the exercise showed us how to reduce the macro code bloat. We will explore that starting next, and expect to achieve much of the savings of functional widgets.

That said, functional-mx was close enough. We will keep it in mind, and perhaps find a role fore it, or even adopt it altogether some day.