Note: I noticed this was posted to /r/programming but it's far from complete, it doesn't even have numbers or let/if/case special operators. It was just a Friday evening's whim to research whether quotation can be practically statically typed and what that might feel like. I'll have to implement at least case and let in order to deconstruct the quoted expressions and implement eval similar to Roots of Lisp before I can answer that to my satisfaction.
Including the reply I wrote in that thread:
As the author of this gist, static typing in general isn't the goal. There are a bunch of statically typed Lisps.
Shen has the type symbol for quoted things:
'+:symbol
My lisp has two types of quotation:
'+:: 'Symbol~+:: '(Int -> Int -> Int)
I'm not quite decided whether I want staged lambda-calculus, or this mixing style. What you end up with is a kind of non-opaque lambda:
(lambda (x) (+ x x))— this is opaque, you can't "look inside" this lambda. But it means the things inside can be statically typed.'(lambda (x) (+ x x))— this is transparent, you can look inside the lambda. It's just a tree. It's not typed, the whole thing is just "symbol" or "tree of symbols".~(lambda (x) (+ x x))— this is both transparent and statically typed. The+must exist, which it does, and have a type, which it does,Int -> Int -> Int, therefore the type of thexs must beInt. So the whole expression has type:'(Int -> Int). Note the', meaning "a quoted thing of type X".
So ~ ends up sitting somewhere in between a lambda and a quote.
I should be able to write:
(defmacro when (p body)
~(if ,p
,body
()))
And then the macro when will have type: 'Bool -> '() -> '(), if that makes sense. I cannot write, for example, (when 123 "go!"). This would be rejected before the macro is even expanded. A well-typed macro is interesting. Haskell doesn't have normal Lisp symbolic quotation, you can't just write 'foo, therefore I don't consider Haskell or its ML ancestors to be part of the Lisps family. It does have 'foo, it instead requires that foo exists as a name in scope. But it doesn't encode any type information, which is not very Haskelly either.
There's also the tricky problem of eval. In regular Lisp, eval throws exceptions. In even Shen this is true, because its quotation is not type-safe. Whereas if your eval is like in my little Lisp:
eval :: 'a -> a
Then the quoted symbol carries the type information and means that eval will at least never produce a type error.
Whether this works in practice I've yet to determine/decide. It's just an idea that interested me.
--
Basic unit type:
λ> replTy "()"
() :: ()Basic functions:
λ> replTy "(fn x x)"
(fn x x) :: b -> b
λ> replTy "((fn x ()) (fn x x))"
((fn x ()) (fn x x)) :: ()
λ> repl "((fn x x) (fn x x))"
((fn x x) (fn x x)) :: c -> c
(fn x x)Statically typed:
λ> repl "(() (fn x x))"
-- *** Exception: Couldn't match expected type: ()
-- against type: (b -> b) -> eStandard quotation:
λ> replTy "'()"
'() :: 'Symbol
λ> repl "'(foo bar)"
'(foo bar) :: 'Symbol
(foo bar)Statically typed quotation:
λ> replTy "~()"
~() :: '()All quotes have type 'a. Normal symbolic Lisp style is 'Symbol,
quoting anything else, e.g. for unit, is '(). Also things have to be
in scope:
λ> repl "~a"
-- *** Exception: Not in scope: `a'Quoted function:
λ> replTy "'(fn x x)"
'(fn x x) :: 'SymbolQuoted function:
λ> replTy "~(fn x x)"
~(fn x x) :: '(b -> b)Notice the type is '(b -> b).
There's an eval function:
λ> replTy "eval"
eval :: 'a -> aIt accepts a quoted a and returns the a that it represents. It
won't accept anything not quoted:
λ> replTy "(eval ())"
-- *** Exception: Couldn't match expected type: 'a
-- against type: ()If given a symbolic quoted expression, it will just return it as-is:
λ> repl "(eval '())"
(eval '()) :: Symbol
()
λ> repl "(eval '((fn x x) '()))"
(eval '((fn x x) '())) :: Symbol
((fn x x) ())Given a well-typed quoted expression it will run the code:
λ> repl "(eval ~((fn x x) '()))"
(eval ~((fn x x) '())) :: 'Symbol
()
λ> repl "(eval ~((fn x x) ()))"
(eval ~((fn x x) ())) :: ()
()
That's a very hard problem you're tackling here. Lisps can usually write self-interpreters, but typed self-interpreters are very hard (the usual conjecture is they don't exist for usual type systems, as a consequence of Gödel's theorems).* Providing
evalas a builtin is one of the ways out, but then it's less clear that you can achieve other metaprogramming tasks — writingevalin the language is a kind of benchmark for its metaprogramming power.*Should you want to read academic papers on how to do it, see papers at PLDI '09 (https://www.uni-marburg.de/fb12/ps/research/tsr?set_language=en) and POPL '15 ("Self-Representation in Girard's System U", not yet publicly available).