3 ms·
Agreed. The strong agree is that Rust's type system---and many other languages' type systems---is a parallel language to its runtime language. Parameterized typ
by justinpombrio 3y ago
Agreed. The strong agree is that Rust's type system---and many other languages' type systems---is a parallel language to its runtime language. Parameterized types are like functions; instantiating a type parameter is like a function call; `impl ... where` does pattern matching. You get a pure functional language based on pattern matching and implemented with memoization. These type systems are often Turing complete on purpose, but yet it's surprisingly hard to get an infinite loop. Like, both Rust's and Java's type systems are Turing complete, yet I've never hit an infinite compilation loop in either by accident.
For more on type systems as programming languages: https://ductile.systems/oxidizing-the-technical-interview/ https://ductile.systems/oxidizing-the-technical-interview/ plus its links at the top.
What I would like to see is a programming language where the runtime language and the comptime language are the same, or nearly the same, and where the comptime language is type safe. Zig isn't this: its `comptime` language is essentially dynamically typed. In Zig, if a function `f` takes a `comptime` argument `t: Type`, it can call `.print()` on it, and the compiler will just assume that that's fine. If you call `f`, you had better read the docs because the type system won't tell you that `t` needs to have an `print()` method. If you're calling `f` directly, that's pretty straightforward, but the trouble comes when you actually call `h`, and `h` looks at the value of some string and based on that decides to call `g`, and `g` calls `f`, and the docs for `h` weren't entirely clear, so now you're seeing an error in `h`, which you didn't even call. Instead, if `f` is going to call `.print()` on a `t`, then its argument `t` can't just be a `Type`, the compiler should check that it's a `Type with method .print()->String`. This requirement would then flow to `g` and `h`, so the type signature for `h` is guaranteed to tell you that `t` is required to have an `print()` method.
For more on merging runtime and comptime languages in a type safe way, see 1ML: https://people.mpi-sws.org/~rossberg/1ml/ https://people.mpi-sws.org/~rossberg/1ml/
EDIT: Deleting my criticism of C++ templates lest it distract from the more substantial things I had to say above.
- gpderetta 3y ago> What I would like to see is a programming language where the runtime language and the comptime language are the same, or nearly the same, and where the comptime language is type safe I have aproximately 0 knowledge of it, but I think TemplateHaskell should do that.
- justinpombrio 3y agoNope. Haskell's type system guarantees that code you construct in Template Haskell is well-formed, but the code you construct is only type checked once, at the end. So if you have a function that constructs code, and the code it constructs has a type error in it, you won't find out unless you call the function. Just like Zig.
- avgcorrection 3y ago> What I would like to see is a programming language where the runtime language and the comptime language are the same, or nearly the same, and where the comptime language is type safe. MetaOCaml might the closest one.
- zozbot234 3y ago> and where the comptime language is type safe You need dependent types in order to do this, which means doing away with Turing-completeness. (Moreover, the principle 'Type is of type Type' as found in Zig comptime leads to type-unsafety. So you need to replace that with some notion of universes.)
- justinpombrio 3y agoWell, you can have dependent types but limit their expressiveness. Rust, for example, has dependent (comptime) types, but they're very extremely limited: fn foo<const N: usize>() -> [f32; N] I imagine having arbitrary comptime code, but more limited use of comptime values in type position. Also, do you know the exact issue with "Type is of type Type"? I know that can lead to _non-termination_, and non-termination completely breaks proof assistants. For example, you can prove `False` with: fn make_false() -> False { return make_false(); } But if you're not building a proof assistant, a function like `make_false()` is fine. Does it lead to any additional problems?