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.Got `maybeOut` wrong, can't edit, but it should be:
maybeOut :: (a -> b) -> (a -> Maybe b)
maybeOut = (.) Just
Also the parenthesis around the last two output types are added for emphasis, but can be safely removed.
> I may still be thinking about this incorrectly. Do you have an language in mind that contradicts this?
Any language which supports "union types" of this sort, e.g. Ceylon or, nowadays, Typescript.