Scala関数型デザイン&プログラミング 〜2章 (2)
"Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド"を買いました。
ちょっとずつ読んで、各章の内容をまとめてゆきます。
今回は2章まで。ループの処理や型について。
2章
2章では、まずScalaの説明が少しあります。
その後、再帰を用いたループの書き方、末尾再帰について触れられています。
ここで実習が入ります。
フィボナッチ数列を取得する再帰関数を作れ。ただし、定義は末尾再帰関数で作ること
直前に階乗の例がありますが、フィボナッチでは直前2つを使うので、引数を更に増やす工夫が必要ですね。
下のようなプログラムを書きました。
末尾再帰なので1000でも大丈夫(Intなので値のオーバーフローはしますが)。
import scala.annotation.tailrec
object Main extends App {
def fib(n: Int): Int = {
@tailrec
def go(n: Int, pre: Int, prepre: Int): Int =
if(n <= 0) prepre
else go(n-1, pre + prepre, pre)
go(n, 1, 0)
}
(0 to 1000).foreach( i => println( fib(i) ))
}
次に、高階関数、多相化の説明に入ります。
仮にC++の入門書であればテンプレートは結構後に出てくるものですが、序盤で出てくるのが面白いです。
ここまでで、また練習問題が出てきます。
比較関数を渡してソートされているかを判定する関数を作れ
直前に、配列内の要素を検索する関数があるので、それと同じ要領で作ればよいです。
import scala.annotation.tailrec
object Main extends App {
def isSorted[A](as: Array[A], ordered: (A,A) => Boolean): Boolean = {
@tailrec
def loop(n: Int): Boolean = {
if(n >= as.length - 1) true
else if(! ordered(as(n), as(n+1))) false
else loop(n+1)
}
loop(0)
}
val test = Array(1, 2, 2, 3)
println(isSorted(test, (a:Int, b:Int) => a <= b))
println(isSorted(test, (a:Int, b:Int) => a < b))
}
引き続き、関数リテラル、partialを使った型による制限の例を示しています。
型で実装を決めてゆくのはパズル感があります。
ここから怒涛の練習問題。
curry, uncurry, composeの3つを作成せよ、というお題です。
型定義に従えば、自ずと実装が出てきます。
object Main extends App {
// ex 1
def curry[A, B, C](f: (A, B) => C): A => (B => C) =
(a: A) => (b: B) => f(a, b)
def test(a: Int, b: Float): (Float, Double) = (a.toFloat, b.toDouble)
val t = curry(test)
val res = t(10)
println(res(2.5f))
// ex 2
def uncurry[A, B, C](f: A => B => C): (A, B) => C = (a: A, b: B) => f(a)(b)
val t2 = uncurry(t)
println(t2(10, 2.5f))
// ex 3
def compose[A, B, C](f: B=> C, g: A=>B): A => C = (a: A) => f(g(a))
val t3 = compose(
(b: Int) => b.toDouble,
(a: Float) => a.toInt
)
println(t3(10.5f))
}
思ったこと
汎用的な処理を作ってそれを組み合わせるという、ライブラリから作るようなイメージです。
最近は大規模なプロダクトも多く仕様変更のオーバーヘッドが大きいため、抽象的な処理を設計・記述する需要は昔より高まっているのではないかと感じます。
そのような能力を鍛える意味でも、関数型を学習する価値はあるのではないかと思いました。
続き: