![]() |
|
If I ever implement a product from scratch again, discriminated unions with compiler enforced exhaustive pattern matching is a hard requirement. It’s too powerful to not have.
|
![]() |
|
What languages could fit that description today? I don't really understand what it even means but maybe I could understand better if I could look at examples in languages which have them.
|
![]() |
|
> To me it feels very similar to an interface (trait) implemented by a bunch of classes (structs) Then you might not fully grok sum types yet. > From my perspective a discriminant vs a vtable pointer is a boring implementation detail the compiler should just figure out for me based on what would be more optimal in a given situation. Disagree. It's a design choice that should be decided by the programmers. There's a tradeoff—choosing which should be easier: adding a new variant or adding a new function/method. It's called the Expression Problem: https://wiki.c2.com/?ExpressionProblem |
![]() |
|
This is the work of a wizard. I've known C for almost 20 years, and never would I have thought the macro system was powerful enough to allow such black magic. This is awesome! |
![]() |
|
> PLEASE, do not use top-level break/continue inside statements provided to of and ifLet; use goto labels instead. Seems like a pretty big footgun. But otherwise, very cool. |
![]() |
|
In Nim, ADTs are painful still (as your example clearly shows), but they are working on adding proper ADTs to the language (I can't find where I read that, but I am sure I did!).
|
![]() |
|
I've been a Nim respecter for many years, it's slept on in general as a language. The difference here is that Nim compiles to C and you can turn the garbage collector off: Zig compiles C and there's no garbage collector. That means the entire standard library is available when generating object code. It's also trivial to opt-in to the C ABI on a fine-grained basis, by defining a function or struct with the extern keyword. I believe this is still fairly current about the difficulties of building Nim dylibs for C programs: https://peterme.net/dynamic-libraries-in-nim.html I expect Nim will stabilize about where D has: it will have a dialect of the language which, with relatively painless accommodations, is able to produce object code which speaks C ABI. Zig is different. The language is relentlessly focused on providing a better alternative to C while occupying the same niche, and a lot of design time has been spent on making it practical to take an existing C program and start writing the new parts of it in Zig. It's a good language, Nim, and getting better. I'd recommend it for someone who is considering Go, for example. |
![]() |
|
Let's say further that you already know Zig exists, and aren't going to use it for reasons that anyone writing a C/Rust/D/C++ program already knows. At least give Nim a try. |
![]() |
|
> "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. |
![]() |
|
> 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. |
![]() |
|
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?? |
![]() |
|
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)
|
![]() |
|
Does Java sealed classes enable something like an exhaustive pattern matching? (A form of pattern matching that will fail at compile time if you add a new class that extends the sealed class)
|
![]() |
|
Yes, since Java 21. Example:
If you added a new subtype to BinaryTree you would need to fix the switch.EDIT: I didn't handle the `null` case above... so it would be a NullPointerException if someone passed null... apparently, Java decided to make handling `null` optional. More information: https://www.baeldung.com/java-lts-21-new-features |
![]() |
|
In Rust [0]:
Given a mutable reference to a value of enum type, you can replace it with another variant. Or you can swap it out with any other value of the same type, even if the variants are different. This is most commonly used for OptionThe limitation here is that for as long as the mutable reference is live, no other code can access the value. So when you do change the variant, you can't have any other references sitting around that point to the removed subfields. [0] https://play.rust-lang.org/?version=stable&mode=debug&editio... |
![]() |
|
> I don't really think it's useful to do that though?? Can you give an example? For an exercise, I had to write a system with Admins and Customers, with the ability to upgrade a Customer into an Admin, or vice versa. My thought was to put them as two subclasses under a User superclass, so that I could put them under a single Users table, and not have to link and unlink things over the conversion. Hibernate ORM supports storing subclasses by adding an implicit discriminator field. However, its object model specifies that a single row always corresponds to a particular instance, so it has no support for changing the subclass of a row. Ultimately, I ended up with a hacky solution of creating a new record with the primary key copied over. > By the way, I would claim Java's sum types are less limited than Rust because in Rust, variants don't have their own type. The consequence is that you can't have functions that only accept some variant, as far as I know (I remember having this problem once), or add "methods" only to one variant... while in Java, because variants are just normal types, you can do both, and doing that is pretty damn useful. At least in Rust, you can simulate this pretty trivially by having each variant store a struct value with all the data and methods you want. See proc_macro::TokenTree [0] for an example of this. Of course, it's not ideal in how verbose it is (though not much worse than Java!), but it can be workable on the consumer's side if you add some extra From impls. [0] https://doc.rust-lang.org/proc_macro/enum.TokenTree.html |
![]() |
|
Indeed, however CLU and Modula-2 were the ones used mostly for practical teaching purposes, until ML became widespread enough for ADT to gain yet another meaning.
|
![]() |
|
Yeah, but the annoying part of Java is that people stick with old versions for a long time. Java 21 looks pretty great but most companies are still using Java 17, or even Java 11 still.
|
![]() |
|
Implication is not primitive, "x->y" is reducible to "not(x) or y". None of those fallacies derive from a lack of direction, but from not being invalid logical deductions.
|
![]() |
|
Algol 68 may have been a failure because it was ahead of its time, which meant it was hard to write a compiler for it. For example, Algol 68 had closures (apparently Knuth snuck them in).
|
![]() |
|
> class-based inheritance is actually pretty simple to understand Simple to understand, a nightmare to debug, as you'll be chasing where your data and data contracts across a ton of files. |