"Checking at runtime* is exactly the problem. Why would checking at runtime be more reliable than compile time?
"Checking at runtime* is exactly the problem. Why would checking at runtime be more reliable than compile time?
If you use metaprogramming (i.e. Lisp macros, Scheme/Racket macros, Clojure macros), then the only way to have truly reliable checks is at run time.
In fact, for debugging and testing, it's much better to have a runtime able to do many things (such as live patching of the code).
Contrary to a language like C or C++, where the compiler produces machine language from the source and then goes out of the picture, in most of the Lisp family languages your program runs along with the runtime; this runtime also contains the compiler itself and is usually big on features; it will not just catch the type errors, it will show you exactly all kind of important information about any, error and will allow you to correct the error if you like, without having to recompile and start again.
I use C#. When you create a Linq expression, you get type safety and it "compiles to" an expression tree that is converted to its runnable form at runtime -- in my case a Sql statement, a MongoQuery, or C# code. It really comes in handy. Theoretically I could change my backing Store from Mongo/C# driver to Sql Server/EF without changing any of the queries. I do this all of the time when unit testing, I substitute an in memory list for a database table and still get complete type safety.
That is a misconception. The macros output a target language and that can be checked. Plus macros can do their own checking before that. A macro expander can perform some static checks. In TXR Lisp, warnings about unbound functions and variables come from the macro-expanding code walk.