Replacing Exceptions With Either/Maybe/Option

前端 未结 2 1861
别那么骄傲
别那么骄傲 2021-02-06 18:21

I came across this dead end while trying to replace exceptions with either monad in c#. Which leads me to think maybe it is not only language specific problem and more techniqu

2条回答
  •  粉色の甜心
    2021-02-06 18:58

    As far as I can tell, Haskell Either is isomorphic to C#/Java-style exceptions, meaning that there's a translation from Either-based code to exception-based code, and vice versa. I'm not quite sure about this, though, as there may be some edge cases that I'm not aware of.

    On the other hand, what I am sure of is that Either () a is isomorphic to Maybe a, so in the following, I'm going to stick with Either and ignore Maybe.

    What you can do with exceptions in C#, you can also do with Either. The default in C# is to do no error handling1:

    public IEnumerable NoCatch(
        IEnumerable source, Func selector)
    {
        return source.Select(selector);
    }
    

    This will iterate over source until an exception happens. If no exception is thrown, it'll return IEnumerable, but if an exception is thrown by selector, the entire method throws an exception as well. However, if elements of source were handled before an exception was thrown, and there were side-effects, that work remains done.

    You can do the same in Haskell using sequence:

    noCatch :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
    noCatch f = sequence . fmap f
    

    If f is a function that returns Either, then it behaves in the same way:

    *Answer> noCatch (\i -> if i < 10 then Right i else Left i) [1, 3, 5, 2]
    Right [1,3,5,2]
    *Answer> noCatch (\i -> if i < 10 then Right i else Left i) [1, 3, 5, 11, 2, 12]
    Left 11
    

    As you can see, if no Left value is ever returned, you get a Right case back, with all the mapped elements. If just one Left case is returned, you get that, and no further processing is done.

    You could also imagine that you have a C# method that suppresses individual exceptions:

    public IEnumerable Suppress(
        IEnumerable source, Func selector)
    {
        foreach (var x in source)
            try { yield selector(x) } catch {}
    }
    

    In Haskell, you could do this with Either:

    filterRight :: (a -> Either e b) -> [a] -> [b]
    filterRight f = rights . fmap f
    

    This returns all the Right values, and ignores the Left values:

    *Answer> filterRight (\i -> if i < 10 then Right i else Left i) [1, 3, 5, 11, 2, 12]
    [1,3,5,2]
    

    You can also write a method that processes the input until the first exception is thrown (if any):

    public IEnumerable ProcessUntilException(
        IEnumerable source, Func selector)
    {
        var exceptionHappened = false;
        foreach (var x in source)
        {
            if (!exceptionHappened)
                try { yield selector(x) } catch { exceptionHappened = true }
        }
    }
    

    Again, you can achieve the same effect with Haskell:

    takeWhileRight :: (a -> Either e b) -> [a] -> [Either e b]
    takeWhileRight f = takeWhile isRight . fmap f
    

    Examples:

    *Answer> takeWhileRight (\i -> if i < 10 then Right i else Left i) [1, 3, 5, 11, 2, 12]
    [Right 1,Right 3,Right 5]
    *Answer> takeWhileRight (\i -> if i < 10 then Right i else Left i) [1, 3, 5, 2]
    [Right 1,Right 3,Right 5,Right 2]
    

    As you can see, however, both the C# examples and the Haskell examples need to be aware of the style of error-handling. While you can translate between the two styles, you can't use one with a method/function that expects the other.

    If you have a third-party C# method that expects exception handling to be the way things are done, you can't pass it a sequence of Either values and hope that it can deal with it. You'd have to modify the method.

    The converse isn't quite true, though, because exception-handling is built into C# (and Haskell as well, in fact); you can't really opt out of exception-handling in such languages. Imagine, however, a language that doesn't have built-in exception-handling (PureScript, perhaps?), and this would be true as well.


    1 C# code may not compile.

提交回复
热议问题