News for hackers. Stuff that matters.
Re:Algebraic Data Types for C99 by skywal_l on Thursday May 09, 2024 @09:05AM (#40307807)
Zig is a modern imperative programming language with ADTs: https://ziglang.org/documentation/master/#Tagged-union
Re:Algebraic Data Types for C99 by chongli on Thursday May 09, 2024 @11:29AM (#40309239)
While this is a step up from C, it is still a long way from the full power and generality of algebraic data types. The key word here is algebraic. In a language with ADTs, such as Haskell, you can pattern match on an arbitrarily complex types, not just the outermost tag. A contrived example (from [1]):

    contrived :: ([a], Char, (Int, Float), String, Bool) -> Bool
    contrived    ([],  'b',  (1,   2.0),   "hi",   True) = False
To achieve a result like this using Zig's switch syntax would seem to involve a huge amount of boilerplate code and nested switch statements.

[1] https://www.haskell.org/tutorial/patterns.html

Re:Algebraic Data Types for C99 by Hirrolot on Thursday May 09, 2024 @11:44AM (#40309400)
This is more of syntax sugar than power and generality, since nested pattern matching can be mechanically translated into "top-level" matching (e.g., see [1] and [2]).

[1] L. Augustsson. Compiling Pattern Matching. In Functional Programming Languages and Computer Architecture, pages 368– 381, 1985.

[2] P. Wadler. Efficient Compilation of Pattern Matching. In S.L. Peyton Jones, editor, The Implementation of Functional Programming Languages, pages 78–103. Prentice Hall, 1987.

Re:Algebraic Data Types for C99 by chongli on Thursday May 09, 2024 @11:52AM (#40309478)
This argument is the most common fallacy I see in programming language discussions. I might as well give it a name right here: "Turing equivalence fallacy" or perhaps "syntax sugar fallacy."

All Turing Complete programming languages are Turing equivalent to one another. Programs written in one language can be mechanically transformed into those written in another. This is irrelevant to the discussion of programming languages. The whole point of creating different programming languages is to explore different ways to express the same program!

Re:Algebraic Data Types for C99 by Hirrolot on Thursday May 09, 2024 @12:03PM (#40309576)
In programming language design, we tend to distinguish between global and local analysis. While type checking and elaboration is an example of global analysis, desugaring is inherently local to some piece of code. Therefore, "power" or "expressiveness" usually mean that something cannot be syntactically "expanded"; e.g., while type classes elaborate into explicit dictionaries, they still require information from the type checker, and therefore considered a "real" feature of a programming language. On the other hand, nested pattern matching can be formulated as local syntax transformation, and therefore it doesn't bring anything fundamentally new to the type system or dynamic semantics.

There's also a great talk on the matter [1], if somebody is interested in formalities.

[1] https://www.youtube.com/watch?v=43XaZEn2aLc

Re:Algebraic Data Types for C99 by mitt_romney_12 on Thursday May 09, 2024 @12:55PM (#40310217)
You're approaching this from a PL design standpoint where the distinction is important, but from a user perspective it doesn't matter if it's just "syntax sugar" or if it's a super complicated to implement all that matters is whether the feature is available or not.
Re:Algebraic Data Types for C99 by Hirrolot on Thursday May 09, 2024 @01:08PM (#40310356)
Typing features affect the way we design APIs. Libraries written in languages with type classes and without them can have completely different designs. If nested pattern matching is not available, this will not affect the APIs, only the function bodies -- because desugaring is local by definition.
Re:Algebraic Data Types for C99 by chongli on Thursday May 09, 2024 @01:23PM (#40310501)
That doesn't matter in practice. If two programming languages have the same underlying feature but one has syntactic sugar to make it very easy to use and the other does not (so is quite cumbersome to use) then you'll find that the library ecosystem for the former language will see the feature in widespread use whereas the ecosystem of the latter will tend to shun the feature.

This is one of the social factors of programming language design and it's one of the main reasons successful programming languages work so hard to establish a coherent philosophy and a set of best practices or idioms within the language. For similar reasons, I believe this is why "anything goes" languages such as LISP have struggled to gain widespread adoption: with no philosophy every programmer becomes an island unto themselves.

Re:Algebraic Data Types for C99 by Hirrolot on Thursday May 09, 2024 @01:50PM (#40310836)
Yes, features that are easy to use will be more often used, while inconvenient features will be less used. I don't quite see any controversy with my comment.
Re:Algebraic Data Types for C99 by chongli on Thursday May 09, 2024 @04:38PM (#40312727)
The point is that in both cases the underlying feature is present, so APIs will be compatible. However, the lack of syntactic sugar in the one case will make any API that uses the feature cumbersome to use in that language, so in practice it will be avoided.
Re:Algebraic Data Types for C99 by lispm on Friday May 10, 2024 @03:22AM (#40316228)
> "anything goes" languages such as LISP have struggled to gain widespread adoption: with no philosophy every programmer becomes an island unto themselves.

There are already two misconceptions.

First: "Lisp has no programming philosophies" and styles.

Not every program starts by zero. Since Lisp exists since the end 1950s, it has seen quite a lot in programming styles over the years and it may contain traces of several. Generally it may support more than one programming paradigm. For example during the Common Lisp standardization there was a wish to have a standardized object system. So instead of the multiple possible approaches (actors, message passing, prototype-based, ...), Common Lisp has just one: CLOS, the Common Lisp Object System. So, much of the object-oriented code written in CL is implemented in one particular object system: CLOS. Object Lisp, Flavors, LOOPs, Common Objects, and a bunch of other once had thus been replaced by one standard.

CLOS also defines a bunch of user-level macros: DEFCLASS, DEFMETHOD, DEFGENERIC, ... Everyone using CL & CLOS will use those macros.

Second: "every programmer becomes an island unto themselves". If we look at the way CLOS was designed: there was a core group of six people from three companies. Around that there was a mailing-list based communication with a large group of interested people. Early on a prototype was implemented as a portable implementation of CLOS. This was widely distributed among interested parties: implementors, companies, research groups, ... Then reports about the language extension and its layers were published, books were published, application & library code was published.

One of famous books coming out of this effort: "The Art of the Meta-Object Protocol". It contained also a toy implementation of CLOS in Common Lisp. Book and the implementation of CLOS (both the larger prototype and the toy implementation) showed in excellent quality how to write object-oriented Lisp code.

https://mitpress.mit.edu/9780262610742/the-art-of-the-metaob...

So, there are communities, which share code and coding styles. Not every programmer is alone and starts from zero.

Re:Algebraic Data Types for C99 by chongli on Friday May 10, 2024 @07:11AM (#40317546)
First: "Lisp has no programming philosophies" and styles

You misquoted me. I said no philosophy, singular. In the programming language context, a philosophy is a convention or a standard. Just as many standards implies that there is no standard, many philosophies implies no philosophy.

Everything else you said is evidence for my premise. Hire 3 different programmers, one from each of the communities, and you might as well have 3 different programming languages. That’s not a standard. That’s not a philosophy. That’s anything goes!

Re:Algebraic Data Types for C99 by lispm on Friday May 10, 2024 @12:00PM (#40320508)
> You misquoted me. I said no philosophy, singular. In the programming language context, a philosophy is a convention or a standard. Just as many standards implies that there is no standard, many philosophies implies no philosophy.

That makes no sense.

> Hire 3 different programmers, one from each of the communities, and you might as well have 3 different programming languages.

Maybe not. They build on the same foundation, a language with a large standard, which is largely unchanged since three decades. A language which can be incrementally extended, without invalidating the rest. A language where extensions can be embedded, without invalidating the rest of the language or its tools. Many projects use SBCL (now itself 25 years old and only incrementally grown) and a bunch of core libraries for it.

> That’s not a standard. That’s not a philosophy. That’s anything goes!

Most languages support widely different software development practices. Take JavaScript: it includes imperative, object-oriented and functional elements (similar to Lisp). It has huge amounts of competing frameworks (many more than any Lisp), where many of them have been superseded and many of them are built on a multitude of other libraries. The developer can pick and choose. Each projects will be different from other projects, depending on which libraries and programming frameworks it uses - and which of those the developer re-invents.

Any half-way powerful language (C++ -> templates, Ruby -> meta objects, Java -> objects & byte code & reflection & class loader, Smalltalk -> meta objects, Rust -> macros, C++ -> language interpreters, Java -> external configuration languages, C -> macro processor, ...) has ways to adapt the language to a certain style & domain.

Any large Java framework defines new standards, new configuration mechanisms, new ways to use new Java features (lambdas, streams, ...). See the large list of features added to the Java language over time: https://en.wikipedia.org/wiki/Java_version_history For many of them there were competing proposals. Each Java code base will use some subset/superset of these features, depending on what is needed and what the personal preferences are. And then the Java architect in the project will not be satisfied and will invent yet another configuration system, this time not using XML or JSON for the syntax, but develop a new embedded scripting language for the JVM and integrate that with his software. I have seen Java architects which eventually didn't understand their own configuration system any more.

If you think a language like Common Lisp is special here, then in reality it is not. It's just slightly different in that it has extensibility as one of its philosophies and provides defined interfaces for that. There is one macro mechanism, which is powerful enough, that for decades it has not been replaced. Every syntactic extension mechanism will use this macro system, which is documented, stable and widely used.

Re:Algebraic Data Types for C99 by kazinator on Friday May 10, 2024 @03:23PM (#40322889)
There is rather no evidence for your premise.

Most of the Common Lisp code that is accessible via public repositories conforms to conventions and is understandable.

Lisp programmers are highly motivated toward encouraging collaboration, since there aren't that many people in Lisp where you can afford to be turning people away toward projects that are easier to get into.

Also, you can easily hire 3 developers and get 3 different languages in, oh, Java or JavaScript. One person is doing Kotlin, another one Scala, ...

Three C++ programmers in the same room could also not understand each other. The greybeard speaking only C++98 with a bit of 2003 doesn't grok the words coming out of the C++20 girl's mouth and so it goes.

Re:Algebraic Data Types for C99 by medo-bear on Friday May 10, 2024 @04:51AM (#40316728)
> I believe this is why "anything goes" languages such as LISP

Why do you think that Lisp is an "anything goes" language? What's your baseline? I think that C is no less an "anything goes" language, but with a much less pleasant UI.

> with no philosophy every programmer becomes an island unto themselves

Some people actually think that Lispers tend to be too philosophical

Re:Algebraic Data Types for C99 by chongli on Friday May 10, 2024 @06:50AM (#40317386)
Lisp is anything goes because of macros. Hire ten different Lisp programmers and you’ll get ten different domain specific languages and no one will understand what everyone else has done. If there is a unifying philosophy of Lisp, it’s probably “don’t use macros” and yet so many ignore it!

For all its faults, C is quite easy to read and understand, even by beginner C programmers. Yes, C also has macros but their clumsiness helps to discourage their use.

Re:Algebraic Data Types for C99 by medo-bear on Friday May 10, 2024 @07:08AM (#40317531)
I'm a Common Lisp programmer. I don't really know how to respond to that except to say, what you describe is not the case at all. Reading others' Common Lisp code is a joy compared to reading others' code in any other language.
Re:Algebraic Data Types for C99 by tialaramex on Thursday May 09, 2024 @01:36PM (#40310665)
Abstractly this is true, but software development is a human practice, so it matters not what's technically possible but what people actually do.

That's why the most important difference between C++ and Rust isn't some technicality even though the technical differences are huge, it's cultural. Rust has a Safety Culture and everything else is subservient to that difference.

Sugar matters, Rust's familiar looking loops are just sugar, it only "really" has a single way to do loops, the loop construct, an infinite loop you can break out of. But despite that, people deliberately write the other loops - and the linter strongly recommends that they write them, because the programs aren't just for machines to compile, they're for other humans to read, and a while let loop is an intuitive thing to read for example, so is the traditional for-each style iterator loop.

Re:Algebraic Data Types for C99 by gpderetta on Thursday May 09, 2024 @12:54PM (#40310201)
> Turing equivalence fallacy

Better known as the Turing Tar Pit.

Re:Algebraic Data Types for C99 by OskarS on Thursday May 09, 2024 @01:14PM (#40310403)
Of course the syntax sugar is a good thing if it makes it easier to write the code, but if the question is about "expressive power of the type system", it's not really relevant: Zig's type system can properly express a sum type. In addition: pattern matching is orthogonal to ADT, you can have pattern matching in both languages with and without algebraic types. Neither one implies the other.
Re:Algebraic Data Types for C99 by anon-3988 on Thursday May 09, 2024 @01:29PM (#40310587)
> Zig's type system can properly express a sum type.

Surely any Turing complete PL can express a sum type? I can't imagine a language that can support products but not sums.

Re:Algebraic Data Types for C99 by Tainnor on Thursday May 09, 2024 @02:12PM (#40311127)
Turing completeness has nothing to do with static type checking. Dynamically typed PLs can't express any type (except Any) yet are still Turing complete.
Re:Algebraic Data Types for C99 by xdavidliu on Thursday May 09, 2024 @11:57AM (#40309525)
syntax sugar IS power. also if the mechanical transformation is nontrivial, then so is the power of the sugar
Re:Algebraic Data Types for C99 by bunderbunder on Thursday May 09, 2024 @02:26PM (#40311293)
This is where I like to cite Dijstra's "Go To Statement Considered Harmful".

What a lot of people miss about that paper is that he wasn't just talking about goto statements. He was also making a more general observation about how more powerful and general programming language features are not necessarily desirable, because they tend to adversely impact developer productivity.

The reason I, as a user, prefer structured control flow statements over goto is not that I believe they are powerful. It's precisely because they are less powerful. The resulting constraints on how the program can be structured make it easier for me to read and reason about existing code. That makes maintaining code easier. It also makes optimization and static analysis easier. And it makes writing tests easier, too.

I have similar feelings about ADTs. The reason I prefer them to other ways of doing composite data types is not that I think they're more powerful. It's that they create constraints that tend to reduce the semantic complexity of the domain models people create in programming languages that use them. And that makes my job easier.

The corollary to that, though, is that I'm not actually all that hype about adding ADTs to existing languages. For reasons that are similar to how the mere availability of structured, reentrant function calls is small consolation in a codebase that's already riddled with goto statements. The real win doesn't come from using ADTs, it comes from not having to worry about all those other confusing overpowered things that aren't ADTs.

Re:Algebraic Data Types for C99 by ajross on Thursday May 09, 2024 @03:00PM (#40311729)
That's exactly where I am. Pattern matched sum types "feel great" to code in to an expert because they are a concise and reasonably tight way to express the otherwise boring "enumerate over possibilities" code.

But they're hard to read for anyone who isn't an expert on not just the language but the type in question (c.f. Rust's Option() idioms all looks like line noise to newbies, etc...). And that's a bad trade.

In essence, this stuff is just Perl all over again. It's a language feature that prioritizes concision over comprehension. And I say that as someone who really likes coding in perl. But "people" don't like perl, and the community moved on, and the reasons are... the same reason that uptake in ADTs is lagging where the experts want it to be.

Re:Algebraic Data Types for C99 by bunderbunder on Thursday May 09, 2024 @03:25PM (#40312028)
You've got to distinguish syntax from semantics, though. I agree, it's easy to turn a big semantic win into a net readability loss if you choose to represent it with an overly terse syntax that promotes code golf.
Re:Algebraic Data Types for C99 by ajross on Thursday May 09, 2024 @06:03PM (#40313394)
Compilers are really smart. It's easy enough in the modern world to demand that a "type" field be checked against all enumerants, preserving the "semantic win". A plain old C switch statement with gcc -Wall will do this today, in fact.
Re:Algebraic Data Types for C99 by jerf on Thursday May 09, 2024 @03:59PM (#40312350)
Pattern matching on the "top level branch" of the ADT, whatever you call it, is pretty darned useful.

Pattern matching on the next level down is a power tool to be used with care.

Having used some pattern matching languages for quite some time, I find anything much deeper than that is a code smell at best and pathological at worst. Pattern matching creates coupling proportional to the depth/complexity/precision of the pattern match. The top-level coupling is often unavoidable; if you're pattern matching at all, you certainly care which "branch" you are on and there is likely no refactoring that away. But the danger rises rapidly the deeper in you go. It's just so easy to pattern match on a third-level part of the complex object when you really ought to be wrapping that behind a function somewhere, possibly itself emitting a sum type value.

... but if all you really need is that "top level" match, a lot of pattern matching syntax and features are not really necessary (if not positively dangerous).

Re:Algebraic Data Types for C99 by ajross on Thursday May 09, 2024 @04:47PM (#40312806)
> Pattern matching on the "top level branch" of the ADT, whatever you call it, is pretty darned useful. Pattern matching on the next level down is a power tool to be used with care.

Which is exactly how Perl apologia arguments went.

Re:Algebraic Data Types for C99 by thesz on Thursday May 09, 2024 @02:37PM (#40311430)
I once explored the Epigram dependently typed programming language. It used to preclude many types of free form pattern matches, due to heavy dependence on structured editor (you speciy a type, it generates pattern matching, you cannot change the structure), so it was almost completely unuseable for many, many tasks.

So, while you are formally right, the need of shortcuts in pattern matching is undeniable to me.

Re:Algebraic Data Types for C99 by madeofpalk on Thursday May 09, 2024 @10:44AM (#40308754)
Re:Algebraic Data Types for C99 by fanf2 on Thursday May 09, 2024 @11:04AM (#40308962)
Union types are not the same as sum types.
Re:Algebraic Data Types for C99 by dtech on Thursday May 09, 2024 @11:18AM (#40309119)
TS narrows union types cases based on conditionals like "if" (called discriminated unions in the docs in the past), and supports exhaustiveness checks. How do they differ in functionality from sum types?
Re:Algebraic Data Types for C99 by mpawelski on Thursday May 09, 2024 @11:37AM (#40309333)
Supports exhaustiveness checks only if you explicitly opt-in it (by coding to pattern where you use helper function that accepts `never` type). "Dicriminated Unions Type"/"Sum Types" feels very hacky there, at least syntax-wise, because it is constraint by being "JS + types" language. It's remarkable what Typescript can do, but having native Discriminated Unions in JS (hence in TS too) would be much more ergonomic and powerful.
Re:Algebraic Data Types for C99 by nyssos on Thursday May 09, 2024 @12:50PM (#40310143)
Sum types are disjoint unions. This `T` has three cases

     L = { tag: "a", payload: string } | { tag: "b", payload: number }
     R = { tag: "b", payload: number } | { tag: "c", payload: boolean }
     T = L | R  
whereas a proper sum type `L + R` would have four.
Re:Algebraic Data Types for C99 by akavi on Thursday May 09, 2024 @03:37PM (#40312147)
As you've demonstrated, you can always construct sum types in typescript with the use of explicit discriminants:

    T = {tag: "L", payload: L} | {tag: "R", payload: R}
The real issue is typescript doesn't have pattern-matching, which make operating on these sum types inelegant
Re:Algebraic Data Types for C99 by brabel on Thursday May 09, 2024 @01:12PM (#40310384)
Isn't that a completely useless distinction?

For all purposes and intents, the "b" type in L and R should be treated the same, no? What do you gain by not doing that??

Re:Algebraic Data Types for C99 by Twisol on Thursday May 09, 2024 @03:36PM (#40312137)
No, it isn't "completely useless".

If you have a function that will normally return a string, but can sometimes fail due to reasons, you may wish to yield an error message in the latter case. So you're going to be returning a string, or a string.

It's not what the content of the data is; it's how you're supposed to interpret it. You have two cases, success and failure, and control will flow differently depending on that, not based strictly on the type of data at hand. We just model those cases in a type.

Re:Algebraic Data Types for C99 by brabel on Friday May 10, 2024 @02:21AM (#40315924)
No disrespect, but that still sounds entirely useless to me. I would never model something as `String | String` as that makes zero sense. You should use a `Result` or `Either` type for that like everyone does.
Re:Algebraic Data Types for C99 by Twisol on Monday May 20, 2024 @08:17PM (#40422386)
> You should use a `Result` or `Either` type for that like everyone does.

You have missed the thread context, which is whether `Either a a` (also written `a + a`) has any merits over simply `a` (which is identical to `a | a`). If you're on the `Either` train already, we are arguing over imaginary beef.

> No disrespect, but that still sounds entirely useless to me.

It is disrespectful to say something "makes zero sense", regardless of anything you might say to the contrary. You've misrepresented my point: nobody wants to model something as `String | String`.

If you have, say, an `Int | Bool`, and you pass both sides through some kind of stringification function, you're naturally going to get `String | String` without ever having written that type down yourself. You wouldn't necessarily want to collapse that to `String`, however, because you may -- for instance -- want to give strings and ints different decorations around them before finally flattening them. You might write such a function as something like

    (ib) => ib
      .mapBoth(showInt, showBool)
      .visit(prepend("int: "), prepend("bool: "));
You couldn't run this if the result of the `mapBoth` had type `String | String`: that type is indistinguishable from `String`, and since you can't tell which case you're in, you wouldn't know which tag to prepend.

You could write the same function without passing through `String | String`:

    (ib) => ib.visit(
      sequence(showInt, prepend("int: ")),
      sequence(showBool, prepend("bool: ")));
And yes, in this especially contrived example, perhaps you may find that to make more sense anyway. But in longer pipelines, sometimes separated over multiple functions, often involving generic code, it becomes much harder to simply always fuse steps in this manner. This is why we say sum types (e.g. `String + String`) compose better: they don't behave any differently depending on whether the two sides are the same or not. You have explicit control over when the two sides rejoin.
Re:Algebraic Data Types for C99 by madeofpalk on Friday May 10, 2024 @07:08AM (#40317529)
Why would you return `string | string` here? Wouldn't you explicitly mark the error, and return `string | error`? (substitute error with whatever type you want there - null, Error, or your own thing)
Re:Algebraic Data Types for C99 by Twisol on Monday May 20, 2024 @08:17PM (#40422390)
> substitute error with whatever type you want there - null, Error, or your own thing

And if I want `error` to be `string` so I can just provide an error message? Proper disjoint sum types let me keep the two sides disjoint, even if they happen to be the same type. Union types will collapse on any overlap, so if I happen to instantiate `error` at `string` then I can no longer tell my error case from my data case.

Re:Algebraic Data Types for C99 by hexane360 on Thursday May 09, 2024 @01:31PM (#40310612)
This often comes up when writing a function which returns a wrapper over a generic type (like Option<T>). If your Option type is T | null, then there's no way to distinguish between a null returned by the function or a null that is part of T.

As a concrete example, consider a map with a method get(key: K) -> Option<V>. How do you tell the difference between a missing key and a key which contains `null` as a value?

Re:Algebraic Data Types for C99 by epolanski on Thursday May 09, 2024 @03:34PM (#40312111)
`T | null` is equivalent to T. You can assign null to `T`>

It's like saying `string | "foo"` it is simply `string` due to subtyping.

Re:Algebraic Data Types for C99 by hexane360 on Friday May 10, 2024 @05:33PM (#40323985)
... no? Unless you're referring to null as a bottom type, then that doesn't hold. Are you describing some property of a specific language?
Re:Algebraic Data Types for C99 by brabel on Thursday May 09, 2024 @02:25PM (#40311290)
This is trivial to model by making your type `T | null | Missing`.
Re:Algebraic Data Types for C99 by efnx on Thursday May 09, 2024 @03:29PM (#40312068)
Maybe trivial to “work around” but there is a difference, ay?

With this type you would have to check/match an extra case!

The type you use there also takes more memory than Option<T> or Maybe<T>. So it has some other downsides.

Re:Algebraic Data Types for C99 by epolanski on Thursday May 09, 2024 @03:30PM (#40312078)
Or just using Option since you would have Some<null> or None in that case.
Re:Algebraic Data Types for C99 by hexane360 on Friday May 10, 2024 @04:02PM (#40323252)
Only if you're designing both functions ahead of time. In other words, it's not composable.
Re:Algebraic Data Types for C99 by chem83 on Thursday May 09, 2024 @11:10AM (#40309024)
F# too. And Elm. But I get your point.