Most active commenters
  • pron(8)
  • whateveracct(5)
  • auggierose(3)

←back to thread

Zig is hard but worth it

(ratfactor.com)
401 points signa11 | 15 comments | | HN request time: 0s | source | bottom
Show context
pron ◴[] No.36150237[source]
> there’s not a direct correlation between the slimness of a language’s syntax and ease of learning

That's absolutely true, but (the standard library aside) the "syntax" -- or, rather the syntax and core semantics -- of a programming language are arbitrary axiomatic rules, while everything else is derivable from those axioms. So while it is true that a small language can lead to a not-necessarily-easy-to-learn overall programming experience, it is the only arbitrary part, and so the only part you need to memorise (or consult the documentation for when dealing with some subtlety you may have forgotten). So a smaller language reduces the need for "language lawyering" after you learn it.

Some languages (e.g. lisps) are deceptively small by relying on macros that form a "second-order" language that interacts with the "first-order" language, but Zig doesn't have that. It has only one language level, which is small and easy to memorise.

But yes, Zig is a bigger language than C, but a far smaller language than C++. What's amazing, though, is that C++ is strictly more expressive than C (i.e. there are programs that could grow exponentially faster in C than in C++, at least without the help of C macros), but Zig is as expressive as C++ (i.e. program sizes may differ by no more than a small constant factor) while being much closer to C in size, and it achieves that without the use of macros.

replies(4): >>36150398 #>>36150954 #>>36151058 #>>36153690 #
the_duke ◴[] No.36150398[source]
I'm not a Zig expert, but I have a different take here.

Zig has comptime, which is essentially a compile time macro written in the main language and with type reflection capabilities.

They can introduce complex behaviour and fail in very cryptic and unexpected ways, which results in an experience very similar to macros or C++ template literals.

replies(1): >>36150717 #
pron ◴[] No.36150717[source]
The objects that are manipulatable by comptime are ordinary program objects and types -- not ASTs. That means that while it's true you can get compile-time errors in similar situations to macros, the errors themselves are like ordinary runtime errors in an untyped language -- while occurring at compile-time, they look like runtime error in Python or JS -- rather than errors due to some "second-order" manipulation of symbols like templates or macros, and so are easier to diagnose (you get a regular stack trace for one).

There is also another interesting difference, albeit a theoretical one. Zig's comptime is what's known in formal languages to be referentially transparent (it basically means that you cannot distinguish between two otherwise identical objects that differ only in their reference name) while macros are not. Because referential transparency is strictly less expressive than "opacity" (but it's also simpler!), it's surprising that so many practical use cases for macros can be addressed with the less powerful (but simpler and much easier to debug) comptime. That's quite a discovery in language design. While other languages also have comptime-like constructs, they also have other complex features that have made it hard to see just how powerful something like comptime alone can be.

replies(3): >>36151303 #>>36151943 #>>36156263 #
auggierose ◴[] No.36151303[source]
It's not really surprising that purely functional programming is expressive. Indeed, it is as expressive as "opaque" programming.
replies(1): >>36151398 #
1. pron ◴[] No.36151398[source]
There's nothing pure functional here (perhaps the term "referential transparency", which some FP fans have come to misunderstand and perpetuate its misunderstanding is what may have given you that impression). Referential transparency is very much less expressive than referential opacity, as there are certain statements that simply cannot be expressed if your language is referentially transparent. For example, in programming, a referentially opaque expression can refer to the name of the variable holding some value. In programming, languages like Zig and Java are more referentially transparent than languages like C and Haskell because the latter have macros.
replies(2): >>36151778 #>>36151826 #
2. whateveracct ◴[] No.36151778[source]
Can you give me an example of a Haskell expression which isn't reverentially transparent (without unsafePerformIO)?

> An expression is called referentially transparent if it can be replaced with its corresponding value (and vice-versa) without changing the program's behavior.

^ that is the definition of referential transparency I am aware of.

You seem to be implying that FPers have bastardized the term through their misunderstanding.

But the bog standard FP definition is a real and useful concept. Maybe it stole something else's name? But I don't think it's due to being mistaken. Because the FP concept itself is pretty rigorous.

replies(1): >>36153339 #
3. auggierose ◴[] No.36151826[source]
Referentially transparent means that you can replace an expression with its value without changing the meaning of the program. If everything you can do must be referentially transparent, then that's purely functional programming, because applying functions without side-effects is pretty much the only thing you can do then. Of course, there are some other techniques like rewriting, which strictly speaking are different from purely functional programming, but I consider these two things to be pretty much the same thing.
replies(1): >>36152488 #
4. pron ◴[] No.36152488[source]
> Referentially transparent means that you can replace an expression with its value without changing the meaning of the program.

Not quite. A referentially transparent expression (E) is one where you can replace any of its subexpressions (A) with another (B) that has the same meaning (not value!!!!) as (A) without changing the meaning (not value!!!) of E. However, in purely functional languages, the meaning of any expression is a value, but that's the important thing about them, not the fact that they're referentially transparent as imperative languages equally are. We often use the word "semantics" or "denotation" instead of "meaning" in the above, and we say that a pure functional one is one that has "value semantics", i.e. one where the meaning of an expression is a value.

Most programming languages are referentially transparent when not using macros (that was the whole point of talking about referential transparency in programming in the first place), and that's important because it demonstrates both the expressive power and the complexity of macros.

replies(2): >>36153708 #>>36154329 #
5. pron ◴[] No.36153339[source]
> Can you give me an example of a Haskell expression which isn't reverentially transparent (without unsafePerformIO)?

Yes: https://github.com/ncaq/debug-trace-var The trick, however, is not unsafePerformIO (destructive mutability has nothing to do with referential transparency in general, although it breaks it in Haskell specifically) but with TemplateHaskell, as quoting has everything to do with referential transparency.

> But the bog standard FP definition is a real and useful concept.

Actually, it's rather tautological. It defines FP circularly (see my comment here: https://news.ycombinator.com/item?id=36152488). It says nothing more than the far more useful explanation: "the meaning of every expression is a value".

replies(1): >>36153671 #
6. whateveracct ◴[] No.36153671{3}[source]
hm okay so basically anything that doesn't use TH or unsafePerformIO is gonna be referentially transparent. And TH is even deferentially transparent at TH-time. It only "breaks it" when evaluating the whole program. But each "stage" maintains the property.

I'm assuming any pure language with macros is also r.t. at each stage and only pedantically breaks r.t. when combined. But I don't think that especially hurts the ability to do fast and loose reasoning so long as the core language is pure.

It definitely doesn't seem correct to say Java is more referentially transparent than Haskell here. You don't have to go into such niches in Java to lose that property.

replies(1): >>36154194 #
7. norir ◴[] No.36153708{3}[source]
Do you have sources for your definition? The original definition that I'm finding from Quine seems to broadly support the interpretation that an expression is referentially transparent if it can be replaced by its value without altering program semantics, as others have stated. Regardless, it isn't clear to me how macros are any more or less referentially transparent than function calls in an impure language.
replies(2): >>36153732 #>>36154447 #
8. whateveracct ◴[] No.36153732{4}[source]
I'm very sure the commenter is being a little pedantic

But even pedantry can't argue that Java is a fundamentally more referentially transparent language than Haskell lol. That threw me for a loop.

replies(1): >>36154477 #
9. pron ◴[] No.36154194{4}[source]
> You don't have to go into such niches in Java to lose that property.

It's not so easy. You'd have to examine debugging information in stack traces and use reflection. You can't write such a "trace" operator in Java or in Zig. Of course, without macros, C is almost perfectly referentially transparent and Haskell is, too (except for unsafePerformIO).

replies(1): >>36156947 #
10. auggierose ◴[] No.36154329{3}[source]
Do you have a pointer to a paper where "referentially transparent" is defined in your sense? I grant you that values of a programming language should be distinguished from the meaning of an expression, which will be a value of some sort in the logic. In that sense, any language with a denotational semantics will be referentially transparent. So maybe what you are really saying is: macros usually don't have a denotational semantics. Not sure that it is helpful to call this referential transparency, because it differs from the meaning most people associate with it.
11. pron ◴[] No.36154447{4}[source]
The thing that is preserved is "meaning" or "referent" -- the term Quine uses (hence, "reference transparency") -- not "value". The distinction between referent/meaning and value is the most important aspect of distinguishing between pure FP programming languages and others, and yet that's the thing that is so commonly confused by FPers using the terminology, which makes it quite pointless.

In most programming languages the reference or meaning of a term is not a value; in pure functional languages the meaning is a value and that's what makes them special, not their referential transparency which they share with imperative languages.

Here's an example from C:

    int global_x = 0;

    void f() { x++; }
    void g() { x++; }
f and g have the same meaning in C (but the function `void h() { x += 2; }` does not) yet `m(f)` and `m(g)` will not have the same meaning if M is defined as:

    #define m(x) #x
However, f and g are interchangeable anywhere else (this is not actually true because their addresses can be obtained and compared; showing that a C-like language retains its referential transparency despite the existence of so-called l-values was the point of what I think is the first paper to introduce the notion referential transparency to the study of programming languages: https://github.com/papers-we-love/papers-we-love/blob/main/l... You may be surprised to see that Strachey also uses the word "value" but his point later is that value is not what you think it is)
replies(1): >>36156120 #
12. pron ◴[] No.36154477{5}[source]
Right, the incorrect and quite pointless common use of "referential transparency" in FP fan circles tends to throw people off when they first see and understand the actual meaning of the term.
replies(1): >>36156125 #
13. norir ◴[] No.36156120{5}[source]
Thank you. That response was clarifying. I understand now what you mean. Funnily enough though, the paper you cited begins:

"Any discussion on the foundations of computing runs into severe problems right at the start. The difficulty is that although we all use words such as ‘name’, ‘value’, ‘program’, ‘expression’ or ‘command’ which we think we understand, it often turns out on closer investigation that in point of fact we all mean different things by these words, so that communication is at best precarious."

Rather than debating the semantics of the colloquial usage of referential transparency, I'm more interested in the question: what can I tell at the call site of a function without knowing the definition of the function? In an impure language, I cannot tell whether the call has side effects without looking at the definition. This is true whether I am using a macro or simply a regular function call.

Now, even if my language of choice is impure, referential transparency of expressions is still a useful concept that can inform how I write my program. I can use naming, for example, to suggest whether a function call may have side effects even if the language compiler can't verify the property. Not perfect, but better than nothing. And if I'm really confused by a bug, I can always just assume that the name is misleading and the function may have unintentional side effects. In other words, I can use the concept of referential transparency to implement a metaprogramming system in my head.

14. whateveracct ◴[] No.36156125{6}[source]
Well it's not pointless. The term as used in FP has a pretty well-defined meaning [1] and it is a quality only some programs have. And a program having that quality enhances (fast-and-loose) equational reasoning.

[1] I can point at most Java code and prove how it fails the definition. It's not especially hand-wavey.

15. whateveracct ◴[] No.36156947{5}[source]
You can also mutate a list that is passed in and suddenly you cannot do substitution to reason about your program.