Partial application and function composition
Metalang99 supports partial application -- a technique that allows applying arguments to functions separately, not all-at-once. Consider this:
#define F_IMPL(x, y, z) v(x + y + z)
#define F_ARITY 3
// 1 + 2 + 3
ML99_EVAL(ML99_appl(ML99_appl(ML99_appl(v(F), v(1)), v(2)), v(3)))
Alternatively, you can write ML99_appl2(ML99_appl(F, v(1)), v(2), v(3))
or ML99_appl3(F, v(1), v(2), v(3))
. It works as follows: first, F
is applied to v(1)
, then its arity specifier F_ARITY
(e.g., the number of parameters it accepts) is decremented; the same happens with v(2)
and v(3)
. The point here is that each application is already a valid Metalang99 term: you can pass it to higher-order metafunctions, which is quite handy with lists:
// 4 5 6
ML99_LIST_EVAL(ML99_listMap(ML99_appl(v(ML99_add), v(3)), ML99_list(v(1, 2, 3))))
ML99_appl(v(ML99_add), v(3))
is a term representing a function that accepts a single natural number and increments it by 3.ML99_listMap
maps each item in our list with this function.ML99_LIST_EVAL
evaluates the list and pastes all resulting items sequentially.
Without partial application, the situation becomes rather clumsy:
#define ADD_3_IMPL(x) ML99_add(v(x), v(3))
#define ADD_3_ARITY 1
// 4 5 6
ML99_LIST_EVAL(ML99_listMap(v(ADD_3), ML99_list(v(1, 2, 3))))
Partial application is especially useful when you want to capture an environment into a closure. With ML99_appl
, it becomes remarkably convenient:
#define F_IMPL(x, y) v(x##_##y)
#define F_ARITY 2
// abc_1 abc_2 abc_3
ML99_EVAL(ML99_variadicsForEach(ML99_appl(v(F), v(abc)), v(1, 2, 3)))
Read more about partial application here.
Another neat functional facility is function composition. Put simply, ML99_compose(f, g)
returns a new metafunction that is identical to first invoking g
with user-provided arguments, and then invoking f
with the result of invoking g
. Take a look at the following macro:
#define TRANSFORM(...) \
ML99_EVAL(ML99_variadicsForEach(ML99_compose(v(ML99_braced), v(ML99_untuple)), v(__VA_ARGS__)))
// { int x; }
// { const char *x, y; }
TRANSFORM((int x;), (const char *x, y;))
Here, ML99_braced
is composed with ML99_untuple
:
ML99_untuple
removes brackets from provided arguments:(1, 2, 3)
=>1, 2, 3
.ML99_braced
wraps arguments into curly braces:1, 2, 3
=>{ 1, 2, 3 }
.Overall,
ML99_compose(v(ML99_braced), v(ML99_untuple))
results in(1, 2, 3)
=>{ 1, 2, 3}
.
Then this function composition is supplied to ML99_variadicsForEach
to achieve the final result.
Partial application and function composition make metafunctions more reusable, which is the essence of functional programming. All higher-order metafunctions in the Metalang99 standard library use partial application under the hood, so you can pass to them both "raw" function-like macro identifiers such as v(F)
and partially applied functions as well. Any Metalang99-compliant macro with an arity specifier can be partially applied, and the operation is very cheap.
Last updated
Was this helpful?