据我所知,Haskell Either 与 C#/Java 样式的异常同构,这意味着存在从基于 Either 的代码到基于异常的代码的转换,反之亦然。不过,我对此不太确定,因为可能存在一些我不知道的极端情况。
另一方面,我我确定的是Either () a is isomorphic to Maybe a,所以在下文中,我将坚持使用Either 而忽略Maybe。
您可以在 C# 中处理异常,也可以使用 Either。 C# 中的默认设置是不进行错误处理1:
public IEnumerable<TResult> NoCatch<TResult, T>(
IEnumerable<T> source, Func<T, TResult> selector)
{
return source.Select(selector);
}
这将遍历source,直到发生异常。如果没有抛出异常,它会返回IEnumerable<TResult>,但是如果selector 抛出异常,整个方法也会抛出异常。但是,如果 source 的元素在引发异常之前被处理,并且存在副作用,则该工作仍然完成。
您可以在 Haskell 中使用 sequence 执行相同的操作:
noCatch :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
noCatch f = sequence . fmap f
如果f 是一个返回Either 的函数,那么它的行为方式相同:
*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 case 返回,你会得到它,并且不会进行进一步的处理。
您还可以想象您有一个抑制个别异常的 C# 方法:
public IEnumerable<TResult> Suppress<TResult, T>(
IEnumerable<T> source, Func<T, TResult> selector)
{
foreach (var x in source)
try { yield selector(x) } catch {}
}
在 Haskell 中,您可以使用 Either:
filterRight :: (a -> Either e b) -> [a] -> [b]
filterRight f = rights . 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 in source)
{
if (!exceptionHappened)
try { yield selector(x) } catch { exceptionHappened = true }
}
}
同样,你可以用 Haskell 实现同样的效果:
takeWhileRight :: (a -> Either e b) -> [a] -> [Either e b]
takeWhileRight f = takeWhile isRight . fmap f
例子:
*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]
但是,正如您所见,C# 示例和 Haskell 示例都需要了解错误处理的风格。虽然您可以在两种样式之间进行转换,但您不能将其中一种与期望另一种的方法/函数一起使用。
如果你有一个第三方 C# 方法期望异常处理是事情的完成方式,你不能传递一个 Either 值序列并希望它可以处理它。 您必须修改方法。
然而,反过来并不完全正确,因为异常处理是内置在 C# 中的(事实上,Haskell 也是如此);您不能真正选择退出此类语言的异常处理。然而,想象一下没有内置异常处理的语言(也许是 PureScript?),这也是正确的。
1 C# 代码可能无法编译。