Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

You seem to think `X -> Y|Null` and `X|Null -> Y` have to be equivalent, but that's not the case. The second function type has a more general input type and a more restricted return type. And a function which can accept X as an input can be replaced with a function that can accept X or Null (or X or anything else) as input type. And a function which has can return types Y or Null (or Y or anything else) can be replaced with a function that can return type Y. Old call site code will still work. Of course this replacement only makes sense if it is possible to improve the function in this way from a perspective of business logic.

> 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.



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.


Last reply. Probably. Here's `maybe` in TypeScript:

    const maybe = <T,>(f: (_: T) => T, d:T) => (x: T|null) => f(x || d);

    console.log(maybe((x) => 2 * x, 3)(null)); // returns: 6




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: