Understanding null safety in Dart
dart.dev
dart.dev
What made Go more successful than Dart was a mix of decisions, luck, and momentum. On an alternate reality, Dart would swap places with Go. Dartium VM would have replaced Javascript on the Web and everyone would have been happy. Go would be seen as an obscure language with missing features that doesn't really do anything better than java/C#/C++
Almost every language is, that's not difficult.
You misspelled Kotlin.
For certain types of tasks, you want to choose a language that is not moving fast and breaking things. Such a language tends to be less "exciting" than others.
Was that true from the start or is it just that it's much older? How fast did Common Lisp move during its own first ten years, before the ANSI standardization?
I don't want a better node.js nor any node.js at all, really. Node.js called and it wants it's browser back. It's an event loop, for crying out loud, and has no business trying to pretend that it understands operating system processes and threads and such...
Dart reminds me of the worst of large corporate committee-ware. Go is at least a decent and respectable programming language, even if it IS garbage-collected...
I suppose I will get flagged for having strong expressed emotions about Dart. I dislike working with it, immensely, and this was even before Flutter... let's please avoid discussing Android Studio, chip heat dissipation, and global warming... Clearly the Goog has no influence on the fact that you need 6 cores running at 1.whatever gigahertz to open Farty Birds or whatever is hip. Flag away. Sorry, "null safety"... is that like "if (cat ==== 'undefined') {break exception omg try catch}?"
.... What does Dart do better than it's nearest competitor? What does Dart compete with again?
Silly stuff like "I like wide-code-formatting, not vertical stacks with 4 characters on a line or something. (cultural)
"Too many moving parts" in almost every implementation of anything, (as in "modern" javascript/front-end frameworks and their build-toolchains) such that its built for a large corp to use lots of warm bodies to "divide et imperis". (I can understand this with regards to Flutter, half of the complication of this cross-platform thing being Googles fault, of course. Android Studio needing it's own dedicated 32-core terabyte-ram compute cluster, naturally, and trying to find cross-platform layout containers etc without being verbose = good luck!)
Particular pain points? Let's go with some Flutter stuff, as it's arguably the primary use case folks will encounter Dart for, now that the Dart VM isn't going to be embedded in all the browsers suddenly, as was proposed at one point.
Arguably, I'm doing it all wrong, as someone will point out. Elaborate (client-side) modeling that will only work if everything is completely fleshed out:
We have our classes, but OH let's make the state a separate object and let's basically have you haul 3 paragraphs of junk around to represent one thing and some related stuff that should probably be part of the same object... Hey, who knows, maybe it'll be like CSS in some future revs in which you have to look for 20 class blocks extending the same class, but that's in Crystal etc, and it's raison-d-etre seems pretty established (extensibility) but it does make localization a PITAS.
...and then we have factory methods, and we can't really debug passing stuff through them until we get data, but it's not as simple as if(this.readyState == 4 && this.status ==200) {boom, does it look like JSON? is it an array instead? what do we have here?} so there's this "build an elaborate model on the client side, but then you are basically only validating and attempting to JSON.parse whatever your (likely http) API spits out.
factory TextLine.fromJson(Map<String, dynamic> json) {} Never mind that I have to finish tiling all model bits to pull something through and see what I have, although the flutter hot-reload helps speed things up a bit, which you then end up pulling data through what pretends to be fancy/modern but is actually no better than the browsers HTTP XHR methods
//the future Stream<List<Album>> fetchCards(int limit, int skip) async* { http.Response response = await http .get('https://mobile.somesite.com/albums/'+ limit.toString() + '/' + skip.toString() ); List responseJson = json.decode(response.body); yield responseJson.map((m) => new AlbumCatalog.fromJson(m)).toList();
Syntax is generally fine, text is better than SVG, but somehow working with Dart makes me feel depressed, in a Javascript kind of way... (lot's of tooling and attempts to not be what it really is underneath, hence my gut reaction to arrow functions and pretend-classes in Javascript, etc...)
Yes, there are still shortcomings, but to me, the biggest shortcoming is too much legacy crap that doesn’t use the standard library. For example, Express predates the standard request, response, and URL classes, so it invents its own. Plus Node doesn’t even fetch FFS. If everyone actually wrote things in modern JS, the situation would be better.
This cuts both way: it was great not having to think about typings all the time around input/output, but it was also cumbersome when implementing the internal implementation. I'm a bit biased because I've come to think in term of structural typing much more. Typing in Dart reminded me more of my time with C#.
Null safety was a huge missing piece though, glad to see it being added.
For example, an integer that's used to index into an array, so it must be positive. Or a string that's a valid street address. You can maintain that info by packing it into a unique structure (ie, wrap in an object, using a unique key), but that's awkward to access. Or you can always pass around the parent object (eg, House), which has a unique structure, but then you're introducing unnecessarily tight coupling which is a disaster to maintain.
- https://github.com/gcanti/newtype-ts
- https://github.com/Microsoft/TypeScript/issues/4895#issuecom...
but it isn't as nice as say Python's `typing.NewType` or Haskell's `newtype`
1: https://spin.atomicobject.com/2018/01/15/typescript-flexible...
2: https://basarat.gitbook.io/typescript/main-1/nominaltyping
- Overall the language is simpler and more regular, it's easier to read it.
- Async / await is way simpler and less mentally taxing than Kotlin's coroutines.
Unfortunately, the Dart web worker implementation isn't very convenient and I think some work could be done there to make it nicer to use. It's a very important use case for today's devices.
Also, Dart has code generation so you can write your own language features, like the freezed package which implements algebraic data types even though Dart doesn't have them natively.
I'm planning on writing a library in Dart that implements a custom TCP protocol, and I think ADTs would be the best fit for capturing all the valid states and error conditions. I really don't want to have a bunch of nullable fields or use the visitor pattern :/
I've been tracking this issue [0] and hopefully with the release of null safety, the team will have the time to implement ADTs!
Both feel highly verbose to me, but maybe that's just cos I'm used to something with quite minimal syntactic overhead.
Ultimately, they're very similar as languages so it wouldn't take long for a Typescript developer to picn up Dart or vice versa.
Personally I've been thinking about safe use of arrays. Arrays are such an important data structure and yet they're horribly unsafe. Every time I do index math I get that same nervousness that I used to get accessing fields in nullable data structures. I'd love for a way to verify array indices that doesn't involve too much dependent typing.
https://docs.racket-lang.org/reference/for.html?q=for%2Flist...
> (for/list ([i '(1 2 3)]
[j "abc"]
#:when (odd? i)
[k #(#t #f)])
(list i j k))
'((1 #\a #t) (1 #\a #f) (3 #\c #t) (3 #\c #f))That extends naturally to any number of types. For example, a type representing a network request can be “Loading” or “Loaded(‘data)” or “Error(‘error)” and you can’t just blindly access the data without also handling loading states and error states.
I absolutely love this when doing frontend work in ReasonML, and I miss it in JavaScript. TypeScript definitely has this functionality but I’ve never felt that it was natural to use.
Yep. Though, there are two ways to interpret that statement.
One of the things that has always slightly annoyed me about Go's messaging is that it conflates "complicated for the language's implementers" and "complicated for the language's users." To the point where it sometimes verges on being a sort of mild gaslighting.
I definitely agree with your feelings around array indices. I feel the same nervousness every time I do a slice or count backwards from the end of an array. I'm not really sure what the fix would be here, until I can just tell the computer what I want it to do, though...
e.g. even in GC'd languages there are libraries to provide immutable data structures because we understand that having multiple writers to data is a mess. The true ideal (for coding ergonomics) is exactly 1 writer, but there was no way to statically verify that.
I mean, a compiler that accepts no valid programs is problematic is it not?
The way that the Rust compiler does its thing does not really work for a garbage collected language, and the comment that I was responding to, which was itself responding a restarting of what I just mentioned, said that you'd want Rust-style verification anyways for those things, which is not really an answer to the question because that kind of thing is not something that Rust can do.
For example;
let x = 5; let y = vec![0; y * 2];
for i in 0..x * 3 { y[x] }
That shouldn't compile.
You don’t need strict reasoning for it to be useful like any other type level feature.
Because of the SAT solver you have some restrictions on what you can prove, but they're so much more ergonomic I think it's more than worth it.
What I've been wondering for my own language is whether I could design something like Rust's borrow checker but for ranges of values. That way I can guarantee valid indexing.
Also, one feature I would love to see implemented in a modern language is this:
numbers..toString() // Equivalent to numbers.map(x => x.toString())You mean something like
string.(1:5)
in Julia?In Raku (the language formerly known as Perl6), that is spelled
numbers>>.toString()
or using proper idioms @numbers>>.StrBut doesn't it seem reasonable to use TypeScript for flutter now? If you are not a Googler, Where do you use Dart (outside flutter) in production for a purpose where it is better than TypeScript?
Why? Libraries.
Flutter started as an experiment from chrome team, so their first language was JavaScript, when they move to dart they decided to write the whole framework layer in dart, the engine is in C/C++, so it seems there is no going back to a JS world.
https://flutter.dev/docs/resources/technical-overview#layer-...
With the Dart, they got a VM, GC and other tools and a language team that could grow Dart to suit flutter. They also got stateful hotreload, a Dart idea, which was something that the Flutter team didn't know they wanted.
Take this survey with a grain of salt, because the results come from IntelliJ users, and IntelliJs most important and popular product is IntelliJ IDEA, a Java IDE (and other things)
So the results will skew in that direction for that userbase.
Then again, none of the responses here are outside flutter development!
Though, note I am the author.
Of course, the Flutter team could build the toolchains they need themselves. Or they could just...use the up-to-par language they're already using instead of rewriting things for no real reason.
Is this not useless most of the time, since you'll usually get an exception (with stack trace) when the value is used?
Optional typing I never really cared about, because even in languages with var I find myself writing the types whenever possible.
I think you can't practically have null safety without flow typing, at least in an imperative language, otherwise you end up with casts everywhere. MyPy has null safety, though it takes some effort to turn it on for most codebases.
It's one of those features where once you get used to it, it's hard to fathom how anyone programs without it. It's such a simple yet expressive feature that's incredibly useful when writing normal, ordinary code. Nothing fancy or theoretical.
As sibling said, ML-style languages get away with this a different way - chained "container" operations in Scala, for example, are "right-biased", so the only get called if the result of the preceding operation was a success or not null.
I've heard it called "Railway Oriented Programming": https://fsharpforfunandprofit.com/rop/