<p>Consider the following code:</p>
<pre class="lang-dart prettyprint-override"><code>typedef Nullable&lt;T&gt; = T?;

Nullable&lt;T&gt; unit&lt;T&gt;(T x) =&gt; x;
Nullable&lt;U&gt; bind&lt;T, U&gt;(Nullable&lt;T&gt; x, Nullable&lt;U&gt; Function(T) f) =&gt;
    x == null ? null : f(x);

final int? x = null;
Nullable&lt;bool&gt; f(int? y) =&gt; y == null ? true : y &gt; 0 ? false : null;
Nullable&lt;String&gt; g(bool x) =&gt; x ? &quot;true&quot; : null;
final Nullable&lt;int?&gt; m = null;

void main() {
    // left identity
    print(bind&lt;int?, bool&gt;(unit&lt;int?&gt;(x), f) == f(x));

    // right identity
    print(bind&lt;int?, int?&gt;(m, unit&lt;int?&gt;) == m);

    // associativity
    print(bind&lt;bool, String&gt;(bind&lt;int?, bool&gt;(m, f), g) == bind&lt;int?, String&gt;(m, (x) =&gt; bind&lt;bool, String&gt;(f(x), g)));
}
</code></pre>
<p>The first print statement produced false, which proved that <code>Nullable</code> isn't a monad w.r.t. the given <code>unit</code> and <code>bind</code> function.</p>
<p>What is the reason that the first statement outputs false, unlike a monad?</p>

<monads><nullability>
Why isn't the nullable marker in Dart a monad?
CC BY-SA 4.0
2024-05-25T17:02:37.477Z
It sort of looks like `int??` is automatically implicitly "flattened" to `int?` in Dart, which is not really what you want for a monad. You want this to be *explicit*, and able to be accomplished by an operation often called `join`. You get `join` by applying `bind` to the identity function.
David Young
CC BY-SA 4.0
2024-05-25T20:21:38.147Z
So how does the flattening affect the laws of monads, compared to a `Maybe` monad?
Michael Tsang
CC BY-SA 4.0
2024-05-25T21:29:57.690Z
Is the question about explaining why this result doesn't meet the monad laws, or explaining why Dart did things this way?
Michael Homer
<p>This question has two possible meanings. The first is <strong>what prevents Dart's nullable marker from obeying the laws of monads?</strong></p>
<p>The answer is that Dart's nullable marker isn't a monad because <code>Nullable&lt;Nullable&lt;T&gt;&gt;</code> is the same type as <code>Nullable&lt;T&gt;</code> (<code>(T?)?</code> is the same type as <code>T?</code>).</p>
<p>Some languages such as Rust and OCaml have an &quot;option&quot; type, which <em>is</em> a monad. <code>Option</code> is the same as <code>Nullable</code> except in the case of <code>Option&lt;Option&lt;T&gt;&gt;</code> vs <code>Nullable&lt;Nullable&lt;T&gt;&gt;</code>. Specifically, <code>Option&lt;Option&lt;T&gt;&gt;</code> has two separate &quot;absent&quot; instances <code>None</code> and <code>Some(None)</code>, but <code>Nullable&lt;T&gt;</code> only has one <code>null</code>; there's no difference between the outer or inner &quot;nullable&quot; being null, whereas there is a difference whether the outer or inner option is <code>None</code>*.</p>
<p>Translating your example to Rust helps show why the distinction is important (<a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=fcfc9cd334547580bc94d3efdc3119e2" rel="noreferrer">Playground</a>):</p>
<pre class="lang-rust prettyprint-override"><code>fn unit&lt;T&gt;(x: T) -&gt; Option&lt;T&gt; { Some(x) }
fn bind&lt;T, U&gt;(x: Option&lt;T&gt;, f: impl FnOnce(T) -&gt; Option&lt;U&gt;) -&gt; Option&lt;U&gt; {
    match x { None =&gt; None, Some(x) =&gt; f(x) }
}

const X: Option&lt;i32&gt; = None;
fn f(y: Option&lt;i32&gt;) -&gt; Option&lt;bool&gt; {
    match y { None =&gt; Some(true), Some(y) =&gt; if y &gt; 0 { Some(false) } else { None } }
}
fn g(x: bool) -&gt; Option&lt;&amp;'static str&gt; { if x { Some(&quot;true&quot;) } else { None } }
const M: Option&lt;Option&lt;i32&gt;&gt; = None; // Or `Some(None)` since there's no difference in Dart

fn main() {
    // left identity
    dbg!(bind(unit(X), f) == f(X));

    // right identity
    dbg!(bind(M, unit) == M);

    // associativity
    dbg!(bind(bind(M, f), g) == bind(M, |x| bind(f(x), g)));
}
</code></pre>
<p>If you run the playground, all three statements evaluate to <code>true</code>. The reason <code>bind(unit(X), f) == f(X)</code> holds in Rust and <code>bind&lt;int?, bool&gt;(unit&lt;int?&gt;(x), f)</code> doesn't hold in Dart is precisely because <code>unit(X)</code> evaluates to <code>Some(None)</code>, while <code>unit&lt;int?&gt;(x)</code> flattens to <code>null</code>. Dart's <code>bind&lt;int?, bool&gt;(null, f)</code> then evaluates to <code>null</code>, as Rust's <code>bind(None, f)</code> would, except the Rust expression is actually <code>bind(Some(None), f)</code> which evaluates to <code>Some(true)</code>, the correct value.</p>
<p>* Furthermore, <code>Option&lt;Option&lt;Option&lt;T&gt;&gt;&gt;</code> has 3 separate &quot;absent&quot; instances, while <code>Nullable&lt;Nullable&lt;Nullable&lt;T&gt;&gt;&gt;</code> still has 1, and so on.</p>
<hr />
<p>The second possible meaning is <strong>why did the designers of Dart include a &quot;nullable&quot; marker instead of a proper &quot;optional&quot; monad</strong>, i.e. why does Dart not differentiate <code>Nullable&lt;T&gt;</code> and <code>Nullable&lt;Nullable&lt;T&gt;&gt;</code>?</p>
<p>I don't know the answer to this, I can only speculate. Notably, Dart isn't the only language to have this issue: Kotlin's nullable marker also flattens &quot;inner&quot; and &quot;outer&quot; nullability into one instance, but Swift's &quot;optional&quot; marker does not (<a href="https://gist.github.com/dcastro/69cecd670ccd78ab0ff33626f33db3b2" rel="noreferrer">source and demo</a>).</p>
<p>The main advantage I see of implementing flat <code>Nullable</code> instead of nest-able <code>Optional</code> is that it makes a compiler that creates fast executables easier to implement. Non-null and nullable objects can both be represented by pointers, with nullable objects represented by the null pointer. However, an <code>Option&lt;Option&lt;T&gt;&gt;</code> can't be represented by a pointer (ignoring the possibility of using pointer metadata), because there's only one &quot;null pointer&quot;, so no feasible encoding for <code>Some(None)</code> that is different than the one for <code>None</code>. Moreover, both Dart and Kotlin compile to other languages (JavaScript and Java respectively) that have native support for nullable types but not nested optionals. In both cases, it's still possible to encode a nested optional, usually either via a tagged union or nullable wrapper struct with a nullable inner value (Java even has the latter built-in, <a href="https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/Optional.java" rel="noreferrer"><code>Optional</code></a>). The problem is that the wrapper union/struct optional representation is much slower than the &quot;nullable pointer&quot; or &quot;native nullable&quot; one, so a decent compiler with <code>Optional</code> and no <code>Nullable</code> needs to implement <em>both</em> representations and compile <code>Optional</code>s to the former when possible (as Swift and Rust do), and this adds to the total code size and maintenance.</p>
<p>Meanwhile, this is extra code size and maintenance for what many would argue isn't much of a disadvantage. Because in practice, most people aren't using monads in Dart and Kotlin code, and it's likely that most people who write Dart or Kotlin rarely if ever encounter an issue with the nullable flattening (I don't recall ever encountering it myself or hearing about a real-world example where it was a problem for someone else). Though this is debatable, as evidenced by the fact that languages like Rust and Swift put in the effort to implement proper nestable optional types.</p>

CC BY-SA 4.0
2024-05-26T05:17:38.613Z
2024-05-26T05:17:38.613Z
tarzh
# Why isn't the nullable marker in Dart a monad?
Posted by: username_0 on 2024-05-25
Consider the following code:
typedef Nullable = T?;

Nullable unit(T x) => x;
Nullable bind(Nullable x, Nullable Function(T) f) =>
    x == null ? null : f(x);

final int? x = null;
Nullable f(int? y) => y == null ? true : y > 0 ? false : null;
Nullable g(bool x) => x ? "true" : null;
final Nullable m = null;

void main() {
    // left identity
    print(bind(unit(x), f) == f(x));

    // right identity
    print(bind(m, unit) == m);

    // associativity
    print(bind(bind(m, f), g) == bind(m, (x) => bind(f(x), g)));
}

The first print statement produced false, which proved that Nullable isn't a monad w.r.t. the given unit and bind function.
What is the reason that the first statement outputs false, unlike a monad?
**Tags:** monads nullability
> **Comments:**
> **username_1**: It sort of looks like `int??` is automatically implicitly "flattened" to `int?` in Dart, which is not really what you want for a monad. You want this to be *explicit*, and able to be accomplished by an operation often called `join`. You get `join` by applying `bind` to the identity function.  
> **username_0**: So how does the flattening affect the laws of monads, compared to a `Maybe` monad?  
> **username_2**: Is the question about explaining why this result doesn't meet the monad laws, or explaining why Dart did things this way?  
---
## 1 answers
### Answer by username_3 on 2024-05-26. Score: 5 | Accepted answer
This question has two possible meanings. The first is what prevents Dart's nullable marker from obeying the laws of monads?
The answer is that Dart's nullable marker isn't a monad because Nullable> is the same type as Nullable ((T?)? is the same type as T?).
Some languages such as Rust and OCaml have an "option" type, which is a monad. Option is the same as Nullable except in the case of Option> vs Nullable>. Specifically, Option> has two separate "absent" instances None and Some(None), but Nullable only has one null; there's no difference between the outer or inner "nullable" being null, whereas there is a difference whether the outer or inner option is None*.
Translating your example to Rust helps show why the distinction is important (Playground):
fn unit(x: T) -> Option { Some(x) }
fn bind(x: Option, f: impl FnOnce(T) -> Option) -> Option {
    match x { None => None, Some(x) => f(x) }
}

const X: Option = None;
fn f(y: Option) -> Option {
    match y { None => Some(true), Some(y) => if y > 0 { Some(false) } else { None } }
}
fn g(x: bool) -> Option<&'static str> { if x { Some("true") } else { None } }
const M: Option> = None; // Or `Some(None)` since there's no difference in Dart

fn main() {
    // left identity
    dbg!(bind(unit(X), f) == f(X));

    // right identity
    dbg!(bind(M, unit) == M);

    // associativity
    dbg!(bind(bind(M, f), g) == bind(M, |x| bind(f(x), g)));
}

If you run the playground, all three statements evaluate to true. The reason bind(unit(X), f) == f(X) holds in Rust and bind(unit(x), f) doesn't hold in Dart is precisely because unit(X) evaluates to Some(None), while unit(x) flattens to null. Dart's bind(null, f) then evaluates to null, as Rust's bind(None, f) would, except the Rust expression is actually bind(Some(None), f) which evaluates to Some(true), the correct value.
* Furthermore, Option>> has 3 separate "absent" instances, while Nullable>> still has 1, and so on.

The second possible meaning is why did the designers of Dart include a "nullable" marker instead of a proper "optional" monad, i.e. why does Dart not differentiate Nullable and Nullable>?
I don't know the answer to this, I can only speculate. Notably, Dart isn't the only language to have this issue: Kotlin's nullable marker also flattens "inner" and "outer" nullability into one instance, but Swift's "optional" marker does not (source and demo).
The main advantage I see of implementing flat Nullable instead of nest-able Optional is that it makes a compiler that creates fast executables easier to implement. Non-null and nullable objects can both be represented by pointers, with nullable objects represented by the null pointer. However, an Option> can't be represented by a pointer (ignoring the possibility of using pointer metadata), because there's only one "null pointer", so no feasible encoding for Some(None) that is different than the one for None. Moreover, both Dart and Kotlin compile to other languages (JavaScript and Java respectively) that have native support for nullable types but not nested optionals. In both cases, it's still possible to encode a nested optional, usually either via a tagged union or nullable wrapper struct with a nullable inner value (Java even has the latter built-in, Optional). The problem is that the wrapper union/struct optional representation is much slower than the "nullable pointer" or "native nullable" one, so a decent compiler with Optional and no Nullable needs to implement both representations and compile Optionals to the former when possible (as Swift and Rust do), and this adds to the total code size and maintenance.
Meanwhile, this is extra code size and maintenance for what many would argue isn't much of a disadvantage. Because in practice, most people aren't using monads in Dart and Kotlin code, and it's likely that most people who write Dart or Kotlin rarely if ever encounter an issue with the nullable flattening (I don't recall ever encountering it myself or hearing about a real-world example where it was a problem for someone else). Though this is debatable, as evidenced by the fact that languages like Rust and Swift put in the effort to implement proper nestable optional types.
---
