Elixir ではクロージャは作れない?

Elixir の関数は、関数を返すことができて、その関数は元の環境を覚えている。だからこないだのエントリでは「クロージャだ」と書いた。
だけど、今日になって違うんじゃないかと思うようになった。というのは、返ってきた関数は確かに元の環境を覚えているけど変更はできないみたいだからだ。
例えば、JavaScript で典型的なクロージャを書いてみよう。下の make_counter 関数の返す関数は、呼び出されるごとに 1 ずつ大きな数を返す。

function make_counter(init) {
  var count = init;
  return function() {
    count = count + 1;
    return count
  }
}

var counter = make_counter(0);
console.log(counter());
console.log(counter());
console.log(counter());
^o^ > node counter.js
1
2
3

Elixir で同じように書いてみよう。

defmodule Counter do

  def make(init) do
    count = init
    fn ->
      count = count + 1
      count
    end
  end
end

counter = Counter.make(0)
IO.puts counter.()
IO.puts counter.()
IO.puts counter.()

見てわかるように、JavaScript のコードをほぼそのまま Elixir で書き直したものだ。ところが、これを実行してみると:

^o^ > elixir counter.exs
1
1
1

このとおり、関数を何度呼び出しても数が増えていかない。
これって、やっぱりクロージャとは言わないんじゃ?それとも何か間違ってる?

Elixir 練習問題 ModulesAndFunctions-6

今度のは一種の数あて問題だ。

defmodule Chop do

  def guess(actual, a..b) do
    g = div(a + b, 2)
    _guess(actual, a..b, g)
  end

  def _guess(actual, _, g) when actual == g do
    IO.puts("It is " <> to_string(g))
    IO.puts(g)
  end
  def _guess(actual, a.._, g) when actual < g do
    IO.puts("It is " <> to_string(g))
    guess(actual, a..(g - 1))
  end
  def _guess(actual, _..b, g) do
    IO.puts("It is " <> to_string(g))
    guess(actual, (g + 1)..b)
  end

end
^o^ > iex practice_6_6.exs
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Chop.guess(273, 1..1000)
It is 500
It is 250
It is 375
It is 312
It is 281
It is 265
It is 273
273
:ok

あれ、関数の戻り値が表示されちゃうな。まあ、いいか。

Elixir 練習問題 ModulesAndFunctions-1~3

まずは、本の説明に出てきた Time モジュールに triple 関数を追加。

defmodule Times do

  def doubles(n) do
    n * 2
  end

  def triple(n) do
    n * 3
  end

end

そしてこれを2つのやり方で実行。1つは iex の引数として読み込んで実行する。

^o^ > iex practice_6_1.exs
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Times.triple(2)
6

もう1つのやり方は、iex の中で c コマンドを使ってファイルをコンパイルして読み込む。

^o^ > iex
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c "practice_6_1.exs"
[Times]
iex(2)> Times.triple(2)
6

こっちのやり方だと、モジュールをコンパイルした Elixir.Times.beam ファイルができるんだな。

最後に、Time モジュールに quadruple 関数を追加。

defmodule Times do

  def doubles(n) do
    n * 2
  end

  def triple(n) do
    n * 3
  end

  def quadruple(n) do
    doubles(n) * doubles(n)
  end

end

実行してみる。

^o^ > iex practice_6_3.exs
Eshell V8.0  (abort with ^G)
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Times.quadruple(2)
16

Elixir 練習問題 Functions-4

Elixir では、関数を返す関数を書くことができる。しかも、返ってくる関数はそれが定義された元の環境を覚えている。つまりクロージャだ。
というわけで、練習問題をやってみよう。

iex(1)> prefix = fn a -> (fn b -> a <> " " <> b end) end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(2)> mrs = prefix.("Mrs")
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(3)> mrs.("Smith")
"Mrs Smith"
iex(4)> prefix.("Elixir").("Rocks")
"Elixir Rocks"

Elixir 練習問題 Functions-3

iex(1)> fizzbuzz = fn
...(1)>   0, 0, _ -> "FizzBuzz"
...(1)>   0, _, _ -> "Fizz"
...(1)>   _, 0, _ -> "Buzz"
...(1)>   _, _, n -> n
...(1)> end
#Function<18.52032458/3 in :erl_eval.expr/5>
iex(6)> fizzbuzz2 = fn n -> fizzbuzz.(rem(n, 3), rem(n, 5), n) end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(7)> fizzbuzz2.(10)
"Buzz"
iex(8)> fizzbuzz2.(11)
11
iex(9)> fizzbuzz2.(12)
"Fizz"
iex(10)> fizzbuzz2.(13)
13
iex(11)> fizzbuzz2.(14)
14
iex(12)> fizzbuzz2.(15)
"FizzBuzz"
iex(13)> fizzbuzz2.(16)
16

Elixir 練習問題 Functions-1

「プログラミングElixir」、第I部を読み終わった。これから少しずつアウトプットしていこう。
とりあえずは、第5章 無名関数 の練習問題から、関数を3つ(list_concat, sum, pair_tuple_to_list)作れ、と。

まずは list_concat

iex(1)> list_concat = fn l1, l2 -> l1 ++ l2 end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex(2)> list_concat.([:a, :b], [:c, :d])
[:a, :b, :c, :d]

次に sum

iex(3)> sum = fn a, b, c -> a + b + c end
#Function<18.52032458/3 in :erl_eval.expr/5>
iex(4)> sum.(1, 2, 3)
6

最後に pair_tuple_to_list

iex(5)> pair_tuple_to_list = fn {a, b} -> [a, b] end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(6)> pair_tuple_to_list.( {1234, 5678} )
[1234, 5678]