I came up with a
derive macro that specializes
match as a refactoring tool for self-hosted programs. I built it on top of blossoms
I think it's fun to use because any position in expression context can reflect on its role in another expression, using all values available at runtime. Source code to be published in new Denxi edition I hope to have out by the end of the month.
For now, I'll just go over the current syntax and behavior of the macro.
(module+ test (derive derivative #:as (declassify derivative) #:taxa rule-position #:diff [`(derive derivative ,_ ... #:diff [,datum ,_] ,_ ...) (rule-position datum)] [(list-no-order (? rule-position? rp) _ ...) rp]))
derive call expresses all the s-exp level edits you'd like to make to the surrounding program if your text editor's cursor sat at the corresponding call site. This specific example is kind of neat because the first
match pattern under
#:diff is targeting itself. The result of the expression is the datum form of that pattern.
(quote (quasiquote (derive derivative (unquote _) ... #:diff [(unquote datum) (unquote _)] (unquote _) ...)))
(derive ...) is a copy of the entire surrounding source code as a list, refactored using a front-end to
match. The match clauses under
#:diff control what code is replaced with new values. The clauses match elements provided in child-to-parent order, such that the
(derive ...) expression itself is the first expression used in
match. Next, it's the
(module+ test ...), then any hypothetical traversal of proper or improper lists leading to the topmost enclosing S-expression. If no clauses match for an element at some level, then that element stays. All uses of
match occur during runtime. The
derive macro only serves to associate the call site with the runtime behavior.
Every element of an (im)proper list has its own child-to-parent traversal, so it is possible to distinguish
equal? values (like copies of
module forms) based on these traversals. This is a nice property, because it means that any lexically-equal
derive starts operating exactly on itself. This bleeds over into other benefits, like
module forms containing programs that can recognize their position in the whole module tree.
#:as (declassify derivative), and
#:taxa are all related. The first
derivative identifier binds the result of the final use of
match. The expression after
#:as uses that binding to compute the value of the whole
derive macro, kind of like how
#:result works in
declassify function is an accessor that returns the value encapsulated by the ad-hoc
#:taxa (we don't want a
rule-position classifier, we want the classified value). The
#:taxa identifiers help generate temporary constructors and predicates that classify values without ambiguity in match patterns.
This example works by having a
match pattern extract the datum form of itself to classify accordingly. Since I'm traversing towards parent values, the
list-no-order rule naturally propagates classified elements.
#:as returns the element itself, without the classifying wrapper. In effect, this replaces the whole program's source code with just that one