使用Either / Maybe / Option替换例外 [英] Replacing Exceptions With Either/Maybe/Option

查看:140
本文介绍了使用Either / Maybe / Option替换例外的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了这个死胡同而试图用c#中的monad替换异常。
这导致我认为它可能不仅仅是特定于语言的问题,还有更多与技术相关的缺失功能。



让我试着在全球范围内重新解释它:



鉴于:


  • 我有第三方功能导入到我的代码中,我无法访问),它收到一个惰性列表(c#IEnumerable,f #Seq ...)并使用它$ / $>

    我想要:


    • 在方法的懒惰列表参数上应用函数(LINQ select,map ...)将采取列表中的每个元素(懒惰),并将执行可能失败的计算(抛出一个异常或返回Error / Either)。

    • 在第三方函数中只消耗内部,我不希望每次迭代遍历每个元素。




    使用异常/副作用时,如果错误是fou,可以通过从select,map函数抛出异常轻松实现nd,这将停止执行内部第三方功能。然后我可以处理它之外的异常(没有第三方意识到我的错误处理),将错误处理的责任交给我。



    while无论是在不改变第三方功能的情况下,似乎都不可能获得相同的行为。直觉上,我试图将列表从Eithers列表转换为列表中的任一列表,但这只能通过使用函数列表来完成。 Like,aggregate或reduce(Haskell的Sequence函数的行为是一样的吗?)。

    所有这些让我想到Maybes / Eithers或Error作为返回类型的问题,行为?有没有另一种方法来实现与他们同样的事情?

    解决方案

    据我所知,Haskell 与C#/ Java风格的异常是同构的,这意味着有一个从>或者的代码到基于异常的代码的转换,反之亦然。但我不太清楚这一点,因为可能会有一些我不知道的边缘情况。

    另一方面,我 > am 可以肯定的是 ()a 同构于也许一个 ,所以在下面,我将继续使用或者并忽略也许



    在C#中,你也可以用或者来完成。在C#中的默认设置是不做错误处理。:

      public IEnumerable< TResult> NoCatch< TResult,T>(
    IEnumerable< T> source,Func< T,TResult>选择器)
    {
    return source.Select(selector);
    }

    这将遍历 source IEnumerable< TResult> ,但是如果选择器引发异常,整个方法也会抛出异常。但是,如果元素 source 在抛出异常之前被处理,并且存在副作用,那么该工作仍然完成。



    你可以在Haskell中使用 sequence 来完成相同的操作:

      noCatch ::(Traversable t,Monad m)=> (a  - > m b) - > t a  - > m(t b)
    noCatch f =序列。 fmap f

    如果 f 是一个函数,返回或者,那么它的行为方式是相同的:

      * 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

    正如你所看到的,如果没有返回 Left 值,你会得到一个 Right 回到案例,包含所有的映射元素。如果返回 Left 个案,您就会得到该结果,并且不再进行任何处理。



    你也可以想象你有一个C#方法可以抑制个别的异常:

      public IEnumerable< TResult>抑制< TResult,T>(
    IEnumerable< T>源,Func< T,TResult>选择器)
    {
    foreach(源中的var x)
    try {在Haskell中,你可以用<$($)来完成这个工作。 c $ c>

      filterRight ::(a  - > Ebb) - > ; [a]  - > [b] 
    filterRight f =权利。 fmap f

    这将返回所有 Right 值,并忽略 Left 值:

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

    您也可以编写一个方法来处理输入,直到抛出第一个异常(如果有的话):

      public IEnumerable< TResult> ProcessUntilException< TResult,T>(
    IEnumerable< T> source,Func< T,TResult> selector)
    {
    var exceptionHappened = false;
    foreach(源代码中的var x)
    {
    if(!exceptionHappened)
    try {yield selector(x)} catch {exceptionHappened = true}
    }

    $ / code>

    再次,您可以使用Haskell实现相同的效果:

      takeWhileRight ::(a  - > Ebb eb) - > [a]  - > [或者] b $ b takeWhileRight f = takeWhile isRight。 fmap f 

    示例:

      *应答和GT; takeWhileRight(\i-i->>如果i< 10然后右i或其他i)[1,3,5,11,2,12] 
    [右1,右3,右5]
    *答案> takeWhileRight(\i-i->>如果我< 10然后右边我是左边的我)[1,3,5,2]
    [右1,右3,右5,右2]

    然而,您可以看到,C#示例和Haskell示例都需要知道错误处理的风格。虽然您可以在两种样式之间进行翻译,但不能使用具有预计其他类型的方法/函数。



    如果您有第三方C#方法希望异常处理是事情的完成方式,你不能传递一系列或者值,并希望它能够处理它。 您必须修改方法。



    然而,反过来并不是真的,因为异常处理是内置在C#中的而Haskell事实上也是如此);你不能真正选择退出这种语言的异常处理。想象一下,没有内置异常处理的语言(PureScript,也许?),这也是真实的。






    1 C#代码可能无法编译。


    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 technique related missing feature.

    Let me try to re-explain it more globally:

    Given:

    • I have a 3rd party function( a function that is imported into my code and I have no access to) which receives a lazy list (c# IEnumerable,f# Seq...) and consume it

    I Want:

    • To apply a function (LINQ select,map...) on the method's lazy list argument and will take each element of the list (lazily) and will do computation that might fail (throwing an exception or returning Error/Either).

    • The list to be consumed only "inside" the 3rd party function, I don't want to have to iterate over each element more then once.

    With Exceptions/side effects this can be achieved easily with throwing exception from the select, map functions if error was found, this will stop the execution "inside" the 3rd party function. Then I could handle the exception outside of it (without the 3rd party being "aware" of my error handling), leaving the responsibility of the error handling to me.

    While with Either it does not seem to be possible to get the same behavior without altering the 3rd party function. Intuitively I was trying to convert the list from the list of Eithers to Either of list, but this can be done only by consuming the list with functions. Like, aggregate or reduce (does Haskell's Sequence function act the same?).

    All this leads me to the question are Maybes/Eithers or Error as return type, missing this behavior? Is there another way to achive the same thing with them?

    解决方案

    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<TResult> NoCatch<TResult, T>(
        IEnumerable<T> source, Func<T, TResult> selector)
    {
        return source.Select(selector);
    }
    

    This will iterate over source until an exception happens. If no exception is thrown, it'll return IEnumerable<TResult>, 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<TResult> Suppress<TResult, T>(
        IEnumerable<T> source, Func<T, TResult> 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<TResult> ProcessUntilException<TResult, T>(
        IEnumerable<T> source, Func<T, TResult> 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.

    这篇关于使用Either / Maybe / Option替换例外的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆