https://re.factorcode.org/2024/10/emit.html
Home Tags GitHub
[ ] [Go]
Re: Factor
Factor: the language, the theory, and the practice.
Emit
Sunday, October 13, 2024
One of the interesting aspects of a concatenative language like
Factor is that blocks of logic can be easily extracted and easily
reused since they apply logic to objects on the stack.
For example, if this was a word that operated on stack values:
: do-things ( a b -- c d )
[ sqrt * ] [ swap sqrt + ] 2bi ;
One change we could easily make is to extract and name the two pieces
of logic:
: calc-c ( a b -- c ) sqrt * ;
: calc-d ( a b -- d ) swap sqrt + ;
: do-things ( a b -- c d )
[ calc-c ] [ calc-d ] 2bi ;
We could also convert it to operate on local variables:
:: do-things ( a b -- c d )
a b sqrt * a sqrt b + ;
And extract those same two pieces of logic:
:: calc-c ( a b -- c ) a b sqrt * ;
:: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
a b calc-c a b calc-d ;
But, notice that we have to specify that the local variable a and b
have to be put back on the stack before we can call our extracted
words that make the computations.
Hypothetical Syntax
Today, someone on the Factor Discord server asked about this very
issue, wanting to have extractable pieces of logic that would
effectively be operating on nested local variables, wherever they are
used. Inspired by the goal of don't repeat yourself and the
convenience of extracting logic that operates on the data stack.
Specifically, they wanted to be able to take blocks of logic that
operate on named variables, and extract them in a similar manner to
the logic blocks that operate on the stack - offering this
hypothetical syntax as the goal:
EMIT: calc-c ( a b -- c ) a b sqrt * ;
EMIT: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
calc-c calc-d ;
Let's try and build real syntax that allows this hypothetical syntax
to work.
Building the Syntax
First, we make a tuple to hold a lazy variable binding:
TUPLE: lazy token ;
C: lazy
Then, we need a way to generate temporary syntax words in a similar
manner to temporary words:
: define-temp-syntax ( quot -- word )
[ gensym dup ] dip define-syntax ;
We create temporary syntax words to convert each named references to
lazy variables:
: make-lazy-vars ( names -- words )
[ dup '[ _ suffix! ] define-temp-syntax ] H{ } map>assoc ;
Given a quotation that we have parsed in an emit description, we can
build a word to replace all these lazy variables by looking them up
in the current vocabulary manifest:
: replace-lazy-vars ( quot -- quot' )
[ dup lazy? [ token>> parse-word ] when ] deep-map ;
And, finally, create our emit syntax word that parses a definition,
making lazy variables that are then replaced when the emit word is
called in the nested scope:
SYNTAX: EMIT:
scan-new-word scan-effect in>>
[ make-lazy-vars ] with-compilation-unit
[ parse-definition ] with-words
'[ _ replace-lazy-vars append! ] define-syntax ;
Using the Syntax
Now, let's go back to our original example:
EMIT: calc-c ( a b -- c ) a b sqrt * ;
EMIT: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
calc-c calc-d ;
Does it work?
IN: scratchpad 1 2 do-things
--- Data stack:
1.4142135623730951
3.0
Yep! That's kind of a neat thing to build.
I have added this syntax in the locals.lazy vocabulary, if you want
to try it out.
I'm not sure how useful it will be in general, but it is always fun
to build something new with Factor!
<< Base16 Themes Battlesnake >>
(c) John Benediktsson 2008-2024