キャンセルを考慮しない場合
var x =await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) return;
Console.WriteLine(just3.Value);
キャンセルされたときは、おとなしく終了してみる。
まず InputTargetAsync, FMap, Just について説明します→
18.
キャンセルを考慮しない場合
var x =await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) return;
Console.WriteLine(just3.Value);
今回のサンプルコードのために用意したメソッドを軽く紹介
InputIntegerAsync メソッド: () → Task<IMaybe<int>>
ユーザーに数値を入力してもらう。
成功すると Just<int>, 失敗すると Nothing<int> を返す
19.
キャンセルを考慮しない場合
var x =await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) return;
Console.WriteLine(just3.Value);
今回のサンプルコードのために用意した型を軽く紹介
IMaybe<T> インターフェース
Just<T>, Nothing<T> という派生クラスがある。
一言でいうとnullの代わり
20.
キャンセルを考慮しない場合
var x =await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) return;
Console.WriteLine(just3.Value);
今回のサンプルコードのために用意したメソッドを軽く紹介
FMap メソッド: (this Task<IMaybe<T>>, Func<T, T2>) → Task<IMaybe<T2>>
元となったTask<IMaybe<T>>の中身を射影して返す。
実装がつらい
// while で無理やり実装した例
while(true)
{
var x = await InputIntegerAsync();
if (x is not Just<int> just) continue;
while (true)
{
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) break;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) continue;
Console.WriteLine(just3.Value);
return;
}
}
24.
実装がつらい
// while で無理やり実装した例
while(true)
{
var x = await InputIntegerAsync();
if (x is not Just<int> just) continue;
while (true)
{
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) break;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) continue;
Console.WriteLine(just3.Value);
return;
}
}
1回目の入力。
キャンセルされたら
もう一度入力を受け取る
2回目の入力。
キャンセルされたら
1回目の入力に戻りたい
3回目の入力。
キャンセルされたら
2回目の入力に戻りたい
25.
実装がつらい
// while で無理やり実装した例
while(true)
{
var x = await InputIntegerAsync();
if (x is not Just<int> just) continue;
while (true)
{
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) break;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) continue;
Console.WriteLine(just3.Value);
return;
}
}
ループが挟まることで、
何を実装したいのかの意図が
埋もれやすくなってしまった
これは3段階なのでまだマシ
つらさ
26.
実装がつらい
// gotoで無理やり実装した例
First:
var x= await InputIntegerAsync();
if (x is not Just<int> just) goto First;
Second:
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) goto First;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) goto Second;
Console.WriteLine(just3.Value);
27.
実装がつらい
// gotoで無理やり実装した例
First:
var x= await InputIntegerAsync();
if (x is not Just<int> just) goto First;
Second:
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) goto First;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) goto Second;
Console.WriteLine(just3.Value);
1番目は、キャンセルされると
1番目をリトライ
2番目は、キャンセルされると
1番目をリトライ
3番目は、キャンセルされると
2番目をリトライ
28.
実装がつらい
// gotoで無理やり実装した例
First:
var x= await InputIntegerAsync();
if (x is not Just<int> just) goto First;
Second:
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) goto First;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) goto Second;
Console.WriteLine(just3.Value);
gotoを使って後戻りをすると、ちょっと
したミスで分かりづらいバグを呼ぶ
ラベル名があるおかげでwhileを使った
例よりも直感的
つらさ
完成形
var result =await PhaseProcess.Entry<Unit>()
.Then(async _ => await InputIntegerAsync())
.Then(async x => await InputIntegerAsync().FMap(p => x + p))
.Then(async y => await InputIntegerAsync().FMap(p => y + p))
.Run(Unit.Id);
Console.WriteLine(result);
今回はLINQを使った手法で実現しました! PhaseProcess な
る新しいクラスが今回の肝
Entryメソッドで書き始めて、後戻りの各段階でやりたい処理
をThenメソッドで繋げていって、最後にRunで実行できる
31.
キャンセル無しの場合の比較
var result =await PhaseProcess.Entry<Unit>()
.Then(async _ => await InputIntegerAsync())
.Then(async x => await InputIntegerAsync().FMap(p => x + p))
.Then(async y => await InputIntegerAsync().FMap(p => y + p))
.Run(Unit.Id);
Console.WriteLine(result);
var x = await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) return;
Console.WriteLine(just3.Value);
キャンセル無し
キャンセルあり
今回の手法
32.
完成形と、キャンセル無しの場合の比較
var result =await PhaseProcess.Entry<Unit>()
.Then(async _ => await InputIntegerAsync())
.Then(async x => await InputIntegerAsync().FMap(p => x + p))
.Then(async y => await InputIntegerAsync().FMap(p => y + p))
.Run(Unit.Id);
Console.WriteLine(result);
var x = await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) return;
Console.WriteLine(just3.Value);
キャンセル無し
キャンセルあり
今回の手法
キャンセル無しの場合
と同程度に、処理の流
れが追いやすいはず
33.
完成形と、キャンセル無しの場合の比較
var result =await PhaseProcess.Entry<Unit>()
.Then(async _ => await InputIntegerAsync())
.Then(async x => await InputIntegerAsync().FMap(p => x + p))
.Then(async y => await InputIntegerAsync().FMap(p => y + p))
.Run(Unit.Id);
Console.WriteLine(result);
var x = await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
var z = await InputIntegerAsync().FMap(p => just2.Value + p);
if (z is not Just<int> just3) return;
Console.WriteLine(just3.Value);
キャンセル無し
キャンセルあり
今回の手法
IMaybeを外す処理も
ついでにサポート
foreachでも後戻りがしたい
var list =new List<int>();
foreach (var i in Enumerable.Range(0, 3))
{
var x = await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
list.Add(just2.Value);
}
Console.WriteLine(list.Aggregate((a, b) => a * b));
これがキャンセルを考慮しない実装
2回の入力を1セットとして、
3回繰り返す
56.
foreachでも後戻りがしたい
var list =new List<int>();
foreach (var i in Enumerable.Range(0, 3))
{
var x = await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
list.Add(just2.Value);
}
Console.WriteLine(list.Aggregate((a, b) => a * b));
foreachループを使う場合、繰り返しをまた
いで後戻りできる必要がある
i = 2 のときにここで
キャンセルされたら
戻り先はここ (ただし i = 1)
57.
完成形
var result =await PhaseProcess.ForEach(Enumerable.Range(0, 3),
builder =>
{
return builder.Then(i => InputIntegerAsync())
.Then(x => InputIntegerAsync().FMap(p => x + p));
})
.Run(Unit.Id);
Console.WriteLine(result.Aggregate((a, b) => a * b));
あるイテレーションの最初でキャンセルすると、その前のイテ
レーションの最後がリトライされる。 PhaseProcess.ForEach
がそういった処理を担ってくれる
58.
完成形 ラムダ式のこの戻り値は
PhaseProcess<Unit, int>型
あるイテレーションの最初でキャンセルすると、その前のイテ
レーションの最後がリトライされる。 PhaseProcess.ForEach
がそういった処理を担ってくれる
var result = await PhaseProcess.ForEach(Enumerable.Range(0, 3),
builder =>
{
return builder.Then(i => InputIntegerAsync())
.Then(x => InputIntegerAsync().FMap(p => x + p));
})
.Run(Unit.Id);
Console.WriteLine(result.Aggregate((a, b) => a * b));
59.
キャンセルなしとの比較
foreach (var iin Enumerable.Range(0, 3))
{
var x = await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
}
PhaseProcess.ForEach(Enumerable.Range(0, 3), builder =>
{
return builder.Then(i => InputIntegerAsync())
.Then(x => InputIntegerAsync().FMap(p => x + p));
};
キャンセル無し
今回の手法
キャンセルあり
60.
キャンセルなしとの比較
foreach (var iin Enumerable.Range(0, 3))
{
var x = await InputIntegerAsync();
if (x is not Just<int> just) return;
var y = await InputIntegerAsync().FMap(p => just.Value + p);
if (y is not Just<int> just2) return;
}
PhaseProcess.ForEach(Enumerable.Range(0, 3), builder =>
{
return builder.Then(i => InputIntegerAsync())
.Then(x => InputIntegerAsync().FMap(p => x + p));
});
キャンセル無し
今回の手法
キャンセルあり