5 ms·
> Zig’s comptime feature is most famous for what it can do: generics!, conditional compilation!, subtyping!, serialization!, ORM! That’s fascinating, but, to be
by karmakaze 2y ago
> Zig’s comptime feature is most famous for what it can do: generics!, conditional compilation!, subtyping!, serialization!, ORM! That’s fascinating, but, to be fair, there’s a bunch of languages with quite powerful compile time evaluation capabilities that can do equivalent things.
I'm curious what are these other languages that can do these things? I read HN regularly but don't recall them. Or maybe that's including things like Java's annotation processing which is so clunky that I wouldn't classify them to be equivalent.
- awestroke 2y agoRust, D, Nim, Crystal, Julia
- deleted 2y ago[deleted]
- elcritch 2y agoDefinitely, you can do most of those things in Nim without macros using templates and compile time stuff. It’s preferable to macros when possible. Julia has fantastic compile time abilities as well. It’s beautiful to implement an incredibly fast serde in like 10 lines without requiring other devs to annotate their packages. I wouldn’t include Rust on that list if we’re speaking of compile time and compile time type abilities. Last time I tried it Rust’s const expression system is pretty limited. Rust’s macro system likewise is also very weak. Primarily you can only get type info by directly passing the type definition to a macro, which is how derive and all work.
- tialaramex 2y agoRust has two macro systems, the proc macros are allowed to do absolutely whatever they please because they're actually executing in the compiler. Now, should they do anything they please? Definitely not, but they can. That's why there's a (serious) macro which runs your Python code, and a (joke, in the sense that you should never use it, not that it wouldn't work) macro which replaces your running compiler with a different one so that code which is otherwise invalid will compile anyway...
- int_19h 2y ago> Rust’s macro system likewise is also very weak. How so? Rust procedural macros operate on token stream level while being able to tap into the parser, so I struggle to think of what they can't do, aside from limitations on the syntax of the macro.
- Nullabillity 2y agoRust macros don't really understand the types involved. If you have a derive macro for #[derive(MyTrait)] struct Foo { bar: Bar, baz: Baz, } then your macro can see that it references Bar and Baz, but it can't know anything about how those types are defined. Usually, the way to get around it is to define some trait on both Bar and Baz, which your Foo struct depends on, but that still only gives you access to that information at runtime, not when evaluating your macro. Another case would be something like #[my_macro] fn do_stuff() -> Bar { let x = foo(); x.bar() } Your macro would be able to see that you call the functions foo() and Something::bar(), but it wouldn't have the context to know the type of x. And even if you did have the context to be able to see the scope, you probably still aren't going to reimplement rustc's type inference rules just for your one macro. Scala (for example) is different: any AST node is tagged with its corresponding type that you can just ask for, along with any context to expand on that (what fields does it have? does it implement this supertype? are there any relevant implicit conversions in scope?). There are both up- and downsides to that (personally, I do quite like the locality that Rust macros enforce, for example), but Rust macros are unquestionably weaker.
- elcritch 2y agoThanks, that’s exactly what I was referencing. In lisp the type doesn’t matter as much, just the structure, as maps or other dynamic pieces will be used. However in typed languages it matters a lot.
- forrestthewoods 2y agoRust macros are a mutant foreign language. A much much better system would be one that lets you write vanilla Rust code to manipulate either the token stream or the parsed AST.
- rurban 2y agoPerl BEGIN blocks
- tmtvl 2y agoPPR + keyword::declare (shame that Damien didn't actually call it keyword::keyword).
- foobazgt 2y agoYeah, I'm not a big fan of annotation processing either. It's simultaneously heavyweight and unwieldy, and yet doesn't do enough. You get all the annoyance of working with a full-blown AST, and none of the power that comes with being able to manipulate an AST. Annotations themselves are pretty great, and AFAIK, they are most widely used with reflection or bytecode rewriting instead. I get that the maintainers dislike macro-like capabilities, but the reality is that many of the nice libraries/facilities Java has (e.g. transparent spans), just aren't possible without AST-like modifications. So, the maintainers don't provide 1st class support for rewriting, and they hold their noses as popular libraries do it. Closely related, I'm pretty excited to muck with the new class file API that just went GA in 24 (https://openjdk.org/jeps/484 https://openjdk.org/jeps/484). I don't have experience with it yet, but I have high hopes.
- pron 2y agoJava's annotation processing is intentionally limited so that compiling with them cannot change the semantics of the Java language as defined by the Java Language Specification (JLS). Note that more intrusive changes -- including not only bytecode-rewriting agents, but also the use of those AST-modifying "libraries" (really, languages) -- require command-line flags that tell you that the semantics of code may be impacted by some other code that is identified in those flags. This is part of "integrity by default": https://openjdk.org/jeps/8305968 https://openjdk.org/jeps/8305968
- foobazgt 2y agoJust because something mucks with a program's AST doesn't mean that it's introducing a new "language". You wouldn't call using reflection, "creating a new language", either, and many of these libraries can be implemented either way. (Usually a choice between adding an additional build step, runtime overhead, and ease of implementation). It just really depends upon the details of the transform. The integrity by default JEPs are really about trying to reduce developers depending upon JDK/JRE implementation details, for example, sun.misc.Unsafe. From the JEP: "In short: The use of JDK-internal APIs caused serious migration issues, there was no practical mechanism that enabled robust security in the current landscape, and new requirements could not be met. Despite the value that the unsafe APIs offer to libraries, frameworks, and tools, the ongoing lack of integrity is untenable. Strong encapsulation and the restriction of the unsafe APIs — by default — are the solution." If you're dependent on something like ClassFileTransformer, -javaagent, or setAccessible, you'll just set a command-line flag. If you're not, it's because you're already doing this through other means like a custom ClassLoader or a build step.
- ephaeton 2y agowell, the lisp family of languages surely can do all of that, and more. Check out, for example, clojure's version of zig's dropped 'async'. It's a macro.