The main reason not to do it that way is the "you're on your own" nature of dynamism, in and of itself. If you don't come up with a good structure for checking things, the language isn't going to do it for you. And this is fine within the context of the quick hack or the home-grown type system, but less useful for proving broad, general properties about the code. Particularly when this is an industry where the majority of programmers are assumed to be hugely inexperienced, with less than 5 years under their belt, and trusted to build fairly complex systems.
Classical static type systems(your Pascal, Java, etc.) only really know the world in terms of combinations of primitives; they don't prove much beyond simple matching of input to output. But that's yesterday's approach - it's not what industry is looking towards.
What the newer static languages(of the Haskell and Rust ilk) have converged upon is to bolt a powerful constraint solver engine to the type-checking system, which in turn shapes the whole language around pleasing the type-checker. The resulting minimum code quality is often higher, but also semantically torturous.
One really nice use of this is printf. In C, printf needs a remarkably large amount of code and it’s quite slow. In zig, printf marks the format string as comptime. Then the compiler runs the code looping over and evaluating the format string, then unrolls the result into the binary. The output ends up being a few terse calls to write / format with no extra machinery at runtime. And you get clean compile errors if the format string doesn’t match your arguments - all with no special casing in the compiler.
More detail, with code: https://ziglang.org/documentation/master/#Case-Study-printf-...
I think nim has something similar.
I’m surprised rust went with macros instead of something like comptime - though I assume there’s some trade offs I’m not aware of.
Rust has procedural macros which are Rust libraries built and run compile time that can transform raw tokens into valid Rust code (or more raw tokens for nested macros). Procedural macros can annotate any statement or code block - although some specific ones might still be on nightly only.
[0] https://github.com/rust-lang/rust/issues/54726
[1] https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver...
As for the format string at comptime, it's a big improvement over C sure (no missing parameter, yes!) but I still feel that it's still much less readable than Python f-strings.
This is really, really powerful.
Even with unsafe C++, I’m used to writing very high level, high performance concurrent code. If it crashes after it’s passed CI testing for a week or so, it’s probably a hardware bug.
(Of course, there are exceptions, but those fall outside of the parts I have the compiler check for me.)
An example: this function is only valid on positive, odd integers in the range 3-91. With any static type system, this has to be a runtime check anyway.
I’m not an expert on dependent types, but that sounds like what those try and solve. But at that point, you just want compile time to be a full program, which has all of the same pitfalls as the runtime of a program.
D has fairly permissive CTFE which uses the language itself; it remains statically typed.
Haskell is statically typed while the type system can be made Turing complete by using certain language extensions.
The Terra language (embedded in Lua) is statically typed and is metaprogrammed using Lua.
Racket is certainly dynamic, but Typed Racket is ... well it's still dynamic, but type annotations are statically checked.