Problem
Some of my rows have a missing value — NONE in Morel,
effectively NULL in SQL, NaN in pandas. I want to count how
many are missing, drop them, default them, or unwrap them safely.
Setup
val orders = [
{ id = 1, quantity = 12, status = SOME "fulfilled" },
{ id = 2, quantity = 30, status = SOME "fulfilled" },
{ id = 3, quantity = 8, status = SOME "cancelled" },
{ id = 5, quantity = 6, status = NONE },
{ id = 6, quantity = 20, status = SOME "fulfilled" },
{ id = 11, quantity = 16, status = NONE }
];
Example
Keep the rows where status is known, and unwrap it so downstream
steps see a plain string:
from ord in orders
where isSome ord.status
yield { ord.id, status = valOf ord.status };
val it =
[{id=1,status="fulfilled"},{id=2,status="fulfilled"},
{id=3,status="cancelled"},{id=6,status="fulfilled"}]
: {id:int, status:string} list
What's happening
status has type string option, which Morel reads as "a string, or
nothing". That's the whole missing-value story — there is no null,
and there is no NaN. Either the value is SOME "fulfilled" or it's
NONE, and the type system makes you handle both. That's the payoff
over SQL's tri-valued logic, where status = NULL silently evaluates
to "unknown" and ghosts through your WHERE clauses.
isSome asks "is this SOME _?", valOf pulls the value out, and
together they do the moral equivalent of "filter nulls, then unwrap."
If you reach for valOf on a NONE, Morel raises Option at
runtime — that's why the isSome guard goes first. Pattern matching
(see recipe 12) is a tidier spelling of the same thing when you want
to handle both branches in one pass.
Two patterns worth remembering. getOpt (x, default) unwraps with a
fallback in one call — the Morel equivalent of COALESCE. And for an
"ignore nulls, just use the real ones" pipeline, a where isSome …
step up front narrows the type so the rest of the query can stop
worrying.
Variations
Count the missing rows — a group {} with no key produces one row,
and the compute clause does the counting:
from ord in orders
where not (isSome ord.status)
group {} compute { missing = count over () };
Default the missing ones rather than drop them — getOpt takes the
option and a fallback, returns a plain string:
from ord in orders
yield { ord.id, status = getOpt (ord.status, "unknown") };
See also
- Recipe 12 — Derive columns with pattern matching —
case ord.status of SOME … | NONE …for branch-per-row logic. - Recipe 08 — Select and rename columns — yield an unwrapped value and let the type change.
- Recipe 18 — Higher-order functions on data —
Option.mapfor transforming the inside without unwrapping.