12 ms·
Why nullable types?
- ibraheemdev 6y ago> it is entirely possible to live without null, and languages like Rust do. It seems as though there is this common misconception that Rust does not have a concept of null. Rust does have null pointers [1]! The reason many people do not see null often is because working with and dereferencing raw pointers is an unsafe operation 1: https://doc.rust-lang.org/std/ptr/fn.null.html https://doc.rust-lang.org/std/ptr/fn.null.html
- joshgev 6y ago> It seems as though there is this common misconception that Rust does not have a concept of null. I don't think that's quite relevant, though. Rust doesn't have a concept of nulls like like Java, Javascript, Python, etc do in which just about any variable might contain a value or null. Rust certainly doesn't have that.
- ibraheemdev 6y agoI would not say that it is not relevant. Raw pointers are a fundamental part of the language. It is just that Rust abstracts over them so that many users do not have to worry about them. I see your point though.
- viraptor 6y agoI don't think people mean it doesn't have a raw null pointer. Normally it's about a null object reference that you can assign to (almost) anything like Java's / c's null.
- Cyph0n 6y agoOf course Rust has a null pointer type. Otherwise, it wouldn’t really be a systems programming language :) But most users would rarely if ever need to use that type. Option<T> is how Rust deals with optional/nullable values.
- ChrisSD 6y agoNote that nowadays the preferred way to handle null pointers in APIs is the `Option<NonNull>` type. This unfortunately wasn't available in Rust 1.0. https://doc.rust-lang.org/std/ptr/struct.NonNull.html https://doc.rust-lang.org/std/ptr/struct.NonNull.html
- snarkypixel 6y agoI guess a third option would be to use an "Option" but with much more syntactic sugar. So, rather than calling func(some(3)), you'd just call func(3) and the compiler would automatically wrap it up. Func would be declared this way: def func(int?) to indicate that it's an optional type. IMO you get the best of both world. And then, in many places in the code, you'd have to do "r?.foo()" if r is an Option type. It's different than javascript where the "?" isn't enforced by the compiler.
- sabellito 6y agoTypeScript and, AFAIK, Kotlin provide exactly that. The problem with that special syntax sugar is that then there's no monad to compose with. But it does feel nice for many use cases.
- int_19h 6y agoTypeScript also uses union types for null & undefined. But then on top of that, it has optionals. So you can have: function foo(x: number | undefined); or you can have: function foo(x?: number); The type of x inside foo is the same in both cases, but the type of foo differs. The first version can only be called as foo(x) - where x is possibly undefined - but the second one can also be invoked as foo(), which then has the same meaning as foo(undefined). But this is mostly because they were trying to come up with a type system to capture existing JavaScript patterns. I doubt it'd look like that if it could be designed from scratch.
- bern4444 6y agoIf you have a function that works on numbers: const add3 = val => val + 3; and need to make it work on optional<number>, all you need is map const optionally6 = Some(3).map(add3); where Optional.map works just like array.map. Its the same idea when you map over an array: const add3 = val => val + 3; [1, 2, 3].map(add3); Though an even nicer approach is to use static versions of these functions: const add3 = val => val + 3; const add3ToArrayVals = Array.map(add3); const add3ToOptionals = Optional.map(add3); const someSix = add3ToOptionals(Some(3)); // Some(6); const some456 = add3ToArrayVals([1, 2, 3]);// [4, 5, 6]; This is just a static version of Array.map (doesn't officially exist in JS but is trivial to write) or Optional.map const staticArrayMap = (fn) => (array) => array.map(fn); Edit to add, from your example func(some(3)) would just become some(3).map(func); and then you can do some(3) .map(func) .map(otherFunc) .map(yetAnotherFunc);
- recursivedoubts 6y agoI sometimes wonder if the problem is that we don't have enough nulls. null is typically used as a flag value, but the meaning can be ambiguous: maybe it's the absence of a value, maybe an error occured, etc. Sometimes it has more than one meaning for the same type. Maybe types should be allowed to declare multiple nulls (effectively like an Enum in java) for different flag values. Operations on the different nulls would throw specialized exceptions indicating why that particular null value exists. Equality... hmmm.
- sabellito 6y agoI'm not 100% sure if you're being serious, but in case you are, Algebraic Data Types exist to accommodate those ideas.
- reificator 6y ago> null is typically used as a flag value, but the meaning can be ambiguous Languages like Haskell or Rust handle this in a straightforward way by removing the null and using sum types to replace them. > maybe it's the absence of a value Option<T> contains a possible value, or nothing. https://doc.rust-lang.org/std/option/ https://doc.rust-lang.org/std/option/ > maybe an error occured Result<T, E> contains either a value or an error. https://doc.rust-lang.org/std/result/index.html https://doc.rust-lang.org/std/result/index.html > Sometimes it has more than one meaning for the same type. Result<Option<T>, E> is a result that might have a value.
- kerblang 6y agoCompletely reasonable request, easy to implement along the lines of: public class Name { private Name(String name, int flag){...} public static Name asMissing(){new Name(null, -1);} public static Name asNotApplicable(){new Name(null, -2);} public static Name asNotAvailableYet(){new Name(null, -3);} public static Name asForbidden(){new Name(null, -4);} public static Name create(String name){new Name(name, 0)} public boolean isMissing(){flag==-1} public boolean isNotApplicable(){flag==-2} public boolean isNotAvailableYet(){flag==-3} public boolean isForbidden(){flag==-4} public String get() {if (flag!=0) throw new Exception(flag) else name} }
- bern4444 6y agoI 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...
- markdog12 6y agoPrevious, with a comment from the author: https://news.ycombinator.com/item?id=25334248 https://news.ycombinator.com/item?id=25334248
- identity0 6y ago>There are two main solutions: Use an option or maybe type or Use a nullable type I don't get it. These are literally all exactly the same thing, all slightly varying in ergonomics and compiler support. They may differ slightly but to call them two different categories of solution is just creating a false dichotomy for yourself. > However, the type system is a little more flexible than with option types. The type system understands that a union type is a supertype of its branches. In other words, int is a subtype of int?. That means we can pass a definitely-present-integer to something that expects a maybe-present-integer since that’s safe to do. You can do that in Swift, despite being placed as an example of "Solution 1". > The short answer is that, yes, it is entirely possible to live without null, and languages like Rust do. If you can call say that Swift has "null", then you can equally say that Rust has "null"--it's called "None". The only way "None" and "null" from other languages differ is in built-in compiler support.
- creata 6y agoThey're not the same, and the biggest difference is explained in the Map<int, Option<String>> example: union types merge different uses of null that you usually don't want merged.
- tmpfs 6y agoThis comment is misleading. They may appear to be the same but they are not because in certain languages (like Rust) you are forced to handle the `Option` case when a value is `None` which guides a programmer's thinking in the direction of what to do in that situation. Without these higher level types runtime null pointer exceptions are very common, using `Option` or `Maybe` creates a situation where these sorts of errors are a lot less likely.
- munificent 6y ago> They may appear to be the same but they are not because in certain languages (like Rust) you are forced to handle the `Option` case when a value is `None` which guides a programmer's thinking in the direction of what to do in that situation. Languages with nullable types are equally strict: int? maybeInt; maybeInt + 3; // <-- Compile error.
- markdog12 6y agoFor anyone interested, there's also an excellent article on how nullable types work in Dart, that's more of a deep dive: https://dart.dev/null-safety/understanding-null-safety https://dart.dev/null-safety/understanding-null-safety
- valenterry 6y agoGood article, but there is no "choice". You need both and the article even explains why! You need union types if you have subtyping to avoid wrapping/unwrapping all the time. And you also need option/maybe types for the cases where you must not lose information at runtime. These types can implemented by union types even, by simply "tagging" (= wrapping) the runtime values with a class). A good language gives you both out of the box.
- creata 6y ago> You need union types if you have subtyping to avoid wrapping/unwrapping all the time. In my experience, the option types are always more concise. That's mostly because having option types encourages people to build out nice helper functions like these[0] and these[1]. Haskell's do notation and Rust's Try notation also save a lot of space. [0]: https://doc.rust-lang.org/std/option/enum.Option.html https://doc.rust-lang.org/std/option/enum.Option.html [1]: https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Monad.html https://hackage.haskell.org/package/base-4.14.0.0/docs/Contr...
- valenterry 6y agoI was actually not referring to specifically option types but generally sum types. E.g. inside a function I have a result of (union) type "Integer | ErrorA" in one branch and "Integer | ErrorB" in another branch. ErrorA and ErrorB are completely unrelated types (coming from thirdparty libraries, can't change them). With union types I don't have to do anything and the return type for my function will be inferred as "Integer | ErrorA | ErrorB". Or if you prefer to work with a result type / either type, then it will become "Either Integer (ErrorA | ErrorB)". In most languages I'd have to make it "Either Integer (Either ErrorA ErrorB)" and also change the function code by lifting accordingly. That's verbose and many times I don't want to do that.
- Cyph0n 6y agoIn Rust, you would implement From<ErrorA> and From<ErrorB> for a custom Error enum defined in your application/library. This will allow you to convert any ErrorA or ErrorB into Error. Then, you could simply return Result<Integer, Error> from your function, and the conversion would happen for you behind the scenes. Of course, unless you track the original error types, you will lose some information. But it’s a very clean way to handle multiple error types within your library imo. An example of how this works: https://doc.rust-lang.org/rust-by-example/error/multiple_error_types/wrap_error.html https://doc.rust-lang.org/rust-by-example/error/multiple_err...
- creata 6y agoI agree that union-typed null was the best solution for Dart, but I think many of the article's criticisms of option types aren't good. > We can’t perform arithmetic on an Option<int> any more than we could on a List<int>. Why not? One sensible definition is Some(x) + Some(y) = Some(x + y) _ + _ = None() > In fact, with Dart, we’ve found that most existing code is already statically null safe This is a very good reason to use union types — lots of programs and lots of people are used to thinking this way, and pulling that out from under their feet wouldn't be very nice. > Nullable types, since they have no explicit runtime representation, are implicitly flattened. Yes, flattening things that shouldn't be flattened is the biggest problem with union types. Different people will assign different meanings to null, and it's a real headache when trying to write generic code. Discriminated unions make this a no-brainer. > With nullable types, since there is no representation difference, you can pass a value of the underlying type directly: takesMaybeInt(3); That's a very minor difference, but if it's that important, Rust lets you write fn takesMaybeInt(x: impl Into<Option<i32>>) { ... }
- gnosek 6y agoI agree with all your other points but > > We can’t perform arithmetic on an Option<int> any more than we could on a List<int>. > Why not? One sensible definition is (...) It's not the only sensible definition. It would be equally sensible to implement it as: Some(x) + Some(y) => Some(x+y) Some(x) + None => Some(x) None + Some(y) => Some(y) None + None => None
- creata 6y ago> It's not the only sensible definition Right, which is why I said it's just one sensible definition. :) Although I guess there's at least two sensible definitions of arithmetic on List<int>, too.
- runarberg 6y ago> > > We can’t perform arithmetic on an Option<int> any more than we could on a List<int>. Is actually kind of a weird point to make. There are number of ways to add together List<int> including: listA.zip(listB).map(([a, b]) => a + b); Similarly adding together two optionals could be done with: optionalA.zip(optionalB).map(([a, b]) => a + b); Or if you would rather: optionalA .zip(optionalB) .map(([a, b]) => a + b) .or(optionalA) .or(optionalB)
- bedobi 6y agoMe and my team prefer Option to nullable types. Option is much more clear and idiomatic FP than nullable types. Granted, Option.map and flatMap and nullable type ?.let{} are functionally equivalent but the former just reads so much better, and we want not just functor and monad but applicative as well, no? Eg given a List<Option>, we can use applicative to declaratively and with referential transparency etc etc (you know the usual FP sales pitch) turn it into an empty list if it contains a single non-populated option, or a list of Foo if all options in the list are populated. With nullable types I don't think you could do it as declaratively and elegantly. Actually, even if you could, it's kind of beside the point... To me, the point is, Option has been designed from scratch to be an FP style Maybe type with all that comes with that in terms of it being a functor, applicative functor, monad etc etc whereas while nullable types in some ways are functionally equivalent, a nullable type doesn't implement a Functor interface, or a Monad interface, or an Applicative interface, and when it behaves like it does, it's mostly kind of by accident driven by a pragmatic need, not any real understanding of reusable, lawful FP abstractions.
- WhoCaresLies 6y agoNullable type is the biggest mistake in all the "modern" languages It should be a type Optional<T>, not built into a language, it make no sense memory wise It's like NULL, it should stay, it is not an issue, it is how computers work!!!!!!!
- mrkeen 6y agoYou seem to have a very strong opinion about the issue, but I cannot tell whether you prefer a missing value to be represented by null or by Option.empty.
- dep_b 6y agoWorking with Dart now and it looks like it’s straight from the 90’s in a lot of aspects. It is not s bad as vanilla JS but when given a clean slate to design a new platform so many better choices could have been made...
- skocznymroczny 6y agoI use Dart every now and then and for me "straight from the '90s" is a benefit. I learned the language in about a day. IDE works great, no issues with autocomplete whatsoever (which is often an issue in "modern" languages).
- dep_b 6y agoMost mobile developers come from Kotlin or Swift, that are pretty similar to each other and Kotlin is the official language for Android anyway. I had no trouble doing Kotlin coming from a Swift background, the differences are related to the fact it's a JVM language running on Android but the ideas are largely the same. It would make bridging for native plug-ins a lot easier as well. I don't know how Google Flutter interfacing Google Android could look so medieval when they're basically the same company. They managed to make Xamarin look great in comparison.
- anfilt 6y agoIf we look at set theory the NULL set is a valid set. The problem with NULL is often it's just treated as zero or if passed to a language that does not have NULL it's pretty often to just treat it as Zero. This bit equivalence between zero and NULL zero is often what you see lead to problems. It's why for instance with GPS coordinates NULL island exists. However, errors aside NULL is a perfectly logical value to encounter or need.
- mrkeen 6y ago> NULL is a perfectly logical value to encounter or need. Great! Assign to it the type Null. This is legal: Null myNull = null; Make this illegal: Integer i = null;
- munificent 6y agoThis is essentially what Dart does: https://api.dart.dev/stable/2.10.4/dart-core/Null-class.html https://api.dart.dev/stable/2.10.4/dart-core/Null-class.html The main change with adding nullable and non-nullable types to the language is that we no longer treat Null as a subtype of all types. Instead, you have to opt in using "?", which is basically syntactic sugar for "| Null".
- anfilt 6y agoNULL can be its own type, but the union between the set of integers and NULL is equally valid. However, that new set created by this union is not just integers. To represent a type that can be NULL or an integer it should be a different type. Like I said the problem again is mostly your data is not always contained to your language. If you export GPS coordinates some systems may treat this composite type incorrectly. So even if you have a strong type system people will just be lazy.
- stevefan1999 6y agoI have this funny question all the time: why dont we just use a bit boolean for representing option/nullable/unknown instead? (one bit of hasValue/isEmpty like C# HasValue, but like struct uncertain_value<T> { T value; bool dont_care: 1; } and uninitialized value f<T>() => uncertain_value<T> { dont_care = 1 })
- dllthomas 6y agoWhat do you see as being the benefits of this approach?
- mrkeen 6y agoGiven a 64-bit variable, you can fill it with 64 bits of value, or 64 bits of pointer to some value elsewhere. With your scheme that would be 63 (which I'm not objecting to). Pointers are already special-cased to only contain 0 already. There's no use-case for having 63 bits of pointer and an extra bit saying "ignore the other 63 bits". As for the value case, I think the argument is pretty similar. Why store 63 bits of value if the 64th bit says to ignore them? If you're not talking memory layouts and are looking at adding an extra field to a struct/class. Take you value field - if it's not initialised yet, and null doesn't exist, then what is its state?
- stevefan1999 6y agoif we extend the value to (value, is_null: bit<1>) we can have operated on the value field directly while also knowing whether the value is loaded or not. but you are right, maybe the compiler will outright "optimize" this to (value: align_pow2(sizeof T), is_null: byte) since the compiler will have to align value anyway, so that it will always have to expand the "sentinel". My original idea exactly contrary to your thinking: rather than making 0 NULL and give it the sentinel we always had, why don't we just avoid using it like using some data structure magic? You know, the null is infamous for causing the special case in integral constant expression that C++ inherited from C, and is confusing too, because then 0 is can be considered as (void ) and thus can cause an overload fiasco, that consider f(void) and f(int), what do you think f(0) would be? So the gist is I always think using x=0 to be able to represent NULL/false and !(x=0) to represent true is really really confusing (at least in C/C++). This also caused other languages to have some probable cases of negative number being cast to true so like `if (-1) console.log('lol')` it will print, wat? Also, with my configuration, not only we can always assume the value was intended to have a place to stay without indirection load, but the optional/null/don't-care/noninit will always have a stable state between 0 and 1 -- so we can clamp down without having to deal with extra states like I shown above, which is good from theoretical (because we can express its definite intent rather than guessing the type of the wrapped value) & logical standpoint (that we should not think in 3VL for null, rather, judge it by its concrete properties), not quite so in practice however (again, optimization kicks in nullifying all the good works), this might not entirely escape the pointer-not-null-then-consider-valid problem (what if we loaded a garbage value that might have a random 0/1? nowhere to tell also). In fact, this kind of configuration is not rare to be found: an open addressing hash table can use this kind of setting to represent "tombstone"/deleted field, but in a more optimized manner that we "bit set" because we are going to allocate a contiguous block of memory anyway so like the current universe can have 32 entries then we use lg(32) bytes bit-set at the beginning/end, if insert or delete value at entry x then toggle bit-tombstone of x, if search value y then find first i, x in entries if compare(x, y) = exact and !bit-tombstone[i] then (yes, i) else no...so on. But at the end of the day even that I don't like using 3VL to represent NULL, and my configuration is still like mapping 3VL into double binary anyway, iirc its like if value = U then don't care (either true and false; neither true nor false) else value. An analogy for NULL: if addr = NULL then value is no where to be found/random garbage value else load memory from addr.
- bla3 6y agoThis is a very readable writeup. From a distance, Dart seems to focus on usability, while other languages focus more on simplicity (Go) or expressive power (most modern languages). I appreciate the focus on usability. It's not unique to Dart (Python, ...), but many languages that are popular around here tend to favor expressiveness over usability.
- munificent 6y ago> Dart seems to focus on usability, while other languages focus more on simplicity (Go) or expressive power (most modern languages). Author here. I think we try to focus on all three, but the latter are largely constrained by the language as it was designed in Dart 1.0 (which was mostly done by a different set of people than the ones who work on the language now). I do wish Dart was a simpler language and worry a lot about the added cognitive load of the usability features we add. But it's really hard to take away features. In order to make non-nullable types a non-breaking change, we added a general versioning system to the language [1]. We have to use it carefully, of course, because users don't like migrating code. But I have some hope that we can use that to gradually simplify the language over time. Complexity has a direct usability cost. We also care a lot about expressiveness, but I think other languages tend to define that in terms of "how many different things can you express in a minimal amount of code"? With Dart, we weight more by how often you want to certain things, and by how you want to express that. We're willing to make some things more verbose if it makes common things shorter or more familiar. [1]: https://dart.dev/guides/language/evolution#language-versioning https://dart.dev/guides/language/evolution#language-versioni...
- nicoburns 6y agoYou don't necessarily have to take away features to reduce cognitive load. You can actually reduce it by adding features in some cases. For example, I believe that adding Sum Types would actually simplify most class-based languages, because it would mean that you can use a straightforward tool with a 1:1 mapping from real-world concept to language concept for representing "or" (a very fundamentally logical construct!). As-is you have to use an awkward inheritance pattern with classes. Or use a union and keep track of the tag manually. There's an extra feature, but the usages of the feature become straightforward. It's the same principle as writing longer but more straightforward code rather than a gnarly one-liner.
- deleted 6y ago[deleted]
- winstonewert 6y agoIs it me, or is the real answer: it's hard to retrofit optional types into a language, but you can add nullable types.
- refulgentis 6y agoNah, I was in early on both Swift and nullable dart - at the end of the day, Dart is preferable because it's using flow analysis to know when operations are safe, avoiding the noise of if...let and guard... (Yes, those are nice too, Dart is better! It feels much more ObjC-y)
- jrockway 6y agoBack when I was really into FP, but didn't use functional languages, I decided that the humble list was the answer to all my problems. Operations over lists behave the same regardless of their length, and a 0 element list is not a special case. Say you want to send mail to someone's mailing addresses: addresses = user.addresses() for address in addresses { send_mail(address) } If they have no addresses, the program doesn't blow up. If they have one address, they get mail! Of course, it's possible that having two addresses is just as annoying as having 0 addresses, in which case lists don't help you. Someone will add a second address, and then you won't know where to walk to to go see them. I'll also point out that it's the same number of lines of code as just handling null: address = user.address() if address != null { send_mail(address) } So it's kind of a wash, and I haven't really thought about it since.
- brazzy 6y agoIn many cases you'd want your program to blow up in some form when trying to send mail to someone without an email address, because that would be better than silently assuming they received a message you never ended up even trying to send.
- alexvoda 6y agoWhile this is true, this kind of flexibility makes the other layers harder. On the database side, if you use a relational store, it forces you to normal form 5 which few people are used to. On the front end, allowing everywhere an arbitrary number elements instead of mandatory one, means you need custom widgets everywhere. You need a way of marking the default one. Validation also gets exponentially more complicated. On the UX side, people are simply not used to this level of flexibility. It will get labeled as impenetrable. As much as I like the idea, and believe it is elegant, most probably YAGNI.
- BoorishBears 6y agoI know this is just a throwaway example, but feel just looking at the code layer this kind of elegance is comes at the cost of clarity and conveying intent, and I see that happen a lot. The second method explicitly checking for null is great because it's telling me so many things very plainly: 1. There can be no address for a user 2. There is otherwise one address per one user 3. If there is no address we explicitly do nothing (if clause with no else) If we do the same analysis for the first method it actually feels like it's conveying false conclusions. It's implying there's multiple addresses per user, for example. And the fact that if a user has no address we don't do anything is still true, but the intent feels buried in the behavior of the loop. By default I try to value writing code in a way that conveys the most intent possible over how it feels to write For example, another language with nullable types (Kotlin) has user.address()?.let { it.sendMail() } I've seen people write code like this, and it just feels so unnecessary, how much longer does it take to parse? And it still doesn't have the clarity of intent that a simple null check has.
- tybit 6y agoWow tough crowd. I appreciate the pragmatic approach taken here, and as long as there is type safety round nulls im happy to use the language. Nitpick: As of C# 8 they’ve mostly solved nullable types for classes too with nullable reference types (though retrofitting it made it awkward and imperfect).
- munificent 6y agoIf you talk about type systems, the static FP folks naturally show up and according to many of them, anything that SML/Haskell doesn't do must be wrong. :)
- spqr233 6y agoI think saying that Option types are different from nullable types is not true. They differ at the semantic level, around how you can interact with them (one requires you to case on whether or not the value is there, the other requires you to do that with an if statement), but at the type level the describe the same construct. I think saying something is not the same as something else, when evidently the difference can to reduced down to syntactic sugar is a bad way categorizing and differentiating types.
- chills 6y agoThe article mentions unions vs discriminated unions and then mentions nesting nullable being different than nesting optional, but it doesn't really tie the knot. Imagine (sorry for pseudocode): type Option T = Some T | None type Nullable T = T | null The difference here is that Option "tags" each part of its union, which guarantees that the parts are disjoint. In Nullable, the parts of the union are disjoint only if T is not itself nullable. If T is S | null, Nullable T is S | null | null, which is just S | null (since null and null are not disjoint.
- MereInterest 6y agoI think I'd put it even more on the syntax. Option requires you to explicitly deal with the fact that it is an Option. For nullable types, frequently the syntax doesn't require you to acknowledge that it is nullable at all, and will allow you to continue forward on the assumption that the object doesn't contain null.
- 53AFCB60 6y agoOptional and nullable types are very different. When you wrap a type T in Optional, you make a completely new type that just happens to contain T inside, which means you cannot use Optional<T> in places where you previously accepted T, because you break a contract for the caller. With nullable types (which essentially represent a set `{t | t in T} v {NULL}`) it is possible. This is similar to the notions of co- and contra-variance for inputs/outputs of a function. Rich Hickey had a good talk about problems of representing a missing information in programs, and why union types allow you to incrementally make changes in your codebase without refactoring the whole thing at once.
- dfgdghdf 6y agoThis is not intended as an attack, but a genuine question: what does Dart offer over any other language? Why would I choose Dart?
- refulgentis 6y agoHad wondered this for a bit myself - I'm a bimobile developer, ObjC, Swift, to Java, to Kotlin over the last decade, now on Dart. Dart is by far the most fun I've had in a language because it has an extremely simple surface area, and they really optimize for that, ex. here. But, Flutter is the revolution - utterly paradigm-shifting tool. Much like ObjC, you probably use Dart because it's what Flutter does, but not like ObjC, the simplicity, ease, and clarity of thought it allows is a benefit, as well as Swiss he army knife way the language can be deployed (Android app? Web app? Linux CLI tool? Windows 10 app?)
- deleted 6y ago[deleted]