はじめてなElixir(4) 有理数モジュールをつくってみる

前回は[はじめてなElixir(3)](https://qiita.com/kikuyuta/items/b6c74f3cb677aac4406e)で無限ストリームや文字列で遊んでみました。本当は並行プログラミングにいくつもりだったんですが、ちょっと長い寄り道です。

# 有理数（分数）の計算を実装してみる
Elixir の整数が bignum なのはうれしいですね。実装するほうは大変でしょう。こんな実用性を求める言語によく bignum を導入しましたね。

```
defmodule Factrial do
  def fact(0) do 1 end
  def fact(n) do n * fact(n-1) end
end
```

なんて作って

```
iex(2)> Factrial.fact(0)
1
iex(3)> Factrial.fact(10)
3628800
iex(4)> Factrial.fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
```

なんて遊べるのは33年前に初めて大学で研究室に所属して、Sun2 (SunOS1.2) の Franz Lisp で遊んで以来の感動です。1000の階乗とか求めてコンソールの画面が数字で埋まるのを見て満足した記憶が蘇りました。

はてそうなると今度は有理数も扱って欲しくなるではないですか。

## mix 使ってみました
Elixir本とか [Elixir School](https://elixirschool.com/ja/lessons/basics/mix/) とか
[Elixir | Mix | プロジェクト管理 CLI ツール Mix によるプロジェクト新規作成](https://qiita.com/tbpgr/items/221fc743a23cd9142d53)
とか見まして、見よう見まねで。

ついでに git も初体験。構成管理って RCS と make でやってた（ことがある）人間なんで、github も clone はするけど自分で commit / push とかしたことなかったんですよ。何かと新しいことばかりで目眩がします。

## プログラムはこんな感じで
ま、大した難しいプログラムじゃないです。まずはプリアンブルがでかくなりました。

### コメントをしっかり書いてみる
あとでハマるかもしれませんがとにかく doc としてなんか書いておこうという模範的な態度ということで。

```
defmodule Rational do

  @moduledoc """
  A simple sample module for rational number calicuration.
  """

  @doc """
  - Types
  There is not new defined types.
  Tupples describe rational numbers here.
  The module might define a new rational type hiding numerator/denominator.

  - Functions
  -- add/2
     addition of two rational numbers
  -- sub/2
     subtraction of two rational numbers
  -- mul/2
     muliplication of two rational numbers
  -- div/2
     division of two rational numbers
  -- rof/1
     Reduction of Fraction of a rational number
  -- rcd/2
     Reduction to Common Denominator of a list of two rational numbers.
     Returns a list of two rational numbers whose denominators are the same.
  """
```

こんな書き方で良いのでしょうか。

### プログラム本体の先頭
div って関数を作りたいんですが Kernel モジュールの関数名とぶつかるんです。それも Kernel.div って書かなくて div って書いただけで衝突する。なので、div と書いたときに Kernel.div を指さないように except: 付きで Kernel を import します。

```
  import Kernel, except: [div: 2]
  require Integer
```

あと、最大公約数関数 gcd を使いたいので Integer モジュールを require しときました。ちなみに gcd はあるのに lcm 関数はありません。大した難しくもないのになぜ省略する。

### データ型
分数を {分子, 分母} の形で表現することにしました。

```
{1, 2} # これが 1/2 を表す。
```

もうちょっと勉強して defstruct を使ってみたいですが、今日のところはこれで勘弁。

### 約分関数と通分関数
分数の計算にはとにかく必要ですね。

```
  def rof({n, d}) do
    {Kernel.div(n, Integer.gcd(n, d)), Kernel.div(d, Integer.gcd(n, d))}
  end
```

約分関数 rof/1 は分子と分母をそれぞれ最大公約数で割ればおしまいです。

```
iex(1)> Rational.rof({2,4})
{1, 2}
iex(2)> Rational.rof({4,6})
{2, 3}
```

通分関数 rcd/1 はデータの受け渡しが複雑になります。2つの分数はリストで渡して、リストで返してもらうことにしました。

```
  def rcd([{n1, d1}, {n2, d2}]) do
    d = Kernel.div(d1 * d2, Integer.gcd(d1, d2))
    [{Kernel.div(n1 * d, d1), d}, {Kernel.div(n2 * d, d2), d}]
  end
```

先に分母を計算して、それから答えのリストを返します。

```
iex(1)> Rational.rcd([{1,2},{1,4}])
[{2, 4}, {1, 4}]
iex(2)> Rational.rcd([{1,2},{1,3}])
[{3, 6}, {2, 6}]
iex(3)> Rational.rcd([{1,2},{1,2}])
[{1, 2}, {1, 2}]
iex(4)> Rational.rcd([{4,12},{3,12}])
[{4, 12}, {3, 12}]
iex(5)> Rational.rcd([{4,12},{3,1}]) 
[{4, 12}, {36, 12}]
iex(8)> Rational.rcd([{1,3},{1,6}])                
[{2, 6}, {1, 6}]
iex(9)> Rational.rcd([{1,3},{1,6}]) |> Rational.rcd
[{2, 6}, {1, 6}]
```

最後の、一度 rcd で出た結果は何回やってももう同じです。こうしたくて rcd/2 じゃなくて rcd/1 にしました。

### 加減乗除の関数

あとは公式の通り加減乗除の関数を作ります。乗除算は簡単です。加減算は一旦通分してからの計算です。全部最後に約分してます。

```
  def add({n1, d1}, {n2, d2}) do
    [{x, c}, {y, c}] = rcd([{n1, d1}, {n2, d2}])
    rof({x + y, c})
  end

  def sub({n1, d1}, {n2, d2}) do
    [{x, c}, {y, c}] = rcd([{n1, d1}, {n2, d2}])
    rof({x - y, c})
  end

  def mul({n1, d1}, {n2, d2}) do
    rof({n1 * n2, d1 * d2})
  end

  def div({n1, d1}, {n2, d2}) do
    rof({n1 * d2, d1 * n2})
  end
```

### テストを書いてみる

上の結果ですが、手でもやりましたが、今回は mix を導入したので試験パターンを入れてみました。

```rational_test.exs
defmodule RationalTest do
  use ExUnit.Case
  doctest Rational

  test "simple add test" do
    assert Rational.add({1, 2}, {1, 3}) == {5, 6}
  end

  test "simple sub test" do
    assert Rational.sub({1, 2}, {1, 3}) == {1, 6}
  end

  test "simple mul test" do
    assert Rational.mul({1, 2}, {1, 3}) == {1, 6}
  end

  test "simple div test" do
    assert Rational.div({1, 2}, {1, 3}) == {3, 2}
  end

  test "simple rof test" do
    assert Rational.rof({4, 6}) == {2, 3}
  end

  test "simple rcd test" do
    assert Rational.rcd([{1, 3}, {1, 2}]) == [{2, 6}, {3, 6}]
  end
end
```

本来ならもっと沢山書くべきですね。特に今回は難しくないのでもっと書けばよかったな。

```
$ mix test
......

Finished in 0.05 seconds
6 tests, 0 failures

Randomized with seed 228152
```

となってテストはパスします。実はこれを出す前にボロボロと間違いを指摘されてました。

# 参考文献
[Elixir 1.7.3](https://hexdocs.pm/elixir/)
[Elixir School](https://elixirschool.com/ja/) 
[Elixir | Mix | プロジェクト管理 CLI ツール Mix によるプロジェクト新規作成](https://qiita.com/tbpgr/items/221fc743a23cd9142d53)