11 ms·
The call out to sum types is something I feel. I've been using Rust daily for almost 10 years now, and sum types are absolutely still one of the things I love m
by burntsushi 4y ago
The call out to sum types is something I feel. I've been using Rust daily for almost 10 years now, and sum types are absolutely still one of the things I love most about it. It's easily one of the things I miss the most in other languages that don't have them. I'm usually a proponent of "using languages as they're intended," but I missed exhaustiveness checking so much that I ported a version of it to Go[1] as a sort of lint.
[1]: https://github.com/BurntSushi/go-sumtype https://github.com/BurntSushi/go-sumtype
- zabzonk 4y agoso, um, unions? i have to say that in many years of programming, i have almost never needed to use such types.
- nicoburns 4y agoDoes your code ever contain class hierarchies with a fixed set of classes? Or variables where certain values have special case meaning? Those are the cases where Sum Types make things immeasurably nicer than the alternatives.
- zabzonk 4y ago> Does your code ever contain class hierarchies with a fixed set of classes? no - one of the advantages of OO programing is that class hierarchies (should you feel the need to use them, which mostly i do not) can be expanded. or indeed contracted. > Or variables where certain values have special case meaning? very, very rarely (i would say never, in my own code) but of course we have the null pointer as a counter-example. to which i can say: don't use raw pointers.
- jjnoakes 4y agoDo you have any large open source code bases that you could share?
- kaba0 4y agoThe Visitor pattern, which is a must in certain niches is exactly analogous to pattern matching over a sum type, and I am fairly certain that I can objectively say that the latter is a million times more readable and maintainable. It doesn’t make OOP obsolete, though, at all. Certain other areas are better expressed as hierarchies, e.g. GUI nodes.
- dralley 4y agoTagged unions with exhaustiveness checking, built into the language in a fairly deep way. Very different from what would come to mind for a C or C++ developer who hears the word "union".
- burntsushi 4y agoNope, not unions. Sum types. Sum types would be more analogous to tagged unions or discriminated unions. > i have almost never needed to use such types Well sure. When is an abstraction "needed"? Before Fortran, nobody "needed" a programming language either. So is a programming language necessary? That's the funny thing about the word "need." It has very narrow application, and it's precisely why I didn't mention the word a single time in my top level comment.
- zabzonk 4y ago[flagged]
- deleted 4y ago[deleted]
- cornstalks 4y agoThe union part is just an (obvious) optimization. An implementation detail. Sum types are not required to use a union; they can be implemented without using a union.
- zabzonk 4y agohow, exactly?
- cornstalks 4y agoBy using a struct with lazily initialized members, for example.
- zabzonk 4y agoif that is your example, provide some code.
- cornstalks 4y ago
- edgyquant 4y agoI can’t imagine not having the ability for a variable to be one of multiple potential types. I could probably work around it, sure, but why would you want to?
- zabzonk 4y agoi can't imagine why you would want that. for example, why would i want something defined as an integer hold something other than an integer? this is how strongly-typed languages work.
- Twisol 4y agoScott Wlaschin's "F# For Fun and Profit" website is a great resource for strongly-typed (yes) data modeling with sum types as a fundamental tool. https://fsharpforfunandprofit.com/series/designing-with-types/ https://fsharpforfunandprofit.com/series/designing-with-type... By the third entry in the linked series, we've motivated the use of a sum type with three variants in the example domain model.
- lmm 4y agoYou definitely want an integer to always be an integer, but you often want to represent "A or B". E.g. user has a contact method that's a phone number or an email address. Result of validation is a validated value or a failure message.
- cmrdporcupine 4y agoThe C++ (or Java etc) way to do many of the kind of things people do with Rust pattern matching & sum types would be via OO subtyping polymorphism. e.g. classic visitor pattern, etc. Way more verbose and awkward, and scatters the logic all over the place. Enums + switch is the other, and far less powerful.
- thom 4y agoOr, for the case of errors, with exceptions, which happen to have their own very limited pattern matching syntax.
- kaba0 4y agoI swear I am not paid to shill for Java or anything…, but Java did get sum types recently, and it’s quite good. They made it in a backwards compatible manner by sealed classes that list all of its subtypes. Switch expressions were also implemented, and they can exhaustively match on the given sum type. Pattern matching is quite limited as of now (only usable with records), but it is coming.
- cmrdporcupine 4y agoI should take a look. I kind of have a nostalgic interest in looking back at the JVM again, as 10 years ago it was my main career. The problem with Java was always that it has moved so slowly in acquiring nice things. The actual core runtime is amazingly well engineered.
- mgsloan2 4y agoIn my experience with languages that lack concise sum types and pattern matching, you end up with data types that have lots of implicit invariants. If you find yourself writing docs or thinking in your head things like "Field X is only set if field Y is true" or "If field X is non-null then field Y must be null and vice-versa", then these are indications that sum types would model the data better. Then these invariants can be followed by construction, and it becomes clear what's available at data access - great for interface clarity, robustness, dev tools, morale, etc. Relatedly, storing booleans is a smell, imho typically an enum or sum type is always better in languages that have concise syntax for these. True and False are meaningless without context, and so can easily lead to errors where they are provided to a different context or generally misused due to a misunderstanding about the meaning.
- zabzonk 4y agoi agree with your first point, but if your type-system is sufficently strong to express this, i'd like to see an example. i agree completely with your second point - passing around or storing booleans is usually horrible.
- mgsloan2 4y ago> if your type-system is sufficently strong to express this No fanciness needed, just plain old sum types. It is certainly possible to express those invariants directly in languages with a dependent type systems or refinement types like in liquid haskell - see https://ucsd-progsys.github.io/liquidhaskell-tutorial/Tutorial_05_Datatypes.html https://ucsd-progsys.github.io/liquidhaskell-tutorial/Tutori.... It's typically much easier to reason about and use sum types, though. Of course these examples are trivial and silly, but I see instances of these patterns all the time in big co software, and of course usually the invariants are far more complex but many could be expressed via sum types. I've seen loads of bugs from constructing data that invalidates assumptions made elsewhere that could have been prevented by sum types, as well as lots of confusion among engineers about which states some data can have. > Field X is only set if field Y is true Original gnarly C style pattern: struct TurboEncabulatorConfig { // When true, the turbo-encabulator must reticulate splines, and 'splines' // must be non-null. When false, 'splines' must be null. bool reticulate_splines; struct Splines *splines; }; Rust (let's ignore pointer vs value distinction): enum TurboEncabulatorConfig { NonReticulatingConfig, ReticulatingConfig { splines: Splines }, } > If field X is non-null then field Y must be null and vice-versa. Original gnarly C style pattern: struct TurboEncabulatorConfig { // When non-null, lunar_waneshaft must be null. struct Fan *pentametric_fan; // When non-null, pentametric_fan must be null. struct Shaft *lunar_waneshaft; }; Rust: enum TurboEncabulatorConfig { PentametricTurboEncabulator { pentametric_fan: Fan }, LunarTurboEncabulator { lunar_waneshaft: Shaft }, }
- paulddraper 4y agoAgreed. I don't have much Rust experience, but I do know Scala and Haskell and Typescript. It's one of those patterns that just comes up again and again and it's wonderful when the language offers a good solution.
- auggierose 4y agoAnd I must say that Typescript has the most flexible variation of it. I remember a while ago (> 10 years) that somebody on Lambda the ultimate asked how to flexibly represent compiler intermediate representations using the Scala type system, given that there can be many of them with overlaps. These overlaps are not a problem at all in Typescript.
- paulddraper 4y agoTypescript has discriminated unions, which are amazingly nice
- olliej 4y agoWait, are you saying that std::variant isn't a solution to all the problems? :D
- jackmott 4y agoagreed, many language features have trade offs or limited usefulness, but after years in the profession I feel that sum types are something every language should have.
- epage 4y agoTo me its not just exhaustiveness but that sum types (enums) are just like product types (structs), they have have member methods, implement traits, etc. Coming from C++, when I realized Rust let me do that, it blew me away.
- tomjakubowski 4y agoIn fairness to C++, you could derive a class from std::variant and add methods to it that way. It's less awkward with Rust's enums for sure though. And pattern matching as in Rust is far more expressive (and legible) than what std::variant gives you.
- jcranmer 4y agoA union in C++ is the same thing as a struct, except all of its fields live at the same offset. So you can define any method you want on it, including special stuff like constructors and destructors. No base classes are allowed, though.
- nicoburns 4y agoA method on a union is much less useful when you can't match on the tag though. I suppose you could store tag inside the union. Is that common? I've always imagined C/C++ tagged unions would store tag outside the union.
- School-Cotton 4y agoYes, it’s very common. The difference is rust enforces via the type system that you match on it.
- dilap 4y agoi haven't done c++ in a million years, but huh, you can! can't have any types w/ non-trival copy constructors in a union, though, apparently, which is quite the restriction. https://gist.github.com/erinok/c823af95db408653c7e42ab1893073b7 https://gist.github.com/erinok/c823af95db408653c7e42ab189307...
- loeg 4y agoYeah. In the C++ codebase I work on, we use folly::Expected<T, E> (essentially similar to Rust's Result<T, E>) heavily. Is it as ergonomic as Result is in Rust? Absolutely not. But it's still a nice design pattern for some of the same reasons people use Result in Rust. (And C++ std::optional<T> is somewhat similar to Rust's Option<T> type.) https://github.com/facebook/folly/blob/main/folly/Expected.h#L819-L913 https://github.com/facebook/folly/blob/main/folly/Expected.h...
- cmrdporcupine 4y agoSounds like Google/abseil's "StatusOr', but maybe a bit more freefrom? https://abseil.io/docs/cpp/guides/status https://abseil.io/docs/cpp/guides/status
- loeg 4y agoYeah, we use a fixed-E alias of folly::Expected<> in our codebase which is similar to absl::StatusOr. Something like: namespace our_project { class Status { ... }; template <class T> using Expected = folly::Expected<T, Status>; } absl::StatusOr<T> looks a lot like our_project::Expected<T>. This pattern of providing your own error type and aliasing Result is also somewhat common in Rust, I believe.
- joenot443 4y agoSwift has a similar Result type, it’s very handy.
- tcmart14 4y agoSwift on linux is supposed to be decent server side, but as far as I've heard, not much use outside of that and even limited testimonials on that. The bit I have dabbled in Swift, I really like it. I just wish it grew more out of the Apple ecosystem. It has lots of things I like, but considering I can't exactly take it with me to any system and just run with it is why I have not dived to much into it.
- 4y ago
- raincole 4y ago> I've been using Rust daily for almost 10 years now, I'm just nitpicking but Rust is 7 years old and 7 doesn't feel almost 10... Edit: Rust hit 1.0 7 years ago. Now I feel silly.
- capableweb 4y agoRust was initially created in 2006. Mozilla started sponsoring Rust in 2009. Maybe you're talking about the first stable release, which happened in 2015? Rust has been around for longer than the first stable release though. First spike at Google Trends for "Rust (Programming Language)" happened back in late 2013/early 2014 (https://trends.google.com/trends/explore?date=all&q=%2Fm%2F0dsbpg6&hl=en https://trends.google.com/trends/explore?date=all&q=%2Fm%2F0...) so not at all impossible for someone to have ~10 years experience with it. Although that's probably pretty uncommon. As a bonus, here is a very old (by internet time standards) HN submission titled "Mozilla releases version 0.1 of the Rust programming language (mail.mozilla.org)" - 236 points | Jan 23, 2012 | 82 comments - https://news.ycombinator.com/item?id=3501980 https://news.ycombinator.com/item?id=3501980
- howinteresting 4y agoBurntSushi has been using Rust since before 1.0.
- nicoburns 4y agoRust hit 1.0 is 7 years ago. But burntsushi has been using Rust since before it hit 1.0
- burntsushi 4y agoYeah as other have mentioned, I was using Rust before 1.0. This is my first public commit: https://github.com/BurntSushi/quickcheck/commit/c9eb2884d6a620b90b9986c65916eebc57084e89 https://github.com/BurntSushi/quickcheck/commit/c9eb2884d6a6... I didn't write any substantive Rust before that point. So I'm at over 9 years.
- raincole 4y agooh I see. Sorry I didn't expect you're such an early adopter. Edit: Oh uh you're burntsushi. Should've checked your username.
- howinteresting 4y agoI'm going to make the even stronger claim that a general-purpose statically typed language that doesn't support sum types and exhaustive pattern matching to at least the extent that Rust does is unfit for general use, just like a language that doesn't have product types is unfit for general use. Dynamically-typed languages often have adhoc sum types.
- unclad5968 4y agoWell that's obviously not true given all the successful apps written in languages that don't support sum types.
- tialaramex 4y agoIt depends what you take "unfit" to mean. Suppose I say that cars without seatbelts are "unfit" to use on public roads. Clearly I can't mean it's impossible to use such cars, people used to do it all the time, but perhaps I mean it's a bad idea to use them, and that's harder to argue with which is why we got laws saying you need seatbelts.
- deleted 4y ago[deleted]
- andrewflnr 4y agoA lot of successful apps were written with GOTO. That doesn't mean it's a good idea now.
- mikebenfield 4y agoI know your claim will strike some people as absurd, but I agree. I am honestly flabbergasted that any general purpose language created in the last 20 years doesn't have sum types and pattern matching. To me they are very nearly as fundamental as aggregate types (aka structs or product types).
- johnny22 4y ago
- gnulinux 4y agoThis is exactly what I think too. Sum types are so powerful, I feel a lot safer in Python + mypy with sum types (`from typing import Union`) than anything with C++ [1] even though C++ has a significantly more complex type system (it's type system is TC) and Python is as type-unsafe as a language can get. C++ made this odd choice as if any complex type system is better than a simple type system. When I was a younger software engineer, I agreed with this choice but now I completely disagree. I think next generation of software engineers will see that Turing completeness is a bug, and that the whole thing about software safety is finding weak features that are just powerful enough to be useful. If you don't believe me, go look at a language like Agda. You do not need Turing completeness: function application, sum types, primitive/structural recursion/corecursion are just about everything you need as a programmer and everything else that gives you a Turing complete language is extraneous. [1] unless you liberally use `union`s everywhere which is very unidiomatic C++, but can be done if desired.
- aw1621107 4y ago> even though C++ has a significantly more complex type system (it's type system is TC) For what it's worth, Python's type hints are also Turing complete (https://arxiv.org/abs/2208.14755 https://arxiv.org/abs/2208.14755, discussed on HN at https://news.ycombinator.com/item?id=32779296 https://news.ycombinator.com/item?id=32779296)
- deleted 4y ago[deleted]
- School-Cotton 4y agoRust’s type system is also Turing complete: https://sdleffler.github.io/RustTypeSystemTuringComplete/ https://sdleffler.github.io/RustTypeSystemTuringComplete/
- pharmakom 4y agoC++ has std::variant though?
- 4y ago
- MathMonkeyMan 4y agoUntil very recently, all of the C++ code I wrote was in the 1998 style or in the 2014 style. Since 2017, there is std::variant, which is a sum type template that _technically_ allows for language-level exhaustiveness checking via std::visit. It's not pretty, but it gets you there without requiring compiler support. Rust's version looks way better.
- otabdeveloper4 4y agoboost::variant has been around forever. Most modern C++ codebases do compile-time polymorphism via templates, though. std::variant is a niche use for things like serialization, when you need to make type choices at runtime.
- chippiewill 4y agoI was really excited for `std::variant`, when I actually got it I was seriously disappointed. It's just a pain to use because of how much template magic is used.
- rayiner 4y agoA nice thing about Rust is that it brings many well established good ideas from the ML branch of languages to the curly brave branch of languages. And frankly does a better job naming them (for people who weren’t math majors).
- chippiewill 4y agoA monad is just a monoid in the category of endofunctors. Seriously though, calling Algebraic Data Types an Enum was a stroke of genius.
- japanuspus 4y agoCompletely agree. This article [0] by Raphael Poss, explicitly listing how functional constructs look in Rust compared with "pure" functional languages is one of my favorite takes on Rust. [0]: https://dr-knz.net/rust-for-functional-programmers.html https://dr-knz.net/rust-for-functional-programmers.html
- the_gipsy 4y agoSame here, go would be such a killer language if it just had sum types.
- kaba0 4y agoYeah, it might even reach Java 1.1, feature-wise then.
- the_gipsy 4y agoJava does not have sum types.
- kaba0 4y agoIt didn’t have at 1.1, but it does now: https://news.ycombinator.com/item?id=35133670 https://news.ycombinator.com/item?id=35133670
- the_gipsy 4y agoYou can do that with go too, there are libraries out there. But it's all a joke compared to real support in the language.
- kaba0 4y agoReal support like what Java has? It literally has sealed classes and interfaces that specify all the possible classes that can subtype it. This is enforced by the language and allows for things like exhaustive switch cases. Pattern matching is a related feature which is also available in Java now, though as of yet only limited to records.
- the_gipsy 4y agoSo it's a Frankenstein monster, with no real pattern matching support. Good luck with using it in the real world.
- ptman 4y agoGo generics allow all kinds of things https://github.com/samber/mo https://github.com/samber/mo
- burntsushi 4y agoI'm aware. I wrote go-sumtype long before Go got generics.
- endorphine 4y agoCan someone elaborate on what sum types are and an example in Rust?
- burntsushi 4y agoThe section on "enums and pattern matching" in the Rust book is probably a good place to start: https://doc.rust-lang.org/book/ch06-00-enums.html https://doc.rust-lang.org/book/ch06-00-enums.html A "Rust enum" is a sum type. Also, I talked about ASTs being a good example use case for a sum type. Here's a "real" version of an AST for a regular expression in all its complex glory: https://github.com/rust-lang/regex/blob/a9b2e02352db92ce1f6e5b7ecd41b8bbffbe161a/regex-syntax/src/ast/mod.rs#L469 https://github.com/rust-lang/regex/blob/a9b2e02352db92ce1f6e... You can see that it starts out as a sum type at the top level. And inside each variant is all sorts of product types and other sum types. But do note in practice that sum types aren't limited to specialized things like ASTs. In practice, they come up everywhere. For example, here's a small little state machine used to implement a simple "unescape" routine. e.g., converting the string 'a\xFF\t' to the byte sequence 0x61 0xFF 0x09: https://github.com/BurntSushi/ripgrep/blob/44fb9fce2c1ee1a86c450702ea3ca2952bf6c5a7/crates/cli/src/escape.rs#L6-L17 https://github.com/BurntSushi/ripgrep/blob/44fb9fce2c1ee1a86...
- endorphine 4y agoAre there other examples of sum types besides enums, in Rust? Are the two terms synonymous?
- steveklabnik 4y agoRust does not have sum types outside of enums. Other languages have sum types, and may or may not call them enums depending on the language.
- deleted 4y ago[deleted]
- PaulHoule 4y ago… or sealed classes in Java