9 ms·
The major innovation in this space is to make the compiler an interpreter which can evaluate the language at compile time (and emits code for runtime where it c
by mjburgess 3y ago
The major innovation in this space is to make the compiler an interpreter which can evaluate the language at compile time (and emits code for runtime where it cannot).
This makes brining in the "full power of dynamic languages" almost trivial and extremely performant.
Delete half of rust's symbols, get rid of its macro system, rewrite it until lifetimes are inferred /or/ allocators are explicitly chosen, etc. etc.
I don't want to feel visually assaulted when writing the type signature of the sort of function common in a dynamic language. Here `mypy` is also treasonously guilty.
Consider, eg., zigs "function which returns a type at compile-time" is an example of where blindingly-obvious syntax retains its blindingly-obviousness because of compile-time eval... ie., we dont need a "second syntax" to program the compiler.
This "two syntaxes, one for the runtime and one for the compiler" approach -- has swamped Rust as it aims for greater expressiveness. Not least, because it has a third syntax: one for unsafe.
- kaba0 3y agoYou quickly gets into the undecidable category once you to down that road, which might be fine if you either don’t care about safety, or have runtime checks for that, but none of those were an option for rust (or at least an option that would have made it remotely interesting).
- zozbot234 3y agoIt's undecidable in the general case, but it becomes doable in non-Turing-complete languages that are still expressive enough to be useful for many practical cases. (These languages can express unrestricted recursion as an I/O-like effect that's only available outside the language proper, as part of compiling to a binary.)
- kaba0 3y agoLifetimes only work because they are very restrictive - the moment you can’t decide that you end up with a non-safe language.
- mjburgess 3y agoSoundness, Completeness, Saftey -- I care about these far less than Safe, Sound, Complete 99.999% of the time; a joy to use 100% of the time
- kaba0 3y agoThen you are not the target audience of a low-level language that supposed to run without a runtime, but still safely. Don’t try to change the tool, when you could just choose a more fitting one.
- zozbot234 3y agoRust has an unsafe subset for that 0.001% of tne time you need it.
- steveklabnik 3y ago(Unsafe is a superset, not a subset, incidentally)
- duped 3y ago> This "two syntaxes, one for the runtime and one for the compiler" approach -- has swamped Rust as it aims for greater expressiveness. Not least, because it has a third syntax: one for unsafe. What are you referencing, concretely? For example, "unsafe" doesn't add or remove syntax.
- mjburgess 3y agoSplit the syntax into whether it 1) emits no code (compiler-facing); 2) emits weakly-related code (eg., macros, etc.); 3) emits code close to what's written abstractly (eg., value operations); 4) emits code close to what the machine needs (eg., unsafe code) -- etc. And you'll find about "4 languages" all mixed together.
- duped 3y agoI still have no idea what you're talking about. Can you give an example? Most "syntax" in AOT, statically typed languages does not directly generate machine code, but it does directly impact what machine code is generated. So there's not a clear distinction in practice. For example, a lot of the syntax is used to control method selection and verification - that's not a "different" syntax or language by most folks' definitions.
- mjburgess 3y agoCompare this with lisp, where there's one syntax -- compiler operations are "compile-time" interpretations of the very same syntax. Or below, let's invent a language where there's (in my sense) "one syntax for everything", Eg., consider something like, const SimpleTrait = trait with: val name def GenericApiTrait(type T) = return new trait with: def response : self -> str const MyAPI = GenericApiTrait(SimpleTrait(new class)) with: val name = "World" def response : self -> str = f"Hello $name!" # later on, in the app, def calc(x, y) : int, int -> int = return x + y # ie., the **same** syntax as that earlier which targets compile-time Here you can see that code with ordinary run-time semantics is used for the compile-time operations of generating a trait and specifying that a class implements that trait (ie., we call the trait-defining function on the class). Whereas in Rust, the syntax for returning values and "quantifying over types" is radically different. This is the historical approach (C++, C# .. almost all langs) -- but not one modern innovative langs follow. I think with as much desire for novelty rust has, cramming it all into its "compile-time syntax" has hobbled it. A simple uniform language for both c-time and r-time could be used
- awestroke 3y ago> Delete half of rust's symbols, get rid of its macro system, rewrite it until lifetimes are inferred /or/ allocators are explicitly chosen, etc. etc. Feel free to do this. I predict you will end up with a language that appeals to nobody but yourself
- mjburgess 3y agoWell, I'm more-or-less describing Nim. I imagine it has more users than just me. My version would, of course, be Nim++
- avgcorrection 3y ago> The major innovation in this space is to make the compiler an interpreter which can evaluate the language at compile time (and emits code for runtime where it cannot). Show us an extant language that does this, then. Partial Evaluation (automatic) was a very unsolved problem, last I looked.