5 ms·
> Adding a third type of payment (Cash) is as simple as adding "| Cash" to the definition This is the "expression problem" https://en.wikipedia.org/wiki/Expres
by chriswarbo 8y ago
> Adding a third type of payment (Cash) is as simple as adding "| Cash" to the definition
This is the "expression problem" https://en.wikipedia.org/wiki/Expression_problem https://en.wikipedia.org/wiki/Expression_problem
In FP with ADTs, anyone can write new functions for a datatype. Yet, as you say, adding a new case requires modifying the definition and existing functions.
In OOP, anyone can add a new case, by defining a new subclass. Yet writing a new method for that class requires modifying the definition and existing subclasses.
One of these isn't always better than the other, so it's useful to have both.
> If you try to describe this properly in C# you'll be writing an abstract outer class and N concrete inner classes, plus boilerplate for comparisons and so on.
This overhead is due to emulating one approach using another. It would also take a bunch of boilerplate to implement subclassing with ADTs (e.g. a sum type to describe the methods, a smart constructor to implement the inheritance, etc.).
(Personally I prefer ADTs too, but they're not a magic bullet)
- pdpi 8y agoHaskell's typeclasses (or, equivalently, traits in Rust) help a lot with this problem, though. It's one of the biggest language features I wish I had access to in more languages.
- chriswarbo 8y agoHaskell type classes are great for most situations, except when they fail it can be quite spectacular. I think the "magic" of type classes comes from two things: - All instances are thrown into one global namespace, so we don't need to worry about where to find the right one. - There can be at most one instance per type. Hence there's no ambiguity about which one to pick. These make it easy for the machine to pick the correct instance automatically. Unfortunately, it also means that every time we import or upgrade a library, there's the chance that it will break things by exposing an instance that clashes with what we have. The "backpack" stuff looks interesting in this regard, although it seems very over-engineered (mostly due to legacy concerns).
- 0db532a0 8y agoWhat is backpack stuff?
- sideeffffect 8y agoThe new module system for Haskell (GHC, to be precise) https://ghc.haskell.org/trac/ghc/wiki/Backpack https://ghc.haskell.org/trac/ghc/wiki/Backpack https://plv.mpi-sws.org/backpack/backpack-poster.pdf https://plv.mpi-sws.org/backpack/backpack-poster.pdf
- 0db532a0 8y agoOh thank goodness. Finally.
- unhammer 8y agoOCaml has ADT's, but also polymorphic variants[0] which are open, and let you say "these alternatives or more", or "these alternatives or less": https://v1.realworldocaml.org/v1/en/html/variants.html#polymorphic-variants https://v1.realworldocaml.org/v1/en/html/variants.html#polym... It's the main thing I've missed from OCaml when programming in Haskell (you can sort of do something like this with typeclasses, but I much prefer the OCaml solution).
- dan-robertson 8y agoOcaml also has open variants where one can add cases to an existing sum type (essentially this is like exceptions but less special) and it’s objects are like a dual of polymorphic variants where you get a record which you may add fields to. Neither of these features is as commonly used as polymorphic variants however.
- AnaniasAnanas 8y ago> Yet, as you say, adding a new case requires modifying the definition and existing functions. The same is true in java if you use instanceof. The thing is, just like enums inductive data type definitions are not supposed to be extended by any module other than the one declaring them, and adding more should be considered an API change.
- chriswarbo 8y ago> The same is true in java if you use instanceof. Java isn't a particularly pure example of OOP: it has `if`/`for`/`while`/etc. from structured programming, non-OO "primitive types", non-polymorphic `+`/`-`/`*`/etc. and so on. `instanceof` is another example of a non-OOP feature (the OOP approach would be an `isFoo` method, where each class can decide what to return). > inductive data type definitions are not supposed to be extended by any module other than the one declaring them, and adding more should be considered an API change. Exactly. Yet in OOP, anyone can add new subclasses whenever they like, without ever telling the original author, and instances of those subclasses can co-exist alongside any other subclasses and will (in theory) work fine with all the existing code written for the superclasses. What if I altered your sentence to say the following: "Functions acting on a datatype are not supposed to be extended by any module other than the one declaring that type, and adding more should be considered an API change." This would seem crazy if we applied it to an FP language; yet that's exactly the case in OOP, where a fixed set of methods are implemented inside each class.
- thesz 8y agoYou are talking about expression problem: https://en.wikipedia.org/wiki/Expression_problem https://en.wikipedia.org/wiki/Expression_problem "Extend program with new data types and new functions" It is quite well studied and solved in various languages. And, in my opinion, typical OO language solution does not come as especially succinct.
- chriswarbo 8y ago> It is quite well studied and solved in various languages I wouldn't say the expression problem is "solved"; various languages pick different tradeoffs, but the underlying problem is still there. The best description I've seen of this is at https://www.tedinski.com/2018/02/27/the-expression-problem.html https://www.tedinski.com/2018/02/27/the-expression-problem.h...