You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I still think we should add support for binding a local function of type (-> Syntax (Macro Syntax)) to an identifier, which a macro can then return as part of a syntax object.
In our previous discussions, we had two objections:
the captured variables are meaningless at phases other than the one they were captured in
syntax parameters cover most of the use cases
I have counter-arguments to both objections.
Variable capture
Identifiers already have a different meaning at different phases. Currently, code at level 0 can call let-syntax with a level 1 function, which captures level 1 variables, to bind it to a level 0 identifier my-macro. Then the level 0 body of let-syntax can call (my-macro) in order to run its body at level 1. Since the my-macro body now runs at level 1, it is fine that this body refers to the variables it captured at level 1.
-- level 0
#lang "prelude.kl"
(import (shift "prelude.kl"1))
(meta
-- level 1
(define enable-ten 1)) -- bound in the regular environment at level 1
-- level 0
(example
(let-syntax
[my-macro -- bound in the expander environment at level 0
(lambda (stx)
-- level 1
(if (= enable-ten 1) -- captures enable-ten
(pure '10)
(pure '5)))]
-- level 0
(+ 1 (my-macro)))) -- used at level 0, its body runs at level 1
-- returns 11
Similarly, I want code at level 1 to call, say, let-local-syntax with a level 1 function, which captures level 1 variables, to bind it to a level 0 identifier. This time, inner-macro-name is not that level 0 identifier, it is a level 1 identifier whose value is that level 0 identifier. Then the level 1 body of let-local-syntax can construct a syntax object which contains this level 0 identifier. When that code is spliced into level 0, the level 0 identifier is recognized as a macro, so it runs its body at level 1. Since this body now runs at level 1, it is fine that this body refers to the variables it captured at level 1.
#lang "prelude.kl"
(import (shift "prelude.kl"1))
-- level 0
(define-macro (outer-macro)
-- level 1
(let [enable-ten 1] -- bound in the regular environment at level 1, its value is 1
(let-local-syntax
[inner-macro-name -- bound in the regular environment at level 1, its value is 'inner-macro
(lambda (stx)
-- still level 1
(if (= enable-ten 1) -- captures enable-ten
(pure '10)
(pure '5)))]
(pure `(+ 1 (,inner-macro-name))))))
(example (outer-macro))
-- expands to
-- (+ 1 (inner-macro)) -- used at level 0, its body runs at level 1
-- returns 11
Use case
Currently, if I want a macro to cooperate with the type checker by indicating part of the type of the expression it wants to return, the definition of this macro must be divided into a main macro and an auxiliary macro:
#lang "prelude.kl"
(import"define-syntax-rule.kl")
(define-syntax-rule (aux-macro)
-- imagine a more complex macro which does something different depending on
-- whether A is Integer or String
(lambda (x) x))
(define-syntax-rule (main-macro)
(with-unknown-type [A]
(the (-> A A)
(aux-macro))))
(example (main-macro))
It would be nice to automate this work of splitting the definition of the macro:
(define-macro (main-macro)
(with-unknown-type [A]
(with-partially-known-type (-> A A)
-- imagine a more complex macro which does something different depending on
-- whether A is Integer or String
(pure '(lambda (x) x)))))
With let-local-syntax, the implementation of with-partially-known-type is easy:
And seamlessly allows local variables to remain in scope within the body of with-partially-known-type:
(define-macro (main-macro)
(let [enable-ten 1]
(with-unknown-type [A]
(with-partially-known-type (-> A A)
-- use enable-ten
...))))
Whereas without let-local-syntax, it is necessary for the implementer of main-macro to know that with-partially-known-type defines an intermediate macro under the hood, so that it can make enable-ten a syntax parameter instead of an ordinary local variable.
The text was updated successfully, but these errors were encountered:
I still think we should add support for binding a local function of type
(-> Syntax (Macro Syntax))
to an identifier, which a macro can then return as part of a syntax object.In our previous discussions, we had two objections:
I have counter-arguments to both objections.
Variable capture
Identifiers already have a different meaning at different phases. Currently, code at level 0 can call
let-syntax
with a level 1 function, which captures level 1 variables, to bind it to a level 0 identifiermy-macro
. Then the level 0 body oflet-syntax
can call(my-macro)
in order to run its body at level 1. Since themy-macro
body now runs at level 1, it is fine that this body refers to the variables it captured at level 1.Similarly, I want code at level 1 to call, say,
let-local-syntax
with a level 1 function, which captures level 1 variables, to bind it to a level 0 identifier. This time,inner-macro-name
is not that level 0 identifier, it is a level 1 identifier whose value is that level 0 identifier. Then the level 1 body oflet-local-syntax
can construct a syntax object which contains this level 0 identifier. When that code is spliced into level 0, the level 0 identifier is recognized as a macro, so it runs its body at level 1. Since this body now runs at level 1, it is fine that this body refers to the variables it captured at level 1.Use case
Currently, if I want a macro to cooperate with the type checker by indicating part of the type of the expression it wants to return, the definition of this macro must be divided into a main macro and an auxiliary macro:
It would be nice to automate this work of splitting the definition of the macro:
With
let-local-syntax
, the implementation ofwith-partially-known-type
is easy:(define-macro (with-partially-known-type tp body) (let-local-syntax [inner-macro-name (lambda (_stx) body)] (pure `(the tp (,inner-macro-name)))))
And seamlessly allows local variables to remain in scope within the body of
with-partially-known-type
:Whereas without
let-local-syntax
, it is necessary for the implementer ofmain-macro
to know thatwith-partially-known-type
defines an intermediate macro under the hood, so that it can makeenable-ten
a syntax parameter instead of an ordinary local variable.The text was updated successfully, but these errors were encountered: