Verseのデザインパターンを考えるシリーズです。
今回はメソッドチェーンによるコードの簡略化のアプローチについてです。
関数型言語とメソッドチェーン
Verseの世界では関数も式でありその結果を関数にわたすことも可能です。
以下のコードは1から10を足し算するコードの例です。
※こんなコードを書くメリットは正直ないですが再帰呼び出しを絡めるとよく使う考え方になるので一旦こういう書き方もできる程度で理解を勧めてください
Add(a: int, b: int): int = a + b
Sample(): void =
AddOneFromTen := Add(Add(Add(Add(Add(Add(Add(Add(Add(1, 2), 3), 4), 5), 6), 7), 8), 9), 10)
Print("{AddOneFromTen}") // ← 55が出力される
嫌なコードですよね。
実際こんな計算することはないですが可読性も悪いですし、保守性も悪いです。
この処理をリファクタリングするとしたらこういう形が一般的です
AddAfromB(a: int, b: int): int =
if(a > b):
return 0
return a + AddAfromB(a + 1, b)
Sample(): void =
AddOneFromTen := AddAfromB(1, 10)
Print("{AddOneFromTen}") // ← 55が出力される
この処理は再帰呼び出しを利用したアプローチでaがb以上になるまでAddAfromBは関数内で呼び出されます。
そのためfor文を書かずともループしているような処理が実行でき、aがb以上になった時点で0を返し再帰呼び出しは止まります。
再帰呼び出しは関数型言語で抑えたい設計手法ではありますが、可読性が良いかと呼ばれると微妙なところです。
(慣れている人やプログラマは問題ないと思いますがVerseの思想上誰でも書けること、は大事なファクタとして書いていきます)
ではこの処理をメソッドチェーンを使った実装に修正してみましょう
(Input: int).Add(value: int): int = Input + value
Sample(): void =
AddOneFromTen := 1.Add(2).Add(3)
.Add(4).Add(5)
.Add(6).Add(7)
.Add(8).Add(9)
.Add(10)
Print("{AddOneFromTen}") // ← 55が出力される
いかがですか?
先程のコードより若干直感的に感じるのではないでしょうか。
再帰呼び出しの処理のようにaからbまでを加算する、といった要件は満たせないですが加算する数字が決まっている場合はこちらのほうが使いやすいかもしれません。
正直再帰呼び出しでもメソッドチェーンでもfor文ループでもわかりやすい方法であればどちらでも良いかなとは思ってます(メモリパフォーマンスなどは度外視)がこのあとの記事で紹介していきたいと考えているデザインパターンで多用する内容になっているので覚えていただけると幸いです。
おまけ
こんなこともできます
(Input: int).Print(): int =
Print("{Input}")
Input
(Input: int).Add(value: int): int = Input + value
(Input: int).Subtract(value: int): int = Input - value
(Input: int).Multiply(value: int): int = Input * value
(Input: int).Divide(value: int): int =
if(Result := Input / value):
return Floor(Result)
return 0
Sample(): void =
Result := 10.Multiply(10).Subtract(20).Devide(2).Add(10)
Resultの値は何でしょうか?
メソッドチェーンは前から答えが評価されていくので答えは 50
なのですが、これを計算式にすると以下の数式です。
(((10 * 10) - 20) / 2) + 10
(10 * 10 - 20) / 2 + 10 // 計算優先度でカッコを省略した形
単純に計算式を書くとVerseでもカッコの処理が必要なため読み解くのにより数学的な考え方が必要になります。
メソッドチェーンの場合前から順番に処理の流れを追っていくだけで済むのでコードレビューや保守性も高くなるとおもいます。
まとめ
今回紹介したアプローチはVerseにおける設計手法の一つのアプローチで必ずしも答えではないのでご注意ください。
使う場面によっては再帰呼出しやループを用いたほうがいいシーンももちろんあります。