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


Scalaコップ本の第7章に進みますよー、7章は組み込み制御構造についてですよー

組み込みの制御構造

Scalaの制御構造は以下の6つだけのシンプル思考デス

  • if
  • while
  • for
  • try
  • match
  • 関数呼び出し

Scalaは最初から関数リテラルを持っているので、高水準の制御構造はライブラリに蓄積しているらしいデス。うん、なんとなくPythonみたいな雰囲気がするな。ライブラリ云々の話は9章でやるみたいなので、またあとでー

Scalaは関数型指向なのでifとかfor,try,matchは値を返せるようになってるためにコードを単純化できるっぽいんだけども、そこら辺は実際の制御式を見ていきましょうかねー

if式

うん、ifですな。とりあえず命令型っぽいコードを書いて後から直すよー

// 適当な条件で値が入ったり入らなかったり
val hoge = "hoge"
// if式開始
var result = "default"
if(!hoge.isEmpty)
    result = hoge
関数型に書き換えてみましょうー

上の式でダメダメなvar使っている部分をvalにするのが関数型のトレンドデス。あとifは式なので欲しい値resultに直接ぶっこんでしまいましょー

// 適当な条件で値が入ったり入らなかったり
val hoge = "hoge"
// if式開始
val result = if(!hoge.isEmpty) hoge else "default" 

無理やり一行にまとめた感もあるけども超すっきりじゃないかしら?

結果値resultはvalなので書き換えられる心配もないし、この形式であれば等式推論をサポートしやすくなるので素晴らしいのデス

等式推論

valで定義される変数とその計算式は同じものとみなせることを等式推論というらしいです。条件は式に副作用が無いことですねー。等式推論が使えると一時的に定義した変数を使わずに計算式を用いることができるようになるのですな。

例えば下の処理は上の式の結果値resultをprintln(result)したのと同じ結果になりマス

// 適当な条件で値が入ったり入らなかったり
val hoge = "hoge"
//  等式推論使って表示しますよー
println(if(!hoge.isEmpty) hoge else "default")

resultっていう変数定義すらいらなくなるので使い捨ての式がシンプルになるのね、なるほど

とりあえず

valつかえ、valってことね

whileループ

繰り返しー

前の章でやったような最大公約数を求める処理のwhile版をサンプルに書いてみます

def gcdLoop(x: Long, y:Long):Long = {
    var a = x
    var b = y
    // whileでユークリッド互除法をやるっす
    while(a != 0){
        val temp = a
        a = b % a
        b = temp
    }
    // 値を返すよー
    b
}

実行結果はこんな感じ

scala> gcdLoop(3,4)
res7: Long = 1

scala> gcdLoop(123,24)
res8: Long = 3

うん、上手くいってますねー

do-whileも使えますよー

標準入力を読み出すサンプルメソッドデス

def readMethod() = {
    var line = ""
    do {
        line = readLine()
        println("Read: " + line)
    }while(line != "")
}

ちょっとわかりにくいけど結果ですよー、標準入力を取得して空行のときに終了してます

scala> readMethod
Read: ほげほげ
Read: ふがふが
Read: 
whileとdo-whileはUnit型

意味のある結果値が得られないwhileとdo-whileは式ではなく「ループ」でありマスので、結果値型はUnit型になるみたいデス。

Unit型の値

ちなみにUnit型は値()をとるので、値を取らないJavaのvoid型とは違うんだぜーのお話

雰囲気のわかるサンプルコードを書いてみますよ

// printlnするだけのUnit型のメソッド
scala> def hi(){ println("hi") }
hi: ()Unit
// 結果値が()と等しいことがわかりますなー
scala> hi() == ()
hi
res7: Boolean = true
// 引数省略してみてもいけますね、蛇足ですがー
scala> hi == ()  
hi
res8: Boolean = true

なので、こんな処理はエラー出ますよ

def readMethod() = {
    var line = ""
    // String型にUnit型を代入してます
    while((line = readLine()) != "")
        println("Read: " + line)
    }
}
// エラーメッセージ
// Unit型とString型の!=比較は必ずTrueになるよ!いいの?(意訳)
<console>:6: warning: comparing values of types Unit and java.lang.String using `!=' will always yield true
           while((line = readLine()) != "")

Scalaでは”代入という式”の結果値がUnit型になるので、例えば上の式だと永久にループしてしまうので危険デス

ちなみに雰囲気をつかむために書いてみたこういう単純なのでもダメでしたー

scala> var hoge = ""
hoge: java.lang.String = 

// 代入式の等価評価はダメですねー
scala> (hoge = "huga") != ""
<console>:6: warning: comparing values of types Unit and java.lang.String using `!=' will always yield true
       (hoge = "huga") != ""
                       ^
whileループの存在意義

そもそも純粋関数型言語はループが存在しない場合多いらしく、Scalaでループを残している理由は命令型のひとでも扱い易いように…らしいデス

そもそもwhileループで使ったサンプルは軒並みvarを使用しているので、関数型的指向から外れますねー

関数型なら再帰でしょ?

関数型のアプローチだと再帰処理にしましょーってことで、最初に出てきた最大公約数を求める処理を再帰で書いてみますよー

def gcd(x:Long, y:Long): Long = if(y == 0) x else gcd(y, x % y)

うん、前の章で似たようなのを作ったっすね

さて実行してみると、無事処理が行われますな

scala> gcd(3,4)
res12: Long = 1

scala> gcd(123,24)
res13: Long = 3
ループに関して行間を読んでみた

関数型で書きたかったらループ使うなよ!(`・ω・´)


…間違ったメッセージを受信した可能性もあるので、自己責任で(・∀・)

いじょー

iPhone4だの日本戦だので全然時間がとれなかったので、今回はこんなところでー

次回はforからやりますよー