今天要討論錯誤處裡,我們在前面介紹了Option型別來解決函數沒有對應的問題,但有的時候我們除了沒有對應結果外,可能需要其他的訊息,像是什麼輸入沒有結果,或者是沒有結果的原因。當程式發生意外造成沒有結果的時候,我們通常會用throw exception的方式,前面也有提到微軟建議的拋出例外的方法。然而並不是所有情況都適合拋出例外,比如說大量的資料流時我們更傾向於讓流程繼續進行,最後才把有異常的資料額外處理
記不記得前幾天我用過tuple包裝Int.TryParse
的結果
(bool IsInt, int number, string oper) ParseInt(string s)
{
var isInt = int.TryParse(s, out var n);
return (isInt, n, s);
}
針對這個函數的回傳,如果IsInt是ture的話我就取得轉換後的數字,如果是false的話就將文字取出來判斷需要做什麼操作,這種”如果成功就的到A,如果失敗就得到B”其實是一種蠻常見的抽象,在FP中有定義了一個型別Either
善於處裡這種事,可以仿照一下Option
public class Either
{
public static Either Left<L>(L data) => new Left<L>(data);
public static Either Right<R>(R data) => new Right<R>(data);
}
public class Right<R> : Either
{
public Right(R data) => this.Data = data;
public R Data { get; init; }
}
public class Left<L> : Either
{
public Left(L data) => this.Data = data;
public L Data { get; init; }
}
然後我們就可以針對Either型別設計他的HOF
public static class EitherExtension
{
public static Either Bin<RS, RR, LS, LR>(this Either e, Func<RS, RR> Right, Func<LS, LR> Left)
=> e switch
{
Right<RS> { Data: var data } => Either.Right(Right(data)),
Left<LS> { Data: var data } => Either.Left(Left(data)),
_ => throw new NotSupportedException()
};
}
這邊是不是開始覺得patten match的寫法很神奇呢?Either型別具有兩個元素,其中Right表示對,Left表示錯誤,再利用函子去設計對應的處裡。有了Either型別,前幾天對於TryParse就會變成了
var array = input.Split(' ')
.Select(ParseInt);
.Aggregate(new Stack<int>(), Compute)
.First();
ImmutableStack<int> Compute(ImmutableStack<int> stack, Either s)
=> s switch
{
Right<int> { Data: var n } => stack.Push(n),
Left<string> { Data: var o } => Operate(stack, o)
_ => throw new NotSupportedException()
}
Either ParseInt(string s)
=> int.TryParse(s, out var n)
? Either.Right(n)
: Either.Left(s);
Left的內容就可以放入錯誤訊息,不管是用例外物件甚至是物件的陣列,在最後統一彙整。
有了前面的介紹,其實Either跟Option的概念非常相似,只是針對回傳值保留更多彈性讓我們處理,介紹Either是為了對之後要討論的如何處理純函數與不純函數做鋪墊,但明天會先討論monad。