bitemyapp / bitemyapp/esqueleto
Consider changing how nullability is handled to more accurately model SQL
- Dominant language
- Haskell
- Stars
- 399
- Forks
- 107
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 1
Description
I prefer the Haskell model of `null`, where `Just Nothing` and `Just (Just Nothing)` are different values, and where you use `<$>` and `>>=` to operate on the underlying value.
However with that said SQL does not take this approach, and thus trying to model nullability like the above is a very leaky abstraction. It also adds significant unneeded verbosity and a proliferation of functions that would otherwise be unnecessary.
One example downside is that calling functions/operators on nullable values can be quite painful, even when it would be quite painless in both SQL and regular Haskell:
```
a : boolean nullable
b : boolean nullable
--
c : boolean nullable
c = a or b
```
```
a :: Maybe Bool
b :: Maybe Bool
--
c :: Maybe Bool
c = (||) <$> a <*> b
```
The above cannot really be directly modeled in Esqueleto, so you typically have to do something weird like coalesce the `Bool` into non-nullable form and then operate on that, which is rather painful when the standard `||` semantics is desired.
Another example of the leakiness is that `joinV` is a no-op, which means even though `isNothing` and `isNothing . joinV` are very different in Haskell, they have the exact same semantics in Esqueleto.
One example of verbosity from this is the very common need to use `joinV` in situations where no extra syntax would be needed in SQL. Similarly `just` is also often needed in situations where no extra syntax would be needed in SQL.
My proposal to account for this is admittedly quite a large change, so it may be too backwards breaking to be accepted, but I do think it should be considered.
I think that `SqlExpr` should actually be parameterized by two types, the first representing nullability, and the second representing the actual type itself.
Functions that have defined semantics when interacting with null will then have their type generalized accordingly, for example `(||.) :: SqlExpr n Bool -> SqlExpr m Bool -> SqlExpr (n \/ m) Bool` and `(^.) :: SqlExpr n (Entity v) -> EntityField m v t -> SqlExpr (n \/ m) (Value t)`.
Functions that crash completely when interacting with null can then simply require `NotNullable` at the type level. When serializing to and from Haskell, `Nullable` will naturally be mapped to and from `Maybe`.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reviewing the proposed SqlExpr nullability parameterization, including the mentioned joinV, just, (||.), and (^.) behavior. Compare the proposal with current SQL and Haskell null semantics; the work is done only when the API design, type-level behavior, serialization mapping, and backwards-compatibility impact are resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100