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


Scalaコップ本の6章の続きをやりますよー、今回は前回作成したクラスに演算メソッドを追加したりあれやこれやしていきますデス

ちなみに前回までに作成した有理数演算用でimmutableなクラスのRationalはこんな感じです

// クラス定義(分子:n, 分母:dがクラスパラメータ)
class Rational(n:Int, d:Int) {
    // 分母が0の場合は前提条件違反でエラー
    require(d != 0)
    // toStringメソッドをオーバーライド
    override def toString = n + "/" + d
}

加算の追加

まずは計算式を定義しますー

とりあえず整数による分数表現である有理数の加算なので、分数の加算を模式的に書いてみるっす

(a / b) + (c / d) = (a * d + c * b) / (b * d)

分数を平で書いているから異常に読みづらいけども、とりあえず加算後の分子は (a * d + c * b) で分母が(b * d)になるのがわかりますな。

計算式をとりあえずコードに落としこみますー

上で作った計算式をとりあえずのコードに落としこんでミマス。ちなみにRationalクラスはimmutableなので自分自身に新しい有理数を加えずに、加算の結果は新しいRationalクラスとして返されるようにしマス

class Rational(n:Int, d:Int) {
    require(d != 0)
    override def toString = n + "/" + d
    // RationalオブジェクトにRational型のotherを加える処理デス
    def add(other: Rational):Rational = {
        new Rational(n * other.d + other.n * d, d * other.d)
    }
}
若干脱線してクラス型の話

どうやらクラスとして定義したものは型として使えるみたいですねー

基本型についてやってたときに「基本型もオブジェクトだからScalaではJavaのようにプリミティブ型とクラス型で区別しませんよー」(超意訳)って書いてあった気がするのは、コレと基本型の話だと思っていのかしら?

閑話休題

…と若干脱線しておりましたが上で書いたコードは残念ながらエラーで動きません

// other.dとother.nにはアクセスできねーよ、とコンパイラ様がお怒りです
<console>:9: error: value d is not a member of Rational
               new Rational(n * other.d + other.n * d, d * other.d)
                                      ^
<console>:9: error: value d is not a member of Rational
               new Rational(n * other.d + other.n * d, d * other.d)
                                                                               ^

そもそも、コンストラクタ引数にvalやvarを定義しないと外部からアクセスできなくて、上のように定義されたクラスパラメータはvarやvalの定義がないので、Rationalクラスのnとかdを外部から使えないらしいデス

ちなみにクラスパラメータは内部的にprivate final になるみたいですね、なるほどー

フィールドを追加

外部からのアクセスができるようにフィールドを追加しますよー、ちなみにフィールドはクラス変数?という感じで独自解釈してますけどもあってるかしら?

n, dで初期化したnumer, denomフィールドを追加しますよー

class Rational(n:Int, d:Int) {
    require(d != 0)
    // フィールドを追加します
    val numer: Int =n
    val denom: Int = d
    override def toString = numer + "/" + denom
    // RationalオブジェクトにRational型のotherを加える処理デス
    def add(other: Rational):Rational = {
        new Rational(
            //  フィールドを追加したのでアクセスできますよ
            numer * other.denom + other.numer * denom,
            denom * other.denom
        )
    }
}
まずはアクセスチェック

定義したフィールドにアクセスできるかを確認しますよー

// まずはRationalオブジェクトを生成
scala> val r = new Rational(1,3)
r: Rational = 1/3
// 分子にアクセス
scala> r.numer
res2: Int = 1
// 分母にアクセス
scala> r.denom
res3: Int = 3

OKですねー

実際に加算してみまっす
// r1, r2の2つのRationalオブジェクトを生成します
scala> val r1 = new Rational(1,3)
r1: Rational = 1/3
scala> val r2 = new Rational(1,4)
r2: Rational = 1/4
// 加えてみますー
scala> r1 add r2
res4: Rational = 7/12
// 正式にはこういう呼だしですな
scala> r1.add(r2)
res5: Rational = 7/12

こっちもOKっすな

自己参照

上のコードでは呼び出し元(上のメソッドだと加算される方のRationalクラス)のnumer, denomは単独で書いているんだけど、呼び出し元のもの!と明示するにはthisキーワードをつけましょー、という話です。まあ、いろんな言語でも同じですけどねー

個人的にはthisなしで混乱するのが嫌なのでthisつける方が好みです

ちょっぴり脱線:レシーバとパラメータ

前の章でも出てきたような気がうっすらするけど、引っかかったのでしつこくメモ

オブジェクトO1とO2間で次のように(演算なんかの)メソッド処理が行われる場合にO1をレシーバ・O2をパラメータと呼ぶデス

O1.メソッド(O2)
// 中置演算子だったらこうかな
O1 演算子 O2

上のほうで呼び出し元って適当に呼んでいるのがレシーバ、そうじゃないのがパラメータですな

んじゃthis使ってメソッド追加しますよ

2つのRationalオブジェクトを比較してレシーバのRationalオブジェクトがパラメータのRationalオブジェクトよりも小さいかどうかを判定しますよー

def lessThan(other:Rational) = {
    // 2つの分数の大小関係は分母を揃えた時の分子で比較しますよー
    this.numer * other.denom < other.numer * this.denom
} 

計算結果はこんな感じデス

// 2つのRationalオブジェクトを生成
scala> val r1 = new Rational(1,3)
r1: Rational = 1/3
scala> val r2 = new Rational(1,4)
r2: Rational = 1/4
// 比較してr1が大きいことを確かめますー
scala> r1 lessThan r2
res6: Boolean = false

//こんどは逆のパターンのオブジェクトを生成
scala> val r1 = new Rational(1,3)
r1: Rational = 1/3
scala> val r2 = new Rational(1,2)
r2: Rational = 1/2
// 比較してr2が大きいことを確かめますー
scala> r1 lessThan r2            
res7: Boolean = true
もう一個追加してみますよー

レシーバとパラメータを比較して大きい方を返すメソッドデス

def max(other: Rational) = {
  // lessThanの比較結果を使って大きい方を返します
  if(this.lessThan(other)) other else this
}

実行してみますよー

// 2つのRationalオブジェクトを生成
scala> val r1 = new Rational(1,3)
r1: Rational = 1/3
scala> val r2 = new Rational(1,4)
r2: Rational = 1/4
// 比較して大きい方を取得します
scala> r1 max r2
res8: Rational = 1/3

// 今度は逆パターンで
scala> val r1 = new Rational(1,3)
r1: Rational = 1/3
scala> val r2 = new Rational(1,2)
r2: Rational = 1/2
// r2が帰ってきますよ
scala> r1 max r2                 
res9: Rational = 1/2

無事できましたね

いじょうー

とりあえず今回までで作成したクラスは以下デス

class Rational(n:Int, d:Int) {
    require(d != 0)
    // フィールドを追加します
    val numer: Int =n
    val denom: Int = d
    override def toString = numer + "/" + denom
    // 2つのRationalオブジェクトを加算するメソッド
    def add(other: Rational):Rational = {
        new Rational(
            numer * other.denom + other.numer * denom,
            denom * other.denom
        )
    }
    // 2つのRationalオブジェクトを比較してレシーバのほうが小さいか判定するメソッド
    def lessThan(other:Rational) = {
        this.numer * other.denom < other.numer * this.denom
    } 
    // 2つのRationalオブジェクトの大きい方を返すメソッド
    def max(other: Rational) = {
        if(this.lessThan(other)) other else this
    }
}

次回は補助コンストラクタなんかをやりますよー、亀の歩みで頑張りマス