Rustコトハジメ

プログラミング言語Rustと競プロに関する情報をお届けします。

掛け算の順序問題をRustで解決する

f:id:akiradeveloper529:20190307141741j:plain

掛け算の順序問題というのがあります。例えば、りんごが12個入った箱が3個あったら全部で何個でしょう?という問題に対して、12x3と答えるのが正しくて、3x12と答えるのは間違ってると指導される問題です。間違った順序で解答すると、バツがつけられることもあるそうです。

日本ではかなり古くから存在していて、1972年の新聞で取り上げられたとウィキに書いてありますが、私は経験したことがありません。ラッキーだったのでしょう。いずれにしろ、今回、この難問に対してRustを用いて挑んでみることにします。

今回は、掛け算の順序問題のうち、長方形の面積を対象とします。長方形の面積は縦x横で計算するのが正解で、横x縦で計算するのは間違いとします。この子供の無念を晴らしましょう。

f:id:akiradeveloper529:20190307140847j:plain

まず、学校の先生を実装します。学校の先生は、横x縦という順番で掛け算をしようとするとコンパイルしてくれません。学校の先生がやっていることはこういうことなのです。

このように、数値のような値をクラス(newtype)に包んで型安全をもたらすことは、よくやるプログラミング技法ですが、お金を扱う分野などでは実際に使うこともあるそうです。例えば、誤って円とドルを足し算してしまったら大変ですよね。

use std::convert::{Into, From};
use std::ops::{Add, Mul};

#[derive(Copy, Clone)]
struct Tate(i32);
#[derive(Copy, Clone)]
struct Yoko(i32);
#[derive(Debug)]
struct Menseki(i32);

impl Mul<Yoko> for Tate {
    type Output = Menseki;
    fn mul(self, rhs: Yoko) -> Menseki {
        Menseki(self.0 * rhs.0)
    }
}
impl Add for Menseki {
    type Output = Menseki;
    fn add(self, rhs: Menseki) -> Menseki {
        Menseki(self.0 + rhs.0)
    }
}

fn main() {
    let tateA = Tate(5);
    let yokoA = Yoko(3);
    // let mensekiA_0 = yokoA * tateA; // can't compile
    let mensekiA_0 = tateA * yokoA;
    
    let tateB = Tate(3);
    let yokoB = Yoko(7);
    // let mensekiB_0 = yokoB * tateB; // can't compile
    let mensekiB_0 = tateB * yokoB;
    
    let mensekiAB_0 = mensekiA_0 + mensekiB_0;
    println!("menseki is {:?}", mensekiAB_0);
}

このコードに対して少し修正をして、Yoko * Tateコンパイルを通すように出来れば、Rustの勝利と言えそうです。私はこんなコードを書いてみました。

struct Value(i32);

impl<A, B> Mul<B> for A
where A: Into<Value>, B: Into<Value> {
    type Output = Value;
    fn mul(self, rhs: B) -> Value {
        Menseki(a.into().0 * b.into().0)
    }
}

しかし、残念ながら、Rustはこのコードを受け付けてくれませんでした。これは、「AとかBとかいうふわっふわな型に対してMulを実装したいなら、Mulを定義したcrateの中じゃないとだめ」という意味だと思います。

error[E0210]: type parameter `A` must be used as the type parameter for some local type (e.g., `MyStruct<A>`)
  --> prog.rs:36:1
   |
36 | / impl<A, B> Mul<B> for A
37 | | where A: Into<Value>, B: Into<Value> {
38 | |     type Output = Value;
39 | |     fn mul(self, rhs: B) -> Menseki {
40 | |         Value(self.into().0 * rhs.into().0)
41 | |     }
42 | | }
   | |_^ type parameter `A` must be used as the type parameter for some local type
   |
   = note: only traits defined in the current crate can be implemented for a type parameter

Rustすら掛け算の順序問題を解決することは出来ないことがわかり、絶望しています。(誰かいいアイデアはありませんか?)

結局私は、敗戦処理同然のコードを書き、逃げることにしました。子供の無念を晴らすことが出来なかったという現実から目を背けるように。というか、当初予想していた結論とは違ってしまい興醒めしたため。またなにか社会問題を見つけてリベンジしましょう。Rustになら出来るはず。

impl From<Tate> for Value {
    fn from(a: Tate) -> Value {
        Value(a.0)
    }
}
impl From<Yoko> for Value {
    fn from(a: Yoko) -> Value {
        Value(a.0)
    }
}

fn mul<A,B>(a: A, b: B) -> Menseki
where A: Into<Value>, B: Into<Value> {
    Menseki(a.into().0 * b.into().0)
}

fn main() {
    // ... same
    let mensekiAB_1 = mul(yokoA, tateA) + mul(yokoB, tateB);
    println!("menseki is {:?}", mensekiAB_1);
}