6 ms·
I think dart made the wrong choice here, but I've never used the language personally or professionally. I give the author the benefit of the doubt but Optional
by bern4444 6y ago
I think dart made the wrong choice here, but I've never used the language personally or professionally.
I give the author the benefit of the doubt but Optionals are so much more powerful than what this article covers. You can map, filter, reduce, chain, compose functions that all work with optionals, and lift functions that work on numbers (or any other type) to be functions that work on Optional<number> but none of that is even mentioned in this piece (likely cause then its harder to justify picking nullable types instead)
I also found the Some(Some(3)) example just plain wrong. In those scenarios, typically you just use chain (aka flatMap) instead of map... Seems odd this was even included as a motivating reason when this is very much a solved problem.
I'm certainly biased, as I think FP is an incredible mental model for coding and it seems to be catching on in lots of places too.
I understand language designers have to make choices that benefit their users now and be as simple as possible to opt into without any overhead to account for changes, but man, what a missed opportunity to give developers better tools and mechanisms in addition to opening up the doorway for many people to functional programming. But even Java has support for optionals.
Overall, this appears to be like a missed opportunity and the justification doesn't seem to really reflect an understanding of the benefits optionals provide, and misconstrues their use and capability.
- rokob 6y agoI generally agree with you but the one point they make in this article that really matters for Dart is it does not have pattern matching. Dart’s nominative type system makes much of standard FP much harder than you’d imagine. I know Rust is nominal too but Rust has a lot of complexity to support these things. It is very simple to add a Maybe and Either type to Dart (my code base has variants of these to deal with null issues) but imperative destructing is a pain. You can get most of the benefits of those techniques in user land without the language knowing about it, but I agree it still misses out on some stuff. If Dart already had pattern matching or was willing to add it first then the ADT version could have been chosen, but without that prerequisite nullable types was the only ergonomic choice.
- markdog12 6y agoDart is looking at adding pattern matching and "record" types: https://github.com/dart-lang/language/tree/master/working/0546-patterns https://github.com/dart-lang/language/tree/master/working/05...
- bern4444 6y agoThat's an interesting counter point, though another comment in a different post for this same article[1] pointed out that Dart is considering adding pattern matching too. Though I have trouble understanding how a nominal type system affects this? Does dart not have generics? That's all you need to support these patterns? I'm having trouble understanding how the type of type system plays a role here. Also, what's 'imperative destructing'? Haven't heard that term before, is that just pulling out a property from an object like this from JS? const { foo } = objectWithPropertyFoo; [1]https://news.ycombinator.com/item?id=25334283 https://news.ycombinator.com/item?id=25334283
- rokob 6y agoYes Dart has generics which is how you would implement optional types yourself. The type of type system doesn't play a role here you are right. I was thinking of the really good pattern matching I have experienced usually being present with structural subtyping. Dart is considering adding pattern matching (and real tuples!) and I think they will eventually get there. I just think that would have had to come first to make the case of optionals over nullable types more compelling. I would call that example destructing in the normal sense and it is basically a form of pattern matching. To do that in an imperative language without pattern matching (i.e. what I am calling imperative destructuring), you end up with code like if (maybeFoo.isSome) { final foo = maybeFoo.asValue; ... } when you really want if let Some(foo) = maybeFoo {...}
- codebje 6y agoYou may prefer for foo in maybeFoo { ... } This (AIUI) isn't valid Dart code though, but this would be: maybeFoo.forEach((foo) { ... })
- markdog12 6y ago> You can map, filter, reduce, chain, compose functions that all work with optionals Maybe they didn't mention it because both approaches can do that, or maybe I'm misunderstanding?
- bern4444 6y agoThe difference I see is, when you work with a nullable type, each function has to do the check so const add3 = (val) => val ? val + 3 : null; const multiplyBy10 = (val) => val ? val * 10 : null So the argument type goes from being val to val? where the calling code doesn't necessarily have type safety since this argument is now optional (it can be a number or null and either is acceptable) instead of the following (with no conditionals or ternaries) const add3 = val => val + 3; const multiplyBy10 = val => val * 10; This is possible cause optional.map implicitly handles the case where the data is missing by default, its built into the data type and the code you write doesn't need to worry about it. Its the responsibility of the Optional object.
- markdog12 6y agoAh, I gotcha, and I had already read another of your comments below. Yep, agree that is nicer in these cases. I still agree with the author that they made the right choice for Dart.
- munificent 6y agoYou could define operations on nullable types to allow this in Dart, like: extension NullableNumExtensions on num? { num? operator +(num? other) { if (this != null && other != null) return this + other; return null; } } main() { num? i, j; print(i + j); } For method calls, we have a null-safe method call syntax that automatically lifts the operation to give you a nullable result: int? i; var b = i?.isEven; // b has type null? We could have made all operations implicitly do this lifting, but our experience is that this isn't what users want. They want to know as early in their code when an operation on a potentially-absent value is attempted and reconcile right there what behavior they want.
- 6y ago
- chc 6y ago> I also found the Some(Some(3)) example just plain wrong. I don't see any such example in the article, and searching for that string yields nothing. What part are you talking about?
- bern4444 6y agoIt was the first bullet point in one of the bullet point sections. The text from the article was A Some(Some(string)) means the resource did exist on the server, and we have it in the cache now. Sorry about that, mixed up the types in my head
- WatchDog 6y agoAdding an `Option` type to an existing language doesnt really solve many problems unless you can also somehow remove it's existing support for `null. Java added `Optional` and while it makes interacting with newer API's clearer, there is nothing preventing you from passing a null Optional. I think if you are designing a language from scratch, you should avoid null, but nullable types are a good feature for existing languages. Having written a fair bit of kotlin, I have found the ergonomics of it's nullable type system to be really nice.
- Nullabillity 6y agoLanguage culture matters. Scala has both `Option` and `null`, but there's a strong culture of "`null` is only for Java interop", so you very rarely see a `null` in the wild from libraries, or wonder whether it'd be valid to pass it in.
- ebruchez 6y agoInterestingly, Scala 3 is adding union types as well as explicit `null`s: https://dotty.epfl.ch/docs/reference/other-new-features/explicit-nulls.html https://dotty.epfl.ch/docs/reference/other-new-features/expl... I think that the expectation is that idiomatic Scala code will mostly still use `Option`, while `null` will be used for interoperability with Java, JavaScript or Scala libraries that happen to use `null`. In any case, I am very much looking forward to these additions.
- matt_kantor 6y agoYour username is so incredibly relevant here that I had to check if this was a novelty account.
- munificent 6y ago> You can map, filter, reduce, chain, compose functions that all work with optionals, and lift functions that work on numbers (or any other type) to be functions that work on Optional<number> but none of that is even mentioned in this piece (likely cause then its harder to justify picking nullable types instead) Maybe I'm missing something, but I don't see how that's special to option types. Here's a set of extension methods in Dart that provide the operations you describe: extension NullableExtensions<T> on T? { R? map<R>(R Function(T) transform) => this == null ? null : transform(this); T? filter(bool Function(T) predicate) { if (this == null) return null; if (predicate(this)) return this; return null; } } extension FunctionExtensions<T, R> on R Function(T) { R? Function(T?) lift() => (T? param) => param == null ? null : this(param); } extension NullableIterableExtensions<T> on Iterable<T?> { Iterable<R?> map<R>(R Function(T) transform) => this.map((e) => e == null ? null : transform(e)); Iterable<T?> filter(bool Function(T) predicate) => where((e) => e == null || predicate(e)); } Chaining has built-in syntax: int? maybeInt = ... print(maybeInt?.isEven.toString()); I'm not sure what you mean by "compose". Of course, this is not entirely as expressive as option types because of the inability to nest, but the article is pretty clear that nesting is the major advantage of option types.
- dragonwriter 6y ago> I'm not sure what you mean by "compose". The simplest illustration is the ability to have multiple levels of semantically meaningful optionality: Option<Option<int>> is a meaningful type, and None, Some(None), and Some(Some(1)) are all meaningfully-distinct values. int? doesn't compose to int?? and has no good way to differentiate what Option<Option<int>> represents as None and Some(None).
- contravariant 6y agoIronically the main advantage of Monads is that Option<Option<int>> has a natural transformation to Option<int>.
- 6y ago
- kelnos 6y ago> I also found the Some(Some(3)) example just plain wrong. In those scenarios, typically you just use chain (aka flatMap) instead of map I think you misunderstood. The point here was that you can represent different things using nested Options: 1. Some(Some(3)) = I checked it and the value is 3 2. Some(None) = I checked it and there is no value 3. None = I haven't checked it It's very useful when building a cache, and also for things like JSON parsing (property was not present, property present but null, property present and has non-null value). You just can't represent that using Dart's nullable types system, because "int?" and "int??" are the same, indistinguishable type. Anyway... One thing not mentioned in the article is that the way they've implemented it in dart is via "magic syntax", which I consider to be a big negative. Option<T> is something anyone could implement without language support (assuming the type system already supports it). Anyone could build their own Option type and use it. (Sure, it's best to have this sort of thing in the stdlib so the stdlib will use it itself.) Nullable types with the "?" syntax makes the language itself bigger, and is something a regular user of the language couldn't implement if they wanted to. (Yes, I'm aware that the "?" suffix is just a shortcut for the more generalized union type syntax, but I think my argument holds.) The use of these types in regular code also requires what I consider a sort of special unintuitive requirement on how you structure your code. I have to do explicit null checks and then the compiler/interpreter just "knows" that code in certain places is null-safe through flow analysis. That kind of magic really turns me off. I also don't buy the argument that Option types are a pain to use if your language doesn't have pattern matching. I've been doing a bit of Java lately and have banned null from my code, instead using Option from the excellent Vavr library (I find java.util.Optional to be lacking, ditto for Java's built-in collections library). I tend not to use Vavr's pattern matching, as I find it clunky. But I also have no trouble using Vavr's Option. I guess I just prefer to use languages that have strong type systems where I can express constraints and safety through the types themselves, and not have to rely on language/compiler features to implement those constraints. I think the latter also gives you less flexibility as well.
- mmis1000 6y ago> I have to do explicit null checks and then the compiler/interpreter just "knows" that code in certain places is null-safe through flow analysis. You need to do it anyway or you got a crash. Because remove null from a existing language completely is nearly impossible. And now the compiler is able to know something went wrong in your code instead of happily see you use `null` as a object and crash the whole program. Besides that, that also benefit the library users that the compiler/ide will tell them `you can't pass null here`
- zaphar 6y agoIf you are starting from scratch and designing a brand new language then ADT's are a clearly better choice than allowing null. However Dart is not a brand new language. It's a preexisting language that already has null. Given that context nullable types are the only option that doesn't break compatibility with previous languages and allows code to be made null safe. If you already have to have null values because of prior decisions then ADT's don't add null safety. There may be other reasons to want them that aren't about handling null safety. But you'll still have the problem that values can be null and you won't have given developers a tool to handle those potentially null values safely.
- natchy 6y agoThis comment is answering post’s title more honestly than the author. Less words too.