Cou*_*ren 3 c# haskell functional-programming exception either
我试图用c#中的monad替换异常时遇到了这个死胡同.这让我想到可能不仅是语言特定的问题和更多与技术相关的缺失特征.
让我试着在全球范围内重新解释一下:
鉴于:
我想要:
在方法的惰性列表参数上应用函数(LINQ select,map ...)并将获取列表中的每个元素(懒惰)并执行可能失败的计算(抛出异常或返回Error/Either).
仅在第三方函数"内部"使用的列表,我不希望必须迭代每个元素一次.
有了异常/副作用,这可以通过从select,map函数抛出异常来轻松实现,如果发现错误,这将停止在第三方函数"内部"执行.然后我可以处理它之外的异常(没有第三方"意识到我的错误处理"),将错误处理的责任留给我.
在使用Either时,似乎不可能在不改变第三方功能的情况下获得相同的行为.直觉上我试图将列表从Eithers列表转换为列表中的任何一个,但这只能通过使用函数列表来完成.喜欢,聚合或减少(Haskell的序列函数是否相同?).
所有这些导致我的问题是Maybes/Eithers或Error作为返回类型,错过了这种行为?还有另一种方法可以与他们达成同样的目的吗?
Mar*_*ann 10
据我所知,Haskell Either与C#/ Java风格异常是同构的,这意味着存在从Either基于代码的代码到基于异常的代码的转换,反之亦然.不过,我对此并不十分肯定,因为可能会有一些我不知道的边缘情况.
在另一方面,我很肯定的是,Either () a是同构的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);
}
Run Code Online (Sandbox Code Playgroud)
这将迭代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
Run Code Online (Sandbox Code Playgroud)
如果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
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,如果没有Left返回任何值,您将获得一个Right包含所有映射元素的大小写.如果仅 Left返回一个案例,则可以获得该案例,并且不会进行进一步处理.
你还可以想象你有一个C#方法来抑制个别异常:
public IEnumerable<TResult> Suppress<TResult, T>(
IEnumerable<T> source, Func<T, TResult> selector)
{
foreach (var x in source)
try { yield selector(x) } catch {}
}
Run Code Online (Sandbox Code Playgroud)
在Haskell中,你可以这样做Either:
filterRight :: (a -> Either e b) -> [a] -> [b]
filterRight f = rights . fmap f
Run Code Online (Sandbox Code Playgroud)
这将返回所有Right值,并忽略Left值:
*Answer> filterRight (\i -> if i < 10 then Right i else Left i) [1, 3, 5, 11, 2, 12]
[1,3,5,2]
Run Code Online (Sandbox Code Playgroud)
您还可以编写一个处理输入的方法,直到抛出第一个异常(如果有):
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 }
}
}
Run Code Online (Sandbox Code Playgroud)
同样,您可以使用Haskell实现相同的效果:
takeWhileRight :: (a -> Either e b) -> [a] -> [Either e b]
takeWhileRight f = takeWhile isRight . fmap f
Run Code Online (Sandbox Code Playgroud)
例子:
*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]
Run Code Online (Sandbox Code Playgroud)
但是,正如您所看到的,C#示例和Haskell示例都需要了解错误处理的风格.虽然您可以在两种样式之间进行转换,但是不能使用具有期望另一种样式的方法/函数的样式.
如果你有一个第三方C#方法,它希望异常处理成为事情的完成方式,你就不能传递一系列Either值,并希望它可以处理它.你必须修改方法.
反之亦然,因为异常处理内置于C#(事实上也是Haskell); 您无法真正选择退出此类语言的异常处理.但是,想象一下,这种语言没有内置的异常处理(也许是PureScript?),这也是正确的.
1 C#代码可能无法编译.