これが展示会での間違い探しでした。
VBの元のコード
Dim total As Decimal = 0
For Each item In items
If item.Category = "A" Then
If item.Price > 1000 Then
total += item.Price * 0.9
Else
total += item.Price
End If
End If
Next
If total > 50000 Then
total = total * 0.95
End If
lblTotal.Text = total.ToString("C")C#でリファクタリング
var total = items
.Where(i => i.Category == "A")
.Sum(i => i.Price > 1000 ? i.Price * 0.9 : i.Price) * 0.95m;
lblTotal.Text = $"{total:C}";最後の、合計が50000以上だったら0.95を掛ける、というところの条件分岐が抜けていて、全部に0.95を書けてしまっています。
では、この条件分岐をどうやって関数型に持ち込むか?
一つ目は、拡張メソッド。
public static TResult Pipe<T, TResult>(this T value, Func<T, TResult> f)
=> f(value);
とPipeメソッドを作ります。そうすると、x.Pipe(f) == f(x) となり、順番を後に続けて書くことができますね。
var total = items
.Where(i => i.Category == "A")
.Sum(i => i.Price > 1000 ? i.Price * 0.9m : i.Price)
.Pipe(sum => sum > 50000m ? sum * 0.95m : sum);
lblTotal.Text = $"{total:C}";たしかにロジカルで便利です。でも、ちょっと大げさではありますね。
C#にも、Haskellなど関数型言語でおなじみのパターンマッチングみたいなのがあって、switchを使うと、こう書けます。
var total = items
.Where(i => i.Category == "A")
.Sum(i => i.Price > 1000 ? i.Price * 0.9m : i.Price)
switch
{
var s when s > 50000m => s * 0.95m, var s => s
};
lblTotal.Text = $"{total:C}"; この場合はパターンマッチングの方が、パイプよりシンプルで綺麗かな。
拡張メソッドだとコードの見通しが悪くなって、逆にメンテが無茶苦茶になってしまうかも知れないです。
個人的には拡張メソッドを作って色々いじっているのは楽しいですが、火遊びしているとやけどしてしまいますね。
ところで数年前に「パイプを吸うとイケてるんじゃないか」という思いに憑りつかれ、始めたことがありました。
煙草の葉だけを詰めるだけだから、一番ロジカルな喫煙法だと思ったのですが、詰め方がものすごく難しい。
固く詰めすぎるとすぐに火が消えるし、柔らかすぎると燃えすぎる。葉をもみほぐして入れるとか、三層に入れるとか、色々とネットに解説されている方法を試したのですが、そのうち口の中をやけど。
もともと煙草も吸わず恰好だけだったので、すぐにやめてしまいました。