> it makes it nearly impossible to compile the language
You would honestly be surprised what little difference it makes. You can just go through the source code as you compile it and compile instances of function-like objects with the compiled version. If you imagine a data-type for lists:
class List { Object car; Object cdr; };
class Compiled_List inherits List { Object (*call)(List args); }
Then you would have function calls compile to:
// compiled value of (F . ARGS)
if (F is Compiled_List)
return F->call(ARGS);
else if (F is List)
// interpret F
This is no slower than the compiled version of a regular function call in any other lisp, since there is also a need to type-check the function there. The real issue is the lack of numbers if you are interested in speed.
That doesn't work because there are only four primitive types: symbols, pairs, characters, and streams. You can't add more. Bel is supposed to be a specification, not an implementation.
So pairs are the only compound data type. That is all you have to build more complex stuff out of. Even lists are puns on pairs. So there is no place to hide any adjunct data without violating the spec. This is one of the many reasons that an operational spec is a bad idea.
I am as big a fan of Lisp as you can hope to find (just look at my user name) but PG's infatuation with McCarthy's original definition of EVAL is badly misguided. McCarthy's EVAL was NOT a formal spec of anything, it was an illustration that CONS, CAR, CDR, QUOTE and COND were a really good choice of computational primitives because you could write EVAL in a few dozen lines rather than the pages and pages required to do the same thing for a Turing machine.
> That is all you have to build more complex stuff out of
By this logic you can't add type information to anything because you can only use pairs so there is nowhere to store the type. It's a silly thing to say because it assumes the compiler for the language has to be written in the language.
> So there is no place to hide any adjunct data without violating the spec
The spec is that you have cons, car, and cdr. You could have a secret third element to some pairs that is only used by the compiler without changing the interface of those functions, and hence without changing the interface of the language as a whole. This is the essence of optimising compilers, making changes to the representation of things without changing how they behave.
> PG's infatuation with McCarthy's original definition of EVAL is badly misguided
I agree wholeheartedly with this. This entire document is basically just an extended brain-fart but because it came out of a rich guy's brain-ass a bunch of people will treat it like it's serious.
> It's a silly thing to say because it assumes the compiler for the language has to be written in the language.
It assumes no such thing. All it assumes is that the compiler has to conform to the specification.
> The spec is that you have cons, car, and cdr.
And XAR and XDR. And threads. And no compound data types other than mutable pairs.
> You could have a secret third element to some pairs that is only used by the compiler without changing the interface of those functions, and hence without changing the interface of the language as a whole.
That's right. You could. How would that help?
> This entire document is basically just an extended brain-fart
OK, so we're basically in violent agreement here. We're just quibbling over the justification.
I literally just described in code how that could help. Wherever the compiler encounters an instance of a list in the source code that looks like a function, you compile it and put the result in a secret third element. Then, when you have to apply a list, you first check if it has a compiled third element before you do the slower work of interpreting it. Have you ever written a compiler or used one? What I described here is literally just the process of JIT compilation.
That might work if Bel didn't have threads. But it does, so it doesn't.
Also, assigning the body is not the only way to undermine compilation. You can change a function into a non-function (by changing its first or second element) and vice versa. You can change a function's environment. You can change its argument list. You can modify the body of a function while it is being evaluated. You can change anything about a function at any time.
You can still do escape analysis. You wouldn't even need to be very advanced with it since most code won't do anything close to modifying and expression that's in the process of evaluating.
You clearly don't know what escape analysis is or how it works. Escape analysis applies only to data allocated at run-time. Programs are generally written before they are run (this is the reason that "run-time" is even a thing) and the functions that comprise them in a Bel program are generally globally bound with DEF. Escape analysis doesn't work on globally bound data. It has already escaped before your program even starts to run.
You can look at where a name is used, I say "escape analysis" here because that is the technique closest to what you would be doing. It gets the idea across quite well. At the fundamental level, you are analysing a program to see which parts of it access which values. In this case assessing if a piece code bound to a global variable "escapes" into the rest of the program as data. The objectives and techniques are similar. This is quite clearly possible.
"In compiler optimization, escape analysis is a method for determining the dynamic scope of pointers..."
In the case of global bindings, there is nothing to analyze. Global bindings do not have dynamic scope, they have global scope. (Duh.)
> This is quite clearly possible.
OK, you believe what you want. But I'll bet you $100 that you can't even write a program that reliably determines whether or not a list is a valid Bel function, let alone one that does "escape analysis" on an entire Bel code base (whatever you think that might actually mean).
Here, I'll get you started:
(def is-a-function? (l)
(and (id (car l) 'lit)
(id (cadr l) 'clo)
...
What do you mean by this? Can valid functions produce errors? Does a valid function need to halt?
> In the case of global bindings, there is nothing to analyze
A global name will be used in the program text a finite number of times. You can treat each usage as an the creation of a new value and do escape analysis on it. Doing this on all usages of a variable can determine that the none of these values escape and therefore the value cannot be passed to a list mutating function and is safe to optimise.
That's not an explanation, and quite frankly it's entirely unrelated. You can quite easily guarantee a top-level variable isn't initially aliased by looking at the form that assigned it. In most cases this will be a literal which is a fresh, unaliased value. If the value becomes aliased to more than one name, that means one instance of it escaped, so you will detect that when doing escape analysis. It's really quite simple logic.
> Whatever the spec says.
Well the document does mention a check for validity.
(def variable? (v)
(if (atom v)
(no (literal v))
(caris v vmark)))
(def valid-special-param? ((t-or-o (o var) (o expr) . rest))
(and (valid-params? var)
;; t parameters must have a type check.
(or (id t-or-o o) expr)
(no rest)))
(def valid-params? (p)
(if (no p) t
(variable? p) t
(or (caris p t) (caris p o)) (valid-special-param? p)
(and (id (type p) 'pair)
(valid-params? (car p))
(valid-params? (cdr p)))))
(def valid-top-params? (p)
(and (no (caris p o)) (valid-params? p)))
(def valid-function? ((o lit) (o clo) (o env) (o params) (o body) . rest)
(and (id lit 'lit)
(id clo 'clo)
;; Env, a proper list of pairs of symbols and bindings.
(and (proper env)
(all [id (type _) 'pair] env)
(all variable? (map car env)))
(valid-top-params? params)))
I think this should work, but I don't think it's worth $100.
You would honestly be surprised what little difference it makes. You can just go through the source code as you compile it and compile instances of function-like objects with the compiled version. If you imagine a data-type for lists:
Then you would have function calls compile to: This is no slower than the compiled version of a regular function call in any other lisp, since there is also a need to type-check the function there. The real issue is the lack of numbers if you are interested in speed.