I get it! (Thanks, playing around with actual code helped a ton.) For example, in Typescript you're saying you can add a default value simply:
# old
function f(x: number): number {
return 2 * x;
}
# new
function f(x: number|null): number {
x = x || 3;
return 2 * x;
}
# usage
# old
f(2)
# new
f(2) # still works!
But in Haskell this requires changing the call sites:
-- old
f :: Int -> Int
f = (*2)
-- new
f :: Maybe Int -> Int
f = maybe 0 (*2)
-- usage
-- old
f 2
-- new
f (Just 2) -- different!
But I actually feel this is an antipattern in Haskell (and maybe TypeScript too), and a separate wrapper function avoids refactoring while making things even more user friendly.
-- old
f :: Int -> Int
f = (*2)
-- new
fMaybe :: Maybe Int -> Int
fMaybe = maybe 3 f
-- usage
-- old
f 2
-- new
f 2 -- still works!
fMaybe Nothing -- works too!
Here's some wrappers for general functions (not that they're needed, they're essentially just raw prelude functions):
maybeIn :: b -> (a -> b) -> (Maybe a -> b)
maybeIn = maybe
maybeOut :: (a -> b) -> (a -> Maybe b)
maybeOut = fmap Just
maybeBoth :: b -> (a -> b) -> (Maybe a -> Maybe b)
maybeBoth d = maybeOut . maybeIn d
Added bonus, this approach avoids slowing down existing code with the null checks we just added.