LL脳がscalaの勉強を始めたよ その4

第三章の後半の最後

Scalaコップ本の第三章の最後をやりますー、内容は関数型プログラミングのススメですねー

関数型プログラミングのススメ

ScalaではJavaみたいな命令型の処理だけじゃなくて関数型のプログラムが書けるから、この本では関数型の書き方を学べるように頑張る、お前らも頑張れってさ

関数型の処理は副作用(他の領域への影響)をなくして、INPUT(引数)→OUTPUT(戻り値)の関係をっ徹底して処理を記述しよーぜ!!目安はvarをなくしてvalのみで書くのが理想デス…とのこと

例えば次のような与えられた配列を順次printする処理とかはダメな例

def printArray(args:Array[String]) Unit: = {
    var i = 0
    while( i < args.length) {
        println(args(i))
        i += 1
    }
}

関数型的にダメな部分は次の2点

  • varでmuutableな変数を定義しているところ
  • printlnで処理中に配列要素を出力している((副作用)がある)ところ

ちなみにUnitは戻り値が無い関数型?みたいなもの、javaでいうvoid型らしい

じゃあ、これをどうしたらいいの?ってことで…varも副作用もなくした最終型はこうなるのです

def formatArray(args: Array[String]) = args.mkString("\n")

mkStringは配列要素を引数で結合するメソッド、Joinみたいなもん…だよね?

このような書き方をすれば結果を出力することもできるし、戻り値をテスト用のモジュールに突っ込むこともできるから超便利、わかりやすい、素晴らしい、ヒャッハー

// 表示するときはprintln使っちゃえばいいし
println(formatArray(Array("hoge", "huga", "moge"))
// assert 使えばテストもできるよね
val result = formatArray(Array("hoge", "huga", "moge"))
assert(result == "hoge\nhuga\nmoge")

assertはFalseが返ってくるとAssertionErrorを出すテストにも使える処理とのこと、詳しくは14章でやるらしいので後回しー

関数型ヽ(´ー`)ノバンザーイ

関数型にすれば副作用も減らせるし処理自体がシンプルになるから、いろんなトラブルが減らせるはず、関数型使えよー…と気炎を吐きつつも、Scalaは命令型と関数型の両方が使えるハイブリッドだから適材適所でつかおーぜ!とのまとめ

具体的な指針としては「できるだけ関数型でコーディングする→どうしても無理っぽかったら命令型を使う」っていう姿勢が推奨とのこと。

最後にファイルを読み込んでみようぜ

ファイル読み込んで各行ごとに文字数と行の内容を出力するスクリプトを書いてみる

出力される形式が ”行の文字数" | "行の内容"の形式になるんだけども、行の文字数が1桁だったり2桁だったりするので適切にフォーマットして | の位置がきっちり合うようにスレ、という指示がおまけでついてきました…

コップ本ではパーツに分解して各処理を順繰り説明してくれています

// ファイル操作用のscala.ioパッケージを読み込み
import scala.io.Source
// 各行の文字数の桁数を取得する処理:何度も使うので関数化
def widthOfLength(s:String) = s.length.toString.length
// 入力がある場合は処理開始
if(args.length > 0) {
    // ファイルを読み込んでリスト化する
    // getLinesがファイルの内容をイテレータ化してtoListでそれをリスト化
    val lines = Source.fromFile(args(0)).getLines.toList
    // reduceLeftが第1、第2要素から順に要素を二つずつ取り出して処理を行う
    // 第1、第2の次は第2、第3と比較していくことで一番長い行の文字数を取得
    val longestLine = lines.reduceLeft(
        (a, b) => if ( a.length > b.length) a else b
    )
    // 一番長い行の文字数が何桁あるかを取得
    val maxWidth = widthOfLength(longestLine)
    // 各行ごとに表示処理
    for (line <-lines) {
       // 各行の文字数桁にあわせてフォーマットして出力する
        val numSpaces = maxWidth - widthOfLength(line)
        val padding = " " * numSpaces
        print(padding + line.length + " | " + line)
    }
}
// 入力がない場合はエラーを出力して終了
else
    Console.err.println("please enter filename")

結果はこんな感じ

% scala file.scala file.scala                              [/home/naoabe/Works/tech/scala]
 1 | 
23 | import scala.io.Source
31 | // 各行の文字数の桁数を取得する処理:何度も使うので関数化
55 | def widthOfLength(s:String) = s.length.toString.length
16 | // 入力がある場合は処理開始
22 | if(args.length > 0) {
24 |     // ファイルを読み込んでリスト化する
47 |     // getLinesがファイルの内容をイテレータ化してtoListでそれをリスト化
57 |     val lines = Source.fromFile(args(0)).getLines.toList
47 |     // reduceLeftが第1、第2要素から順に要素を二つずつ取り出して処理を行う
43 |     // 第1、第2の次は第2、第3と比較していくことで一番長い行の文字数を取得
40 |     val longestLine = lines.reduceLeft(
53 |         (a, b) => if ( a.length > b.length) a else b
 6 |     )
26 |     // 一番長い行の文字数が何桁あるかを取得
46 |     val maxWidth = widthOfLength(longestLine)
17 |     // 各行ごとに表示処理
25 |     for (line <-lines) {
35 |        // 各行の文字数桁にあわせてフォーマットして出力する
55 |         val numSpaces = maxWidth - widthOfLength(line)
38 |         val padding = " " * numSpaces
52 |         print(padding + line.length + " | " + line)
 6 |     }
 2 | }
22 | // 入力がない場合はエラーを出力して終了
 5 | else
49 |     Console.err.println("please enter filename")
 1 | 
いじょー

とりあえず3章まででScalaの概要を駆け足でさらっていった感じ

4章以降は細かい内容に入っていきますー、、とのことです。頑張ろう。