
Dart nullability syntax decision: a?[b] or a?.[b] - mpweiher
https://medium.com/dartlang/dart-nullability-syntax-decision-a-b-or-a-b-d827259e34a3
======
jillesvangurp
Kotlin doesn't have the [] syntax. You just go
someObjThatMayBeNull?.aMethod()?.aProperty : orSomeOtherExpression.

I think that wins in terms of readability. Also, every Android developer is
already used to that syntax.

I hope Google at some point opens up Flutter to other languages. IMHO it's
being held back by Dart at this point and they are barking up the wrong tree
by not addressing that directly. People are reluctant to switch to it because
they have to switch language to something less nice. The solution is not
flogging the dead horse that is Dart by slowly adding bits of features that
they hope will bridge the gap.

Kotlin now has a native compiler and reengineering Flutter to play nice with
the LLVM ecosystem would also enable other languages. Like Swift, Rust or C++.
Also, WASM is becoming a thing in the LLVM world. A Flutter evolution that
properly integrated into that eco system would be quite a useful thing to
have.

~~~
cbolton
Not sure about Flutter being held back by Dart. From my experience the
language works really well for Flutter.

For example Dart has first class support for asynchronous streams, very useful
for a reactive framework. Flows in Kotlin are still experimental I think?

The Dart version of async/await works really well, just what you need in
Flutter code and simpler than suspended functions in Kotlin.

The cascade operator is since syntactic sugar and, again, simpler and lighter
than Kotlin's "apply".

Then there are Flutter's killer features, mainly hot reload and soon the Web
support. Is Kotlin.js in the same league as Dart? Honest question! Dart's
JavaScript support is very mature, with 2 backends: one for development and
one for production. And what's the hot-reload story on Kotlin's side? I could
not find an answer on Google, but I did find[1] these words from the "IntelliJ
IDEA product manager @ JetBrains, co-author of Kotlin in Action":

"Flutter relies heavily on features that are only provided by the Dart VM.
It’s not possible to add Kotlin support to Flutter without losing a lot of the
things making Flutter attractive."

So from a marketing standpoint maybe Dart is a liability, but technically I
think it's a strength. I hope marketing won't have the last word!

[1]
[https://twitter.com/intelliyole/status/1105493258954780672](https://twitter.com/intelliyole/status/1105493258954780672)

~~~
hota_mazi
> I hope marketing won't have the last word

It's interesting you think JVM support is just marketing.

For millions of developers around the globe, JVM support is a feature, and one
they really can't do without.

Dart is a liability to them because unproven, and its future is equally
uncertain given Google's track record.

~~~
munificent
Every proven technology was unproven once. I remember when people said Java
was too risky, GC was too slow, and it would never take off.

~~~
zaphirplane
Which side are you arguing for ;) couldn’t resist

------
bsaul
It's so weird when you've been using a feature for years in a language (swift)
and you see another language try to come up to solutions to a problem you
never had..

Chaining ?[] sucks no matter what you do, and as for parser ambiguity, my
guess is that if the parser doesn't know how to understand an expression, then
the best thing is to have it fail and ask the programmer to add parenthesis.
Because chances are it won't make any sense to a human as well.

~~~
nnq
> you see another language try to come up to solutions to a problem you never
> had

...didn't Swift have the same problem to witch it came with the same solution?
(I mean `?.`)

~~~
bsaul

      let a:[Int]? = nil
      let ai = a?[0]
    

and

    
    
      let b: [Int:[Int]]? = nil
      let bi = b?[0]?[0]
    
    

are valid in swift. ?. is used when you want to access a property, but i've
never thought of it as an operator. Rather, i see it as "a?" (aka : unwrap a)
followed by "." dot operator to access a property. I basically just replace a
with a? every time a becomes nullable, and that's it.

------
d--b
> Finally, consider this example of chaining the subscript:

> someJson?[recipes]?[2]?[ingredients]?[pepper]

> To our eyes, that doesn’t look very good. It scans less like a method chain
> and more like some combination of infix operators — a little like ??.
> Compare it to this code:

> someJson?.[recipes]?.[2]?.[ingredients]?.[pepper]

Yep. So in the end ?[] does not look better, and the dot avoids the ambiguity
with ? [] :

There you go. Dot. You could also allow `a.[x]` as a valid alternative to a[x]

~~~
yoz-y
Why exactly is it a problem when the intent here is to actually chain infix
operators.

~~~
ivanbakel
Because `?` is not an infix operator between two things of the same (or
reasonably similar) type, which is what programmers expect from infix operator
chains.

~~~
yoz-y
I see. Coming from swift the ?. syntax feels weird to me because it implies
there is some property accessed or method called when there is not.

Ambiguity with ternary operator is unfortunate but this one is lifted (in
Swift) by harder rules on whitespace. A postfix operator must not have space
between the variable and the ?: operator must have space around.

~~~
munificent
_> Coming from swift the ?. syntax feels weird to me because it implies there
is some property accessed or method called when there is not._

There is. "[]" is a method in Dart. In Dart, almost all operators are
syntactic sugar for corresponding method calls on the left-hand operand. In
Dart, lists and maps are built into the core _library_ , but the "[]" syntax
is not special to those types. Any user-defined class is free to implement the
subscript operator too.

In practice, this is useful because it means user-defined classes can
implement the List and Map interfaces. This gives user collections the same
ease of use as built-in ones.

------
ernst_klim
Why not explicit

    
    
       Option.map (fun l -> Array.nth l n) your_optional_list
    

And why treat something nullable as a special entity when it's just a
container?

And why would anybody want a nullable collection or a special syntax for 'em,
when any collection is already nullable, and an empty list or array is
basically the same thing as None/Null?

~~~
the_duke
For Option types to work well and not be an awkward footgun (like C++
std::optional), you also need sum types and pattern matching.

While Dart should gain those, that's a much more invasive language change I
reckon.

~~~
nybble41
> For Option types to work well and not be an awkward footgun (like C++
> std::optional), you also need sum types and pattern matching.

Which you can derive from lexical closures (which Dart has) using Church
encoding:

    
    
        typedef Optional<T> = R Function<R>(R, R Function(T));
        
        Optional<T> None<T>() {
          return <R>(R none, R some(T x)) => none;
        }
        
        Optional<T> Some<T>(T x) {
          return <R>(R none, R some(T x)) => some(x);
        }
        
        Optional<B> MapOptional<A,B>(B f(A), Optional<A> option) {
          return <R>(R none, R some(B x)) => option(none, (a) => some(f(a)));
        }
    
        String OptionalString<T>(Optional<T> option) {
          return option('None()', (x) => 'Some(${x.toString()})');
        }
        
        void main() {
          Optional<num> opt1 = None();
          Optional<num> opt2 = Some(4);
          print('opt1: ${OptionalString(opt1)}');
          print('opt2: ${OptionalString(opt2)}');
          Optional<num> opt3 = MapOptional((x) => 2*x, opt2);
          print('2*opt2: ${OptionalString(opt3)}');
        }
    

Optional<T> is a sum type, and you pattern match on it by calling the function
and providing expressions for the None and Some cases as parameters.

There may be some implementation limits... I managed to crash the compiler at
[https://dartpad.dartlang.org/](https://dartpad.dartlang.org/) with the first
version which had the expression for opt3 inlined into the call to
OptionalString(). Declaring it as a separate variable worked around the issue.
It's also apparently disallowed to nest generic function types, so you can't
have Optional<Optional<T>>; this might be circumvented by wrapping the
function type in a class.

------
11235813213455
JS went for a?.[b] [https://github.com/tc39/proposal-optional-
chaining#faq](https://github.com/tc39/proposal-optional-chaining#faq)

~~~
asdfman123
I was trying to find which programming language did it first, because I knew
C# did it well before JS, although I definitely wouldn't be surprised if they
weren't the first.

However, in my search I found a list of languages and how they use safe
navigation operators:

[https://en.wikipedia.org/wiki/Safe_navigation_operator](https://en.wikipedia.org/wiki/Safe_navigation_operator)

~~~
kazinator
InterLisp allowed (car nil) and (cdr nil) to safely yield nil, thus making it
safe to probe list structure to any depth. This spread into other dialects and
is that way in Common Lisp

> _In 1974, about a dozen persons attended a meeting at MIT between the
> MacLisp and Interlisp implementors, including Warren Teitelman, Alice
> Hartley, Jon L White, Jeff Golden, and Guy Steele. [ ... ] In the end only a
> trivial exchange of features resulted from “the great MacLisp /Interlisp
> summit”: MacLisp adopted from Interlisp the behavior (CAR NIL) → NIL and
> (CDR NIL) → NIL, and Interlisp adopted the concept of a read table._

[ _Evolution of Lisp_ ,
[https://www.dreamsongs.com/Files/HOPL2-Uncut.pdf](https://www.dreamsongs.com/Files/HOPL2-Uncut.pdf)]

In the Common Lisp Object System, we can specialize any method to arguments of
the _null_ class, a type whose only element is _nil_. Thus for instance, if we
have a generic function _salary_ like this:

    
    
      (salary employee)
    

then if _employee_ is _nil_ , this simply calls for the following primary
method to exist:

    
    
      (defmethod salary ((emp null))
        ...)
    

This can return whatever you want: just _nil_ , or zero or whatever is
appropriate.

If the method is not defined, then (salary nil) signals a condition.

------
userbinator
_(One such corner is that — — a and --a are both valid but mean different
things.)_

I'm not familiar with Dart but I believe the same applies to all the C-derived
syntax languages; predecrement vs subtraction of negation. If I remember
correctly, the C standard even has "a+++++b" as an example (which is a syntax
error, since it parses as "a++ ++ +b", but "a++ + ++b" would not be.)

As a "completely uninformed outside" opinion, how about not adding another
operator, but automatically choosing based on whether the type on the left is
nullable? I feel like there's already a lot of "implicit nullness" going
around with this sort of language design, such that these operators are
redundancy overhead --- and according to the article, the declaration of the
type already tells you whether it's nullable.

~~~
munificent
_> I'm not familiar with Dart but I believe the same applies to all the
C-derived syntax languages_

Yes, you're correct. It's not a _unique_ property of Dart, but it's an example
of a case where the whitespace in your source code is important, even if you
just use it as an input to the automated formatter.

 _> how about not adding another operator, but automatically choosing based on
whether the type on the left is nullable?_

We could do that, but that just kicks the problem down the road. It means the
type of the surrounding expression would itself become nullable (because a
null-safe subscript operator can always evaluate to null). So the user still
has to handle it at some point. It's better to make them handle it at the
first place that null can appear so that they know where it's sneaking into
their code.

------
zellyn
Anyone know what the bang operator does? It's impossible to Google (unlike
"cascade syntax" a few lines down):

    
    
      Is similar to the syntax for the ! operator: e1![e2]

~~~
mraleph
It's a non-null assertion operator which would throw an error if `e1 == null`.

~~~
williamdclt
Would the operator itself throw the error, or trying to access `[e2]` on null?

I never used Dart, I would assume it's basically a cast from
`Optional<SomeType>` to `SomeType`, so wouldn't throw an exception itself

~~~
mraleph
The operator itself would throw an error.

Dart is planning to have a sound non-nullability which means that if something
has static type `SomeType` then it is guaranteed to be a value that is
actually `SomeType` and not something else.

Thus you can't take `SomeType?` and just cast it to `SomeType` because
`SomeType?` can be null and `SomeType` can't be null.

This means this cast has to perform a check and throw if your value is null.

This also means that if you have `e1` and `e1` has non-nullable static type
then `e1` would never be null in the NNBD world - which is a very good
property (e.g. for optimizations).

~~~
williamdclt
Interesting! So you were right to call it an assertion rather than a cast

------
pornel

       a?[b]
    

happens to be valid Rust already. Rust doesn't have the ternary `?:` operator,
because almost everything is an expression, so you can use regular if/else
anywhere. That ship has sailed for Dart, but the other syntax doesn't look too
bad either.

------
moomin
TL;DR; ? would be better, but it would completely break our parser.

(The feature that conflicts, array literals, are using the obviously desirable
syntax as well. This is just good intentions and path dependence in action.)

~~~
dmurray
It's not like it's an implementation detail of their parser, it would make the
language more difficult to write correctly and still be parsed by any parser,
including a human reading the code.

~~~
moomin
100% agree, it just an unfortunate interaction between

1) ?: syntax 2) [] for dereferencing 3) [] for literals 4) ? for null chaining

Each of the decisions is correct in isolation, but together they’re a
nightmare. Which decision you made first determines what you end up with in
the other areas.

~~~
munificent
Exactly right.

If I had a time machine, (1) is what I'd fix. The conditional operator syntax
has always been pretty hokey and if you just make `if` an expression (along
with most other statement forms), you have no need for it.

------
rufius
I'll be uh... honest, I didn't realise anyone still used Dart.

Not meant to be a dig - just forgot it existed.

As to the topic - a single question mark seems to make more sense.

~~~
bootlooped
The Flutter framework is helping keep it alive. I think it's also used a lot
in Google's Fuchsia OS.

I thought it was too bad it was not widely adopted as a JavaScript
replacement, like it was meant to be.

~~~
k__
Yes.

I have the feeling it occupied a weird place in the language ecosystem.

TypeScript was less ambitious and closer to JS so it got more momentum.

Dart is not as close to JS as TypeScript, but doesn't offer things like Reason
while still being as far away from JS as Reason (Own language semantics, with
own toolchain, etc.)

