Skip to content

Commit 9bbea2c

Browse files
author
MPCoreDeveloper
committed
docs: add rebuttal to magic strings critique of Option<T> example
1 parent 961ba3b commit 9bbea2c

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

docs/NULLABLE_VS_OPTIONAL_REBUTTAL.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,73 @@ The argument isn't about *more kinds of absence*. It's about **where** the absen
4242

4343
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*.
4444

45+
## "But Your Example Is Full of Magic Strings"
46+
47+
A fair critique of the `Option<T>` showcase code:
48+
49+
```csharp
50+
var email = (await fdb.GetByIdAsync<UserDto>("Users", 99))
51+
.Map(u => u.Email)
52+
.Bind(e => string.IsNullOrEmpty(e) ? Option<string>.None : Option<string>.Some(e))
53+
.IfNone("no-email");
54+
```
55+
56+
> *"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+
var rows = db.ExecuteQuery("SELECT * FROM Users WHERE Id = 99"); // "Users" typo? Same risk.
67+
if (rows.Count > 0)
68+
{
69+
var email = (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
85+
var email = (await fdb.GetByIdAsync<UserDto>(Tables.Users, userId))
86+
.Map(u => u.Email)
87+
.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)** |
100+
| Row missing | `IndexOutOfRangeException` 💥 | `None` |
101+
| Column null | `NullReferenceException` 💥 | `None` |
102+
| 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+
45112
## TL;DR
46113

47114
> **"Aren't nullable types just optional types?"**

0 commit comments

Comments
 (0)