Live data from Hacker News

Things Zig comptime won't do

matklad.github.io

201–210 of 252 posts

Re: Things Zig comptime won't do

#201

Earlier quoted context omitted.

Well, if you strip away the curly braces and return statement, that's just a regular type definition. Modeling generic types as functions from types to types is just System F, which goes back to 1975. Turing-complete type-level programming is common in tons of languages, from TypeScript to Scala to Haskell. I think the innovation here is imperative type-level programming--languages that support type-level programming…

The thing is, this is not type-level programming, this is term-level programming. That there's no separate language of types is the feature. Functional/imperative is orthogonal. You can imagine functional Zig which writes Pair :: type -> type -> type let Pair a b = product a b This is one half of the innovation, dependent-types lite. The second half is how every other major feature is expressed _directly_ via comptim…

> This is one half of the innovation, dependent-types lite.

But that's not dependent types. Dependent types are types that depend on values. If all the arguments to a function are either types or values, then you don't have dependent types: you have kind polymorphism, as implemented for example in GHC extensions [1].

> The second half is how every other major feature is expressed _directly_ via comptime/partial evaluation, not even syntax sugar is necessary. Generic, macros, and conditional compilation are the three big ones.

I'd argue that not having syntactic sugar is pretty minor, but reasonable people can differ I suppose.

[1]: https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/poly...

Re: Things Zig comptime won't do

#202
post #194

Earlier quoted context omitted.

> But I'm not seeing how comptime is accomplishing that. Because Zig does the work of C++'s templates, macros, conditional compilation, constexprs, and concepts with one relatively simple feature.

From the article: fn print(comptime T: type, value: T) void { That's a template. In D it looks like: void print(T)(T value) { which is also a template.

I think another way to put it is that the fact that Zig reuses the keyword "comptime" to denote type-level parameters and to denote compile-time evaluation doesn't mean that there's only one feature. There are still two features (templates and CTFE), just two features that happen to use the same keyword.

Re: Things Zig comptime won't do

#203

Earlier quoted context omitted.

From the article: fn print(comptime T: type, value: T) void { That's a template. In D it looks like: void print(T)(T value) { which is also a template.

I think another way to put it is that the fact that Zig reuses the keyword "comptime" to denote type-level parameters and to denote compile-time evaluation doesn't mean that there's only one feature. There are still two features (templates and CTFE), just two features that happen to use the same keyword.

Maybe you can insist that these are two features (although I disagree), but calling one of them templates really misses the mark. That's because, at least in C++, templates have their own template-level language (of "metafunctions"), whereas that's not the case in Zig. E.g. that C++'s `std::enable_if` is just the regular `if` in Zig makes all the difference (and also shows why there may not really be two features here, only one).

Re: Things Zig comptime won't do

#204

Earlier quoted context omitted.

Rust prevents classes of bugs by preventing specific patterns. This means it rejects, by definition alone, bug-free code because that bug free code uses a pattern that is not acceptable. IOW, while Rust rejects code with bugs, it also rejects code without bugs. It's part of the deal when choosing Rust, and people who choose Rust know this upfront and are okay with it.

> This means it rejects, by definition alone, bug-free code because that bug free code uses a pattern that is not acceptable. That is not true by definition alone. It is only true if you add the corollary that the patterns which rustc prevents are sometimes bug-free code.

> That is not true by definition alone. It is only true if you add the corollary that the patterns which rustc prevents are sometimes bug-free code.

That corollary is only required in the cases that a pattern is unable to produce bug-free code.

In practice, there isn't a pattern that reliably, 100% of the time and deterministically produces a bug.

Re: Things Zig comptime won't do

#205

Earlier quoted context omitted.

Rust 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.

...? Proc macros _are_ vanilla Rust code written to manipulate a token stream.

You’re right. I should have said I want vanilla Rust code for vanilla macros and I want to manipulate the AST not token streams.

Token manipulation code is frequently full of syn! macro hell. So even token manipulation is only kind of normal Rust code.

Re: Things Zig comptime won't do

#206

Earlier quoted context omitted.

The thing is, this is not type-level programming, this is term-level programming. That there's no separate language of types is the feature. Functional/imperative is orthogonal. You can imagine functional Zig which writes Pair :: type -> type -> type let Pair a b = product a b This is one half of the innovation, dependent-types lite. The second half is how every other major feature is expressed _directly_ via comptim…

> This is one half of the innovation, dependent-types lite. But that's not dependent types. Dependent types are types that depend on values. If all the arguments to a function are either types or values, then you don't have dependent types: you have kind polymorphism, as implemented for example in GHC extensions [1]. > The second half is how every other major feature is expressed _directly_ via comptime/partial evalu…

> Dependent types are types that depend on values.

Like this?

    fn f(comptime x: bool) if (x) u32 else bool {
        return if (x) 0 else false;
    }

Re: Things Zig comptime won't do

#207
post #189

Earlier quoted context omitted.

I'm sorry, but not being able to see that a design that uses a touchscreen to eliminate the keyboard is novel despite the touchscreen itself having been used elsewhere alongside a keyboard, shows a misunderstanding of what design is. Show me the language that used a general purpose compile-time mechanisms to avoid specialised features such as generics/templates, interfaces/typeclasses, macros, and conditional compila…

But Zig's comptime only approximates the features you mentioned; it doesn't fully implement them. Which is what the original article is saying. To use your analogy, using a touchscreen to eliminate a keyboard isn't very impressive if your touchscreen keyboard is missing keys. If you say that incomplete implementations count, then I could argue that the C preprocessor subsumes generics/templates, interfaces/typeclasse…

> But Zig's comptime only approximates the features you mentioned; it doesn't fully implement them

That's like saying that a touchscreen device without a keyboard only approximates a keyboard but doesn't fully implement one. The important thing is that the feature performs the duty of those other features.

> If you say that incomplete implementations count, then I could argue that the C preprocessor subsumes generics/templates, interfaces/typeclasses†, macros, and conditional compilation.

There are two problems with this, even if we assumed that the power of C's preprocessor is completely equivalent to Zig's comptime:

First, C's preprocessor is a distinct meta-language; one major point of Zig's comptime is that the metalanguage is the same language as the object language.

Second, it's unsurprising that macros -- whether they're more sophisticated or less -- can do the role of all those other features. As I wrote in my original comment (https://news.ycombinator.com/item?id=43745438) one of the exciting things about Zig is that a feature that isn't macros (and is strictly weaker than macros, as it's referentially transparent) can replace them for the most part, while enjoying a greater ease of understanding.

I remember that one of my first impressions of Zig was that it evoked the magic of Lisp (at least that was my gut feeling), but in a completely different way, one that doesn't involve AST manipulation, and doesn't suffer from many of the problems that make List macros problematic (i.e. creating DSLs with their own rules). I'm not saying it may not have other problems, but that is very novel.

I hadn't seen any such fresh designs in well over a decade. Now, it could be that I simply don't know enough languages, but you also haven't named other languages that work on this design principle, so I think my excitement was warranted. I'll let you know if I think that's not only a fresh and exciting design but also a good one in ten years.

BTW, I have no problem with you finding Zig's comptime unappealing to your tastes or even believing it suffers from fundamental issues that may prove problematic in practice (although personally I think that, when considering both pros and cons of this design versus the alternatives, there's some promise here). I just don't understand how you can say that the design isn't novel while not naming one other language with a similar core design: a mechanism for partial evaluation of the object language (with access to additional reflective operations) that replace those other features I mentioned (by performing their duty, if not exactly their mode of operation).

For example, I've looked at Terra, but it makes a distinction between the meta language and the object (or "runtime") language.

Re: Things Zig comptime won't do

#208

Earlier quoted context omitted.

From the article: fn print(comptime T: type, value: T) void { That's a template. In D it looks like: void print(T)(T value) { which is also a template.

I think another way to put it is that the fact that Zig reuses the keyword "comptime" to denote type-level parameters and to denote compile-time evaluation doesn't mean that there's only one feature. There are still two features (templates and CTFE), just two features that happen to use the same keyword.

They are the same thing though. Conceptually there's a partial evaluation pass whose goal is to eliminate all the comptimes by lowering them to regular runtime values. The apparent different "features" just arise from its operation on the different kinds of program constructs. To eliminate a expression, it evaluates the expression and replaces it with its value. To eliminate a loop, it unrolls it. To eliminate a call to a function with comptime arguments, it generates a specialized function for those arguments and replaces it with a call to the specialized function.

Re: Things Zig comptime won't do

#209
post #203

Earlier quoted context omitted.

I think another way to put it is that the fact that Zig reuses the keyword "comptime" to denote type-level parameters and to denote compile-time evaluation doesn't mean that there's only one feature. There are still two features (templates and CTFE), just two features that happen to use the same keyword.

Maybe you can insist that these are two features (although I disagree), but calling one of them templates really misses the mark. That's because, at least in C++, templates have their own template-level language (of "metafunctions"), whereas that's not the case in Zig. E.g. that C++'s `std::enable_if` is just the regular `if` in Zig makes all the difference (and also shows why there may not really be two features her…

Agreed. Zig's approach re-uses the existing machinery of the language far more than C++ templates do. Another example of this is that Zig has almost no restrictions on what kinds of values can be `comptime` parameters. In C++, "non-type template parameters" are restricted to a small subset of types (integers, enums, and a few others). Rust's "const generics" are even more restrictive: only integers for now.

In Zig I can pass an entire struct instance full of config values as a single comptime parameter and thread it anywhere in my program. The big difference here is that when you treat compile-time programming as a "special" thing that is supported completely differently in the language, you need to add these features in a painfully piecemeal way. Whereas if it's just re-using the machinery already in place in your language, these restrictions don't exist and your users don't need to look up what values can be comptime values...they're just another kind of thing I pass to functions, so "of course" I can pass a struct instance.

Re: Things Zig comptime won't do

#210
post #104

Earlier quoted context omitted.

I'm going to make you defend that statement that they are "useful". I would counter than macros are "powerful". However, "macros" are a disaster to debug in every language that they appear. "comptime" sidesteps that because you can generally force it to run at runtime where your normal debugging mechanisms work just fine (returning a type being an exception). "Macros" generally impose extremely large cognitive overhe…

> However, "macros" are a disaster to debug in every language that they appear. I have only used proper macros in Common Lisp, but at least there they are developed and debugged just like any other function. You call `macroexpand` in the repl to see the output of the macro and if there's an error you automatically get thrown in the same debugger that you use to debug other functions.

So, for debugging, we're already in the REPL--which means an interactive environment and the very significant amount of overhead baggage that goes with that (heap allocation, garbage collection, tty, interactive prompt, overhead of macroexpand, etc.).

At the very least, that places you outside the boundary of a lot of the types of system programming that languages like C, C++, Rust, and Zig are meant to do.

Post reply on HN