September 2018, rev. October 2018
I was mistaken about this. Six months later I learned that waiting until runtime to determine datatypes seems necessary for indirect assignment without GC.
One of the more misleading mistakes in Lip was an attempt to make it more dynamic by determining at runtime if a symbol is a macro, function, array, hash, string, or symbol. Lip represented this with the instruction IR_MFA, short for macro, function, or array.
How would delaying evaluation of source code until runtime like this be useful in a program? In one hand letting everything get expanded and evaluated at runtime is more dynamic, which is something I want more of in a language. In the other, waiting until the last minute makes it hard to plan the IR generation: it slows down the language. Can the evaluation be prepared when a program is defined without loss of power and performance?
It looks like it can. Look at an array, hash, string, or symbol. While the same expression (a 1) is valid at runtime for all four of these types, the semantics are different for each one. The programmer knows if he's passing parameters to a function or passing a numeric index to get an element from an array. Similarly, it's not meaningful or very useful for (a 1 2 3) to be valid for array, hash, string, and symbol. In its simplest form it's valid only for a function or macro.
So it's ok to allow the language to know a symbol's datatype when the program is defined.
A language can be written with loose or confusing enough semantics that let everything turn into anything at runtime but it wouldn't be a language I'd want to program in. I don't want (a 1 2 3) to return three hash values or three array elements, because if it returns a list of three elements then this is not symmetric with (a 1) returning an atom. It makes me do more work when I'm writing a code rewriter, because I need to support both return types, list and atom. I don't want to have to do this extra work. But I'm ok with the programmer defining a macro or function outside the language core that does it.
If a language could be more dynamic when evaluating these six different types by delaying IR generation until runtime, it wouldn't lead to more powerful programs. The user still needs to know each symbol's type when writing the program.
I thought the main reason IR_MFA wouldn't work is that it's 34x slower in Lip. There's a simpler reason. IR_MFA seems unnecessary.