6 ms·
Hickey's `Maybe` example feels misguided (about breaking existing callers). If Maybe is an input just keep the original function around and provide a new one th
by dwohnitmok 8y ago
Hickey's `Maybe` example feels misguided (about breaking existing callers). If Maybe is an input just keep the original function around and provide a new one that wraps it.
originalF :: x -> y
f :: Maybe x -> y
f None = newDefaultBehavior
f (Some x) = originalF x
If `Maybe` is an output then existing callers SHOULD be broken, because they must now handle a new possibility of failure they didn't before. The fact that this doesn't happen in Clojure is actually a source of pain for me.
It's also rather interesting that Hickey also comes to the same conclusion that optionality doesn't belong in a data structure, but for slightly different reasons: https://news.ycombinator.com/item?id=17906171 https://news.ycombinator.com/item?id=17906171
- boogiewoogie 8y agoMaybe someone figured out they would get half the response the first time and the rest the second time.
- yen223 8y agoIf x represented a set with some values x = {RED, GREEN, BLUE} It would be really swell if the optional type `Maybe x` were a strict superset: Maybe x = {RED, GREEN, BLUE, None} This means functions that map `Maybe x -> y` can accept values of type `x` without issue. But in Haskell (and OCaml) it's not. What we actually have is something more like Maybe x = {Some(RED), Some(GREEN), Some(BLUE), None} which is a totally different set from `x`, which is why functions which shouldn't break, ultimately do.
- tel 8y agoThe downside of that (which can be provided by union types) is that `Maybe (Maybe X) == Maybe X` which may or may not be what you're after. The particular weakness is the interplay with generics where `forall t. Maybe t` depends upon the `Maybe` and the `t` not interacting. If you've got `forall t. t | null` then this type may secretly be the same as `t`---and if you handle nulls then it might conflate meanings unintentionally.
- ndh2 8y agoSo you're saying that the one Nothing has a different meaning than the other Nothing, and that this is something that anybody would want? Or that we have a Nothing and a Just Nothing? I'm sorry, but I don't get how that would be considered good design in any context. Do you have any example where this would be considered sensible?
- lostmsu 8y agoImagine you're working on a compiler. You need to represent compile-time computed value of type Maybe Int (e.g. you are precomputing nullable integers). You see 1 + null. So you have add: Maybe Int -> Maybe Int -> Maybe Int, that takes two precomputed values, and returns new precomputed value for the operation result. However, you can't precompute Console.readInt(). For some expression, you can either be able to compute value at compile time, or not. What is the output type of compileTimeCompute: Expr -> ???
- ndh2 8y agoI don't understand your example. What does compile-time computed stuff have to do with readInt()? I get that it might be possible to do that, use a Maybe Maybe T. But it's like an optional<bool> in C++. It can be done, it's just not a good idea. So if you design your system not to allow that in the first place, nothing of value was lost. If you have specific error cases that you want to communicate, like "what was read from the console didn't parse as an int" as opposed to "the computation didn't find a result", then using the two values "Nothing" and "Just Nothing" as the two distinct values to encode that is not a sound design. Either you have meaning, or you have Nothing. Nothing shouldn't have any meaning attached to it.
- lostmsu 8y ago> "what was read from the console didn't parse as an int" I meant what you read from the console can not be computed at compile time.
- Skeime 8y ago
- dwohnitmok 8y agoYep that's why as Hickey says people are excited about union types. But in the context of a refactor or a change to an API where you care about breaking existing users, there's a perfectly valid way forward. Basically I don't think Option types are as unwieldy as Hickey is making them about to be. Indeed Dotty (one of the examples in the talk) will be keeping Option even in the presence of union types (mainly because union types fall down with nested Options and with polymorphism). That being said Haskell (and Scala) is annoying when it comes to trying to combine different kinds of errors together, if you want an error type more expressive than Option. OCaml here (and Purescript) have a much better story with polymorphic variants.
- ernst_klim 8y agoIn OCaml you have polymorphic sum and product types, so the function val f : [`Red | `Green | `Blue | `None ] -> y Would accept a value of type [`Red | `Green | `Blue ] quite fine. Though I'd still prefer a haskell false positive to a clojure's false negative. Also, if something is considered a string, I expect it to be any string but NULL.
- jtmarmon 8y ago> If Maybe is an input just keep the original function around and provide a new one that wraps it. The problem with that is now you have to maintain two functions that do the same thing just to satisfy the type system. > If `Maybe` is an output then existing callers SHOULD be broken, He agrees with you on this, according to the talk
- dwohnitmok 8y ago1. They don't do the same thing though, i.e. it's not just a wrapper. Dumb wrappers are a problem though in a lot of typed languages in other cases. 2. Ugh this is what I get for watching a video while coding on the side. You're right.
- matt-noonan 8y agoEven without wrapping, I'm not sure that "it breaks existing callers" is such an awful thing. Yes, it is slightly annoying. And if it was a language like Python, making a function suddenly start returning None is going to silently break existing callers, which is pretty awful. But in a language like Haskell, the compiler will find every broken callsite immediately, and the cleanup needed on the caller side is very straightforward. All that aside, in my experience the case of parameters becoming optional or return values losing nullability actually does not happen that much in practice. It's a bit of a strawman.
- remontoire 8y agoThis only works if you own the codebase. If a library maintainer changes the signature from X to maybe then it breaks all of the users of the libraries code. The problem with breaking changes is that the person who breaks things doesnt feel the pain.
- matt-noonan 8y agoNo, I'm arguing that this kind of breaking change is fine-ish, because the compiler helps the end users upgrade their code to the new API in a very straightforward way. In other words, I'm arguing that this kind of breaking change is (1) rare, and (2) ok anyway if you have a strong enough type system that the compiler will just tell you what callsites need modification, and the modification is simple (as in this case).
- robto 8y agoI think "don't break anyone's code" > "break code but tell user about" > "silently break code". Rich Hickey has very strong feelings about breaking other people's code, and I tend to agree with him about: don't do it. Breaking other people's code is mean and unnecessary.
- matt-noonan 8y agoI don't disagree, except to say that "don't break anyone's code" is a bit better than "break in a way that the compiler catches and helps you fix", which is a GREAT deal better than "silently break code". Paranoia about breaking code makes sense in languages that have few mechanisms for aiding in refactoring. It makes less sense when the language is acting as your teammate.
- JBiserkov 8y ago>If `Maybe` is an output then existing callers SHOULD be broken, because they must now handle a new possibility of failure they didn't before. Look at the example again. The output was Maybe y "yesterday", "today" the output is y. Existing callers shouldn't have to change - there is no new possibility they need to handle - they no longer need to deal with the possibility of Nothing.
- kybernetikos 8y agoIt's an interesting one - they shouldn't be broken, but they should update their code when they can since they have a bunch of code to deal with a case that can't happen any more.