You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/NULLABLE_VS_OPTIONAL_REBUTTAL.md
+67Lines changed: 67 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,6 +42,73 @@ The argument isn't about *more kinds of absence*. It's about **where** the absen
42
42
43
43
The statement *"runtime data semantics cannot always be fully proven at compile time"* means exactly this: **the compiler can annotate intent, but it cannot enforce contracts on data it has never seen** (SQL results, JSON payloads, reflection-populated DTOs). `Option<T>` closes that gap by making the proof travel *with the value*.
> *"What if `"Users"` should be `"User"`? What about `"no-email"` — is that a prefix convention? String-based keys, magic strings, ternaries for the logic you're promoting... This is just runtime errors with extra steps."*
57
+
58
+
**This critique is valid — and it targets the example, not the concept.** Let's separate the two.
59
+
60
+
### What the critique actually proves
61
+
62
+
The complaint is about **stringly-typed API design** — and that's a real problem regardless of whether you use `Option<T>` or not. The classic version has the *exact same magic strings*:
63
+
64
+
```csharp
65
+
// Classic — same magic strings, same problems, PLUS silent nulls
66
+
varrows=db.ExecuteQuery("SELECT * FROM Users WHERE Id = 99"); // "Users" typo? Same risk.
67
+
if (rows.Count>0)
68
+
{
69
+
varemail= (string)rows[0]["Email"]; // silent null, no compiler help
70
+
if (!string.IsNullOrEmpty(email))
71
+
SendEmail(email);
72
+
else
73
+
SendEmail("no-email"); // same magic fallback
74
+
}
75
+
```
76
+
77
+
The magic strings exist because the *database API* is string-based. That's not `Option<T>`'s fault — it's the reality of talking to a schema-less query layer.
78
+
79
+
### What a properly typed version looks like
80
+
81
+
The real answer to "I'd expect expressions so they could be compile-time checked" is: **yes, you should, and `Option<T>` composes perfectly with that**:
82
+
83
+
```csharp
84
+
// Strong-typed table reference — no magic strings
.Bind(Option.FromNullOrEmpty) // built-in, no ternary
88
+
.IfNone(Defaults.NoEmail); // named constant, not a magic string
89
+
```
90
+
91
+
Every magic string is now a compile-time symbol. The `Option<T>` chain is unchanged — because **`Option<T>` was never the source of the magic strings**.
92
+
93
+
### What the critique misses
94
+
95
+
The original comparison wasn't "this API has perfect ergonomics." It was:
96
+
97
+
| Failure mode | Classic (NRT) | Option\<T\>|
98
+
|---|---|---|
99
+
| Table name typo (`"Users"` vs `"User"`) | Silent empty result or exception | Silent empty result → `None`**(you must handle it)**|
| Forgot to check | Compiles fine, crashes at runtime | Won't compile — `Option<T>` forces a decision |
103
+
104
+
The table name typo is equally bad in both worlds. But in the classic version you get **three additional unforced crash vectors** that `Option<T>` eliminates.
105
+
106
+
### The honest summary
107
+
108
+
The critique correctly identifies that the *showcase example* was sloppy with magic strings. It does **not** invalidate the core claim: `Option<T>` forces you to handle absence at every step, while NRT lets nulls slip through silently. Better examples should use strongly-typed table references and named constants — and `Option<T>` works just as well with those.
109
+
110
+
---
111
+
45
112
## TL;DR
46
113
47
114
> **"Aren't nullable types just optional types?"**
0 commit comments