Settings

Theme

Practical introduction to algebraic datatypes (ADTs) in TypeScript

medium.com

71 points by elnygren 4 years ago · 22 comments

Reader

lalaithion 4 years ago

I think this article makes the "sum" and "product" terms out to be very complicated, when in fact it's quite simple. If we have two enums

    enum Bool {
        True,
        False
    }

    enum Status {
        Waiting,
        Successful,
        Failed
    }
We can combine them into a product type, like a tuple, or a sum type, like a union.

    type Product = (Bool, Status)
    type Sum = Bool | Status
Now, we ask ourselves, what are the valid values of type Product?

    (True, Waiting)
    (True, Successful)
    (True, Failed)
    (False, Waiting)
    (False, Successful)
    (False, Failed)
There are two Bool values, and three Status values, so there are 2×3=6 values for the product of Bool and Status. Now, what about the Sum type?

    True
    False
    Waiting
    Successful
    Failed
We have 2+3=5 total values for the sum of Bool and Product.

Now, obviously, this math doesn't quite work out for types with infinite values, like strings or bigints or arrays, but that's where the analogy comes from.

If you extend this further, you can even figure out what an exponential type would be: the exponential type Bool^Status is a function that takes a Status as its argument and returns a Bool.

  • ronenlh 4 years ago

    The Sum is called Union in the TypeScript docs, as it narrows the valid values to the overlapping elements.

    In the switch statement it becomes a Sum value (as shown in the article).

    In ReScript (shown as well, which uses OCaml under the hood) the syntax is that of a Variant, in which each element is a different nominal type.

    • lalaithion 4 years ago

      Good point. For example,

          type NotAProduct = Bool | Bool
      
      only has 2 values, not 4. For a real product type, we need

          type Product = {tag: "Left", value: Bool} | {tag: "Right", value: Bool}
      
      which has the requisite 4 values.
      • feoren 4 years ago

        This is also called a "discriminated union", with "tag" being the discriminator here.

      • nyanpasu64 4 years ago

        So to replicate a Rust enum, C tagged union, or C++ std::variant containing one byte for the type and the rest of the object inline, JavaScript needs a boxed object wrapping a string and the actual object contained inside? And the engine can't optimize away the boxed object into an inline value type because it could have multiple aliasing references in multiple locations? Boxing-based languages make me sad.

  • adverbly 4 years ago

    First I've heard of exponential type... to make sure I get it, you're saying there are 2^3 possible functions which take a status and return a bool? So if the enum inputs are success, wait, fail, you'd have

    Fff

    Tff

    Ftf

    Fft

    Ttf

    Tft

    Ftt

    Ttt

    All as possible implementations of that function. Neat...

  • rualca 4 years ago

    > I think this article makes the "sum" and "product" terms out to be very complicated, (...)

    Indeed it would drive the point home if instead of simply mentioning product they referred to Cartesian product.

    I guess sum types imply adding together two domain, but unless there's a subtlety then union would be clearer as well.

  • infogulch 4 years ago

    This is a great explanation for its length, thanks!

  • jimsimmons 4 years ago

    This helps handle IO in pure functional languages because? Is it because functions can have more than 1 type?

    • jhanschoo 4 years ago

      Can you elaborate what you mean by that? Support of sum and product types are used everywhere in typed functional languages and I don't know of any way they are exploited specifically in IO in a way that does not generalize to other domains.

      • jimsimmons 4 years ago

        Whoops looks like I confused algebraic datatypes and algebraic effects. Actually this confusion / realization because of your comment just clicked something for me, so thanks!!

benrbray 4 years ago

Definitely take a look at fp-ts [1]! It's mentioned at the end of the article, but I want to emphasize just how cool it is.

[1] https://github.com/gcanti/fp-ts

  • douglasisshiny 4 years ago

    I know there are a bunch of small guides / articles written by the author of fp-ts, but is there a good book, guide, etc. for diving in for someone who is kind of familiar with functional concepts but not quite ready for going full haskell? A book on haskell?

    • samhh 4 years ago

      I've just gone through this process over the last couple of years. To be honest, the most effective learning tool for me was dedicating my free time for a little while to learning Haskell. It's a higher upfront cost but a huge pay-off, not just in terms of understanding fp-ts but more broadly in how you'll be able to view programming through a new lens.

      That said, I wrote this[1] a year and a half ago for some colleagues just as I was getting into it myself. I hope it helps, and if not let me know if there's anything more specific I could help with.

      [1] https://samhh.com/blog/js-fp-jargon

      • douglasisshiny 4 years ago

        Much appreciated. I'll be sure to read through the post. I did buy a haskell book (Haskell Programming From First Principles) but haven't had time to read through it. Like you said, while there's a big upfront cost, it seems to be the most effective strategy.

  • myvoiceismypass 4 years ago

    Another one to look at is effect-ts[1] which is inspired by both fp-ts and also the scala library/framework ZIO

    [1] https://github.com/Effect-TS/core

g_delgado14 4 years ago

I spoke about this with Feross Aboukhadijeh last week at SpeakeasyJS:

https://www.youtube.com/watch?v=QyunIFfKLIU

inssein 4 years ago

ADTs like the one described here for remote data is a good start, but I haven’t yet found a good way to compose multiple pieces of data that is in potentially different states.

We use another class based concept on top at work where you can compose multiple pieces of remote data together, but it really only works well for the happy path.

ramesh31 4 years ago

The switch case is still a bit redundant and inelegant, particularly considering how out of place switch case syntax feels in a Javascript codebase in general. Early returns are the way to go.

  • elnygrenOP 4 years ago

    The point of the switch-case is to mimic pattern matching and TS is smart enough tell you whether you handled all cases.

    Would you prefer that it's a if-elseif-else based structure instead? I guess that would work too but I feel like it could be easier to write it in a way where you accidentally forget to handle some case (since your else / last return is a potential catch-all).

    I personally don't mind switch-case because I'm so accustomed to it in ReasonML/ReScript already.

  • ducharmdev 4 years ago

    Although I don't mind switch statements, I was thinking similarly about using early returns instead. But this is where switch/match expressions like in Rust or C# would really shine; it's too bad JavaScript/Typescript doesn't support them though.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection