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


Scalaコップ本の題6章の11節からやりますよー、メソッドの多重定義からデス

とりあえず頑張って6章の残り全部やってみますデスはい

前回までに作成したクラス

前回までに作成した有理数クラスRationalはこんな感じですねー

class Rational(n:Int, d:Int) {
    // 分母が0で無いことをチェック
    require(d != 0)
   // 分子分母の最大公約数を取得
    private val g = gcd(n.abs, d.abs)
    // 正規化した分子分母をフィールドとして格納
    val numer = n / g
    val denom = d / g
    // 分子のみをクラスパラメータとする補助コンストラクタを設定
    def this(n:Int) = this(n, 1)
    // クラスのtoString表現を見やすく上書き
    override def toString = numer + "/" + denom
    // 2つのRationalオブジェクトを加算するメソッド
    def + (other:Rational):Rational = {
        new Rational(
            this.numer * other.denom + other.numer * this.denom,
            this.denom * other.denom
        )
    }
    // 2つのRationalオブジェクトを乗算するメソッド
    def * (other:Rational):Rational = {
        new Rational(this.numer * other.numer,  this.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
    }
    // 最大公約数を計算するメソッド
    private def gcd(a:Int, b:Int):Int = {
        if(b == 0) a else gcd(b, a % b)
    }
}

メソッドの多重定義

前回までに作成したRationalクラスでは、Rationalオブジェクト同士の加算と乗算はできるようになったもののRational × 整数みたいな計算ができないのでコレを何とかしましょー

// Rationalオブジェクトの生成
scala> val r1 = new Rational(1,3)
r1: Rational = 1/3
// Rational * Intだと計算できないよ!って怒られます
scala> r1 * 3
<console>:7: error: type mismatch;
 found   : Int(3)
 required: Rational
       r1 * 3
            ^

上の例では前回定義した"*"演算子が被演算子としてRationalのみを対象としているので、Int型はとれませんよー、というエラーっぽいです。なので、"*"演算子にRational以外にIntをとるパターンを追加してやるのが良いらしいです。

こんな感じで同名のメソッドが違う用途(引数の型の違いとか)で複数存在しているような状態を”多重定義されている”と呼ぶらしいですねー

んじゃ多重定義しますよー

ちょっぴり簡易化したRationalクラスに*演算子の多重定義をしますよー

Rational * Rationalに加えてRational * Intを実装しマス

class Rational(n:Int, d:Int) {
    require(d != 0)
    private val g = gcd(n.abs, d.abs)
    val numer = n / g
    val denom = d / g
    def this(n:Int) = this(n, 1)
    override def toString = numer + "/" + denom
    // 2つのRationalオブジェクトを乗算するメソッド
    def * (other:Rational):Rational = {
        new Rational(this.numer * other.numer,  this.denom * other.denom)
    }
    // Rational * Intを処理するメソッド(多重定義)
    def * (i:Int):Rational = {
        // i:Intは分子:i、分母:1なのでこんな計算になりますよー
        new Rational(this.numer * i,  this.denom )
    }
    private def gcd(a:Int, b:Int):Int = {
        if(b == 0) a else gcd(b, a % b)
    }
}

同名のメソッドを定義するだけなのであっさりですねー

実行してみますよー
// Rationalクラス
scala>  val r1 = new Rational(1,3)              
r1: Rational = 1/3
// Rational * Intもあっさり出来たー
scala> r1 * 3
res4: Rational = 1/1

無事に動作しましたよー

多重定義したメソッドは被演算子の型によって自動で振り分けられるわけですな(`・ω・´)

でもこっちはだめなのね

試しに実行してみたInt * Rationalは当たり前のようにダメですな

scala> 3 * r1
<console>:7: error: overloaded method value * with alternatives (Double)Double <and> (Float)Float <and> (Long)Long <and> (Int)Int <and> (Char)Int <and> (Short)Int <and> (Byte)Int cannot be applied to (Rational)
       3 * r1

もしもこういう処理がやりたかったらIntに某か定義しないとダメダメってことですかねー、交換法則がカンタンに成り立たないあたりが数学チックで嫌な気分になりますねー(|| ゚Д゚)トラウマー

こいつの解決策については次の節でやりますよー

Rationalクラスを完成させますよー

ようやく演算操作がそれっぽくなってきたので、減算・除算も付け加えてRationalモデルを完成させてみますよー。各演算は被演算子として整数でも使いたいので多重定義しまくります

んじゃ一気に書きましょー
class Rational(n:Int, d:Int) {
    // 分母が0で無いことをチェック
    require(d != 0)
   // 分子分母の最大公約数を取得
    private val g = gcd(n.abs, d.abs)
    // 正規化した分子分母をフィールドとして格納
    val numer = n / g
    val denom = d / g
    // 分子のみをクラスパラメータとする補助コンストラクタを設定
    def this(n:Int) = this(n, 1)
    // クラスのtoString表現を見やすく上書き
    override def toString = numer + "/" + denom
    //// 2つのRationalオブジェクトを加算するメソッド
    def + (other:Rational):Rational = {
        new Rational(
            this.numer * other.denom + other.numer * this.denom,
            this.denom * other.denom
        )
    }
     // 整数用を多重定義
    def + (i:Int):Rational = {
        new Rational(this.numer + i * this.denom, this.denom)
    }
    //// 2つのRationalオブジェクトを減算するメソッド
    def - (other:Rational):Rational = {
        new Rational(
            this.numer * other.denom - other.numer * this.denom,
            this.denom * other.denom
        )
    }
     // 整数用を多重定義
    def - (i:Int):Rational = {
        new Rational(this.numer - i * this.denom, this.denom)
    }
    //// 2つのRationalオブジェクトを乗算するメソッド
    def * (other:Rational):Rational = {
        new Rational(this.numer * other.numer,  this.denom * other.denom)
    }
    // 整数用を多重定義
    def * (i:Int):Rational = {
        new Rational(this.numer * i,  this.denom )
    }
    //// 2つのRationalオブジェクトを除算するメソッド(逆数の乗算デス)
    def / (other:Rational):Rational = {
        new Rational(this.numer * other.denom,  this.denom * other.numer)
    }
    // 整数用を多重定義
    def / (i:Int):Rational = {
        new Rational(this.numer,  this.denom * i )
    }
    // 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
    }
    // 最大公約数を計算するメソッド
    private def gcd(a:Int, b:Int):Int = {
        if(b == 0) a else gcd(b, a % b)
    }
}
実行するとこんな感じー

新しく追加した減算・除算を軽く実行してみますよ

// Rationaオブジェクトを生成
scala> val r1 = new Rational(1,3)
r1: Rational = 1/3
scala> val r2 = new Rational(1,4)
r2: Rational = 1/4

// Rational同士の除算
scala> r1 / r2
res6: Rational = 4/3
// Rational同士の減算
scala> r1 - r2
res7: Rational = 1/12

// RationalとIntの除算
scala> r1 / 3
res8: Rational = 1/9
// RationalとIntの減算
scala> r1 - 3
res9: Rational = -8/3

おおー、うまくできたー

暗黙の型変換

上記のRationalクラスでRational * Intなんかの計算ができるようになったのですが、Int * Rationalはエラーを吐いてダメダメでした。

とりあえず考えつくのはInt型にRationalとの演算を多重定義すればよいんじゃね?という力技くらいなんですが、Rationalは独自にでっち上げたクラスでScalaの標準じゃないので無理らしいデス

そういうときは暗黙の型変換を定義しましょう

Int型への定義は無理ぽですがScalaには任意の型を別の型に変換する仕組み”暗黙の型変換"があるので、コレを利用してIntをRationalと演算させるときのみIntをRationalに変換するようにしてみますよー

インタープリタに対して呪文を一つ加えますよー

// implicit宣言しますよー
scala> implicit def intToRational(x:Int) = new Rational(x)
intToRational: (Int)Rational

// 無事計算できましたー
scala> 3 * r1                                             
res10: Rational = 1/1

implicit はコンパイラ様に様々な状況で自動的に適用しろ!という宣言をするものらしいですが、アバウト過ぎて魔法に見えてしまいますなー

ちなみに名前で変換ルールを定義しているわけではないみたいなのでこういうのでもOKでした

scala>  implicit def hoge(x:Int) = new Rational(x)         
hoge: (Int)Rational
                                         
scala> val r1 = new Rational(3,1)
r1: Rational = 3/1

scala> 3 * r1
res0: Rational = 9/1

詳しい内容は21章でやるらしいので実際の処理はそこまでwktkすることにしまっす

暗黙の型変換の注意

暗黙の型変換は、そのメソッドがインタープリタのスコープ内に収まらないといけないらしいデス。例えばRationalクラスに定義しても正常に動かないとのこと。スコープに関しても21章でやるらしいので横においときましょー

演算子メソッドと暗黙的型変換の注意事項


演算子メソッドとか暗黙的型変換とかは便利で使い易いものが作れるんだけど、それが読みやすかったり解りやすかったりするとは限らないんだから色々とバランスとって使えな、な!(超意訳)

とりあえず自分以外の人間(未来の自分も含む)のことを考えた設計&実装が大事ね

いじょー


とりあえずなんとかかんとか6章の内容がおわりましたー、次は7章ですよー

ついでにRationalクラスは今後もガッツり使うらしいので忘れないようにしないとな…が、がんばろう