git @ Cat's Eye Technologies Robin / 4a37a56
Update "macro" to "fexpr" in tutorial and rationale document too. Chris Pressey 1 year, 5 months ago
2 changed file(s) with 51 addition(s) and 38 deletion(s). Raw diff Collapse all Expand all
9595 "the world's finest imperative language". I'm looking
9696 for the world's finest functional language though, right?)
9797
98 Macro as fundamental abstraction
98 Fexpr as fundamental abstraction
9999 --------------------------------
100100
101101 This is certainly the most unorthodox feature, the one that departs
105105
106106 It allows the language to have no "special forms" whatsoever.
107107 (Scheme would need at least `define-syntax` if it wanted to define
108 `if`, `set!`, and the other parts of its syntax, as macros.)
108 `if`, `set!`, and the other parts of its syntax, as fexprs.)
109109
110110 Whether having no special forms whatsoever is advantageous in any
111111 way, or not, remains to be seen.
112112
113113 One upshot is that any functionality expressible in the Robin
114 expression language, can be passed to a macro or a function, as
115 a parameter, or returned from a macro or function evaluation.
114 expression language, can be passed to a fexpr or macro as
115 a parameter, or returned from an operator application.
116116
117117 One also thinks it might make analysis of the code simpler — a
118118 parser or analyzer doesn't need to account for any special forms.
119119
120 But, in practice, since everything is a macro, `eval` is called a
120 But, in practice, since everything is a fexpr, `eval` is called a
121121 lot, and `eval` poses a significant problem for analysis.
122122
123123 But also in practice, an analysis tool will expect that the "small"
124124 library has been loaded, and that function calls will use `fun`
125125 as defined there, and thus can base their analysis on the semantics
126 of that macro without caring about its definition, or that its
126 of that fexpr without caring about its definition, or that its
127127 definition contains `eval`.
128128
129129 So the basic saving grace here is this: we can *define* the
130 forms of the language using macros without necessarily *implementing*
131 them using `macro`. As long as the implementation's behaviour
132 matches what the `macro` version specifies, it's compatible
130 forms of the language using fexprs without necessarily *implementing*
131 them using `fexpr`. As long as the implementation's behaviour
132 matches what the `fexpr` version specifies, it's compatible
133133 behaviour and thus an allowable implementation.
134134
135135 No variable numbers of parameters
221221 that (should) have a (relatively) fixed meaning in all Robin
222222 programs, whether they are used in any given program or not.
223223
224 * Note (that should be elsewhere?): most of the macros defined
224 * Note (that should be elsewhere?): most of the operators defined
225225 in `stdlib` are supposed to, intentionally, take a fixed number
226226 of arguments for some reason (nominally, to make some kind of
227227 future static analysis easier.)
348348 prepend
349349 list?
350350 symbol?
351 macro?
351 operator?
352352 number?
353353 equal?
354354 subtract
355355 sign
356 macro
356 fexpr
357357 eval
358358 if
359359 abort
132132
133133 (abort (inapplicable-object (abort (unbound-identifier fun))))
134134
135 You might conclude from this that [`fun`][] is not a built-in — and you'd
136 be right! Unlike almost every other Lisp-like language, in Robin,
137 `fun` is implemented as a macro. It can be imported from the standard
138 library.
135 You might conclude from this that [`fun`][] is not built-in to the
136 language — and you'd be right! Unlike almost every other Lisp-like
137 language, in Robin, `fun` is defined in the standard library, which
138 must be imported before it can be used.
139
140 It's defined as a so-called "fexpr" (which is like a macro but
141 even more general — more on them in a minute.)
139142
140143 As you saw above, you can ask the Robin reference interpreter to
141144 load in the standard library before it runs your program:
172175
173176 bin/robin --enable-builtins pkg/stdlib.robin fact.robin
174177
175 Macros
178 Fexprs
176179 ------
177180
178181 As we mentioned above, functions aren't intrinsic to Robin — the
179 `fun` operator that creates a function is defined as a macro in the
180 standard library.
182 `fun` operator that creates a function is defined as a so-called "fexpr"
183 in the standard library.
181184
182185 You're quite free to simply import the standard library and use `fun`
183 without knowing or caring that it's defined as a macro.
184
185 However, you can write your own macros as well, using [`macro`][].
186
187 The main difference between a function and a macro is that a macro
186 without knowing or caring that it's defined as a fexpr, whatever that
187 is.
188
189 However, you can write your own fexprs as well, using [`fexpr`][].
190
191 The main difference between a fexpr and a function is that a fexpr
188192 does *not* evaluate the arguments that are passed to it. It receives
189193 them as an unevaluated S-expression. What it does with this unevaluated
190194 S-expression is completely up to it.
193197 This is what the [`literal`][] operator in the standard library does,
194198 and this is how it's defined:
195199
196 (define literal (macro (args env)
200 (define literal (fexpr (args env)
197201 args))
198202
199203 With this definition in place you can run
206210
207211 So `literal` is essentially the same as `quote` in Lisp or Scheme.
208212 Except, of course, it's not intrinsic to the language. We wrote a
209 macro to do it instead.
210
211 A macro defined this way also has access to the environment in which
213 fexpr to do it instead.
214
215 A fexpr defined this way also has access to the environment in which
212216 it was called (the `env` parameter). There is also an intrinsic
213217 operator called [`eval`][] which evaluates a given S-expression in a
214 given environment. With these tools, we can write macros that *do*
218 given environment. With these tools, we can write fexprs that *do*
215219 evaluate their arguments, just like functions.
216220
217 (define id (macro (args env) (eval env (head args))))
221 (define id (fexprs (args env) (eval env (head args))))
218222 (display (id (subtract 80 4)))
219223
220224 If you run this you should see 76.
221225
222 This distinction between "functions" and "macros" is rather minor,
223 and often we don't care to distinguish between them, so we call them
224 all "operators".
225
226 ### Recursive macros
226 When we don't care to distinguish between "functions" and "fexprs"
227 and "macros" we just call them all "operators".
228
229 ### Recursive fexprs
227230
228231 (To be written).
232
233 Macros
234 ------
235
236 Just like a function, in Robin, is a kind of fexpr, so too is a
237 macro a kind of fexpr. A macro is a fexpr whose return value is
238 expected to be a piece of syntax which will be further evaluated
239 as a Robin expression.
240
241 (To be continued).
229242
230243 Referential transparency
231244 ------------------------
346359 Here is an example reactor.
347360
348361 (reactor (line-terminal) 0
349 (macro (args env)
362 (fexpr (args env)
350363 (bind-vals ((event-type event-payload) state)
351364 (if (equal? event-type (literal init))
352365 (list state
362375 that the state does not change and does not make any difference to the
363376 program, but an initial state still must be given.
364377
365 After that is the transducer, given as a `macro`. It receives, as its
378 After that is the transducer, given as a `fexpr`. It receives, as its
366379 arguments, an event, which consists of an event type and an event payload,
367380 and the current state. It extracts these.
368381
385398 [`head`]: ../stdlib/head.robin
386399 [`if`]: ../stdlib/if.robin
387400 [`list?`]: ../stdlib/list-p.robin
388 [`macro`]: ../stdlib/macro.robin
401 [`fexpr`]: ../stdlib/fexpr.robin
389402 [`number?`]: ../stdlib/number-p.robin
390403 [`operator?`]: ../stdlib/operator-p.robin
391404 [`prepend`]: ../stdlib/prepend.robin