Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It took me awhile to realize but this is one of the big things that algebraic data types (ADTs) help you to do...design your data types so they have exactly the number of valid states that your domain has. To use another common FP way of saying it...make invalid states unrepresentable.


It's not just the ADTs but the exhaustive pattern matching that help make sure you've covered all of the cases in your ADT. You'll see JavaScripters use objects as a poor man's ADT where they'll use the key name as a the constructor or a { tag: key, ... }, but that has caveats (aside from feeling less first-class): 1) JavaScript can't do exhaustive pattern matching so you'll always need to handle null cases, 2) checks must all happen at runtime which means you'll end up having to throw/catch exceptions and nothing will hold your hand to say that you've missed a case. TypeScripters can handle 1 & 2 for safety, but the ergonomics are bad and verbose so you'll see a lot of folks skip it. Similar languages will have pattern matching, safety, but lack the lightweight/dense ergonomics. When you dive into a language in the ML family (Haskell, OCaml, Standard ML, et. al.), the ergonomics make you want to use them, and they are idiomatic to the ecosystem and you'll want to use them for just about everything--either the ones in the Preludes/stdlib like Maybe, Either, List, etc. or by building them yourself.

An example in the wild that demonstrates this `fp-ts`'s explanation of how to do ADTs in TypeScript where you can see the comparison in PureScript is a one-liner (both data and polymorphic data): https://github.com/gcanti/fp-ts/blob/master/docs/guides/pure...


That Typescript ADT example is heavily outdated (or explicitly obtuse)

class Bar { tag="bar" as const; constructor(public value:string){} } class Baz { tag="baz" as const; constructor(public value:boolean){} } type Foo = Bar|Baz;


That is better, but it's still nowhere near `type Foo = Bar String | Baz Boolean`. It's also nowhere as clean to pattern match on.


That's true if pattern matching specifically is what you want, in practice we don't constructs ADT's for the sake of it and rolls with interfaces that also meshes well with data imported from other sources (ie JSON).

Also while pattern matching isn't an explicit thing in TS you get most benefits from compiler checked accesses that can recognize type-tests in your code and derive types and do property access checks.


A classic way to do this, is enumerations.


The nice thing about ADTs is that you can couple the state variables with the enum, so that the representable state is better tied to the enum value. (Yes, you can kind of do this in C with a discriminated union of a struct holding an enum and a union, but the type system doesn't stop you form grabbing a union member that doesn't match up with the current enum value.)


I use Swift.

Swift enums are really nice[0].

[0] https://littlegreenviper.com/miscellany/swiftwater/enums-wit...


To clarify: Swift enums are ADTs, because they can hold data for each case. Same as Rust enums. This contrasts to C enums which are not ADTs because they cannot store additional data.


I like Swift but the examples on that page are quite over-engineered. Reads like someone was seduced by their own cleverness. There are much simpler ways to achieve what is being done in that example.


If you read that page, you’d understand that it was supposed to be “over-engineered.” I assume that you did read it, and took the least charitable viewpoint. I didn’t post it for peer review. It reflects my enthusiasm, as I was learning the language, and is probably four years old.

I'm a fairly decent chap, and have nothing against you. Not sure why it's so important to you to begin our relationship with an attack. I suspect we have far more in common, than we do, differences.


My apologies, I didn’t realize you were posting a link to your own site! I would have phrased my comment differently, although probably that should not have made a difference and I should’ve phrased it differently anyway.


And mine. I just get tired of the "Attack first" approach that everyone takes, these days.

I know that I sound like a demented old man, when I say that I miss the old days, when we all shared passions for the tech, and, even when we were in competition with each other, we tended to have respect for one another.

But those were also the days of the UseNet flamewars, so it wasn't all flowers and bunnies (and I was definitely a flame warrior).


ADTs is a concept I've had a tough time understanding; the approximate definition that made the most impact with is considering them as a specific type of enumerations: Ones where each variant holds a value. Is this correct?


It is the other way around: enumerations are special cases of algebraic data types with no values attached.

In algebraic data types we have a sum (union) of a labeled cartesian products, and each product may have arbitrary number of values:

data MaybeInt = NoInt | AnInt Int

data MaybeItsPair = NoIntsPair | AnIntPair Int Int

The first constructor in both data types is a labeled (No...) product with empty number of value to apply cartesian product to.

The second constructor in both data types is a cartesian product: in first case an Int and in case a pair of Ints.

Of course you can have more involved data type:

data Expr = Void | Const Int | Var String | Bin BinOp Expr Expr | Un UnOp Expr

A label, two single value constructors and much more involved operations as well.


Can you explain the term 'cartesian product' in this context? I'm familiar with the cross product from undergrad linear algebra but I haven't applied algebra to types before, and I don't understand what two sets would produce a single Int as their product.

I haven't studied much theoretical computer science so I'd love to hear some good beginner resources on this stuff.


Cartesian product: https://en.wikipedia.org/wiki/Cartesian_product

The number of variants of the AnIntPair construction is the number of distinct elements in the Int type, squared.

I used definition of algebraic data type in Haskell: http://wiki.haskell.org/Algebraic_data_type


"Cartesian product" is a fancy word for "pair". Or more generally "tuple".


That’s the way Swift enums work.

Here’s an interesting little exploration: https://littlegreenviper.com/miscellany/swiftwater/writing-a...


Hence why they are called enums in Rust.


Enumerations are not ADTs.


The notion that your use cases have static well defines use cases is quaint. In practice, I've seen endless debates about whether state ahold be represented as a variable, parameter, type, or member, often with good arguments for all sides.


And state machines


State machines in the type system to check they’re good at compile time! <3 Idris




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: