Template PureScript
reverofevil opened this issue · 4 comments
Pros:
- Allows a lot of powerful techniques needed to work with data (type providers, XML, JSON and SQL interop)
deriving
directives are the part of the library, one could define some morederiving
s (for example,Check
to generateisNothing
,isJusy
etc.)
Cons:
- Typecheck gets trickier
- Needs update to module system
Tutorial for macros in Scala: http://docs.scala-lang.org/overviews/macros/overview.html
As a first step, what about a built-in primitive like:
reify :: a -> (AST -> b) -> b
An example:
f = reify (if 1 == 1 then True else False) print
Basically, reify
inlines the AST of the first argument into the second argument. Some issues/questions:
- The reified AST needs to be complete, i.e. free variables need to be recursively inlined. As an alternative, something like
a -> (Tuple AST (Map Var AST) -> b) -> b
may be good enough, whereMap Var AST
is the environment of all bindings in scope. - Can this be implemented as a simple desugaring step? Should be doable, except when the AST references something from another module. Are all imported modules available when desugaring a module?
The most obvious use case for something like this is sending code over the wire, Erlang-style. Right now this is only possible with some sort of restricted custom DSLs (+ free monads etc), where things like if
or case
need to be encoded as combinators, losing things like exhaustivity checking in the process.
So I thought about this a little bit more.
First of all, the type of reify
can actually be simplified to forall a. a -> AST
.
So when an expression is reified, simply replace reify a
with the serialised AST of a
. If there are any free variables in a, recursively generate ASTs for those. If a free variable references a Declaration
from another module, first resolve that and then continue as above (is this possible with the current module system?)
Furthermore, taint all binders referenced by free variables in a
as "reified" and replace their type with something equivalent to (AST, x)
for any binder x
. When a dependent module calls a function with a tainted argument, reify it and pass the AST along the original value.
I.e:
M1:
value :: Int
value = 1
M2:
f :: (Int -> Int) -> AST
f g = reify (g M1.value)
M3:
test :: Int
test = M2.f (\x -> x * 2)
Now, there are two free variables in the expression passed to reify
in M2.f
: g
and M1.value
. M1.value
is a declaration in M1
, so just generate its AST normally. g
references a binder, so taint it (using some simplified AST notation here):
f :: (AST, Int -> Int) -> AST
1: f (gAST, g) = gAST `Abs` reify M1.value
2: f (gAST, g) = gAST `Abs` Lit 1
Now, M3.test
calls M2.f
. Its first argument is tainted however, so pass along its AST:
test :: Int
1: test = f (reify (\x -> x * 2), \x -> x * 2)
Now continue the process as above, recursively. Since there are no free variables in \x -> x * 2
, just generate its AST:
2: test = f (Var x `Abs` (Var x `App` Op "*" `App` Lit 2), \x -> x * 2)
I don't think the type checker needs to be modified for that, because the tainted flag just tells every call site to replace exp
with (reify exp, exp)
. That should be enough.
Why this is closed?