【Elixir】ElixirとOOP、プログラミングスタイルの違い

https://qiita.com/advent-calendar/2021/fukuokaex

2021/12/08の回です。

昨日は [Koyoさん](https://qiita.com/koyo-miyamura) の [Elixir + SendGrid でメール送信してみる](https://qiita.com/koyo-miyamura/items/34e369200fe1aeafb0af) でした。

# はじめに

私はfukuoka.exを通じて、Elixirに関わってちょうど1年になります。
普段は~~O社を憎みながら~~Javaでお仕事をするエンジニアなんですが、知人の紹介でElixirを触り始めました。

当然JavaとElixirのパラダイムは大きく違うので、最初は戸惑うことがありました。
今回は振り返りとして、それぞれのプログラミングスタイルの違いを書いていこうと思います。

# ループ処理の取り扱い

**Elixirではfor文などを使う必要がありません。**
「何を言っているんだ？」と思いますが、階乗を計算するソースコードを見てみましょう。

まずはJavaのコードから。はい、普通にfor文でループしていますね。

```Java:Loop.java
static int factorial(int num) {
    int result = 1;
    for (int i = num; i > 0; i--){
        result = result*i;
    }
    return result;
}
```

次にElixirのコードを見てみます。一見すると、ループしているようには見えません。

```Elixir:factorial.java
defmodule Factorial do
  def of(0), do: 1               # 初項
  def of(n), do: n * of(n - 1)   # n>0の項
end
```

階乗の処理は、数学的には以下で定義できます。
factorial.exの関数はそれぞれ初項、n>0の項に対応します。

```math
a_0 = 1 \\
a_n = n \times a_{n-1} \quad (n>0)
```

プログラミングコンテストをやっていると「これって再帰処理じゃね？」と気づくかもしれません。
はい、その通り。Elixirでは「**再帰処理とパターンマッチでループ処理を表現**」します。
Elixirでは、例えば`` Factorial.of(3)`` は以下の流れで実行されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173839/b6173d67-f627-b2f5-36d0-7f5fb489861d.png)

この ``of(0)NG,  of(n)OK`` または ``of(0)OK,  of(n)NG`` というのがポイントです。
関数が実行される際、その引数のパターンに合致するものを探して実行する仕組みを**パターンマッチ**と言います。

# 分岐処理の取り扱い

**Elixirではif文などを使う必要がありません。**
またまた「何いってだこいつ？」と思うかもしれませんが、これもソースコードで確認していきましょう。

まずはJavaのコードから。はい、普通にif文で分岐していますね。
※わざと酷い書き方にしています。

```Java:Branch.java
static boolean strJudge(String str) {
    if (str.isEmpty()) {
        return false;
    }

    if ("hogehoge".equals(str)) {
        return false;
    } else if ("foo".equals(str)) {
        return false;
    } else {
        return true;
    }
}
```

次にElixirのコードを見ていきます。以下のように**ガード節とパターンマッチ**を使っています。

```Elixir:guard.ex
defmodule Guard do
  def what_is(x) when not is_binary(x) do false end  # 文字列か否かの判定
  def what_is(x) when x == "" do false end
  def what_is(x) when x == "hogehoge" do false end
  def what_is(x) when x == "foo" do false end
  def what_is(x) do true end    # xを使用していないのでwarning
end
```

このように、Elixirではif文を使うことなく**ガード節**で記述することが可能です。
Javaのコードでも、リファクタリングする際にガード節にすると可読性が向上することがあります。
そのため、最初からガード節での記述を求めているElixirは**エレガントな言語**だと言えます。

## 応用：複雑な分岐処理について

例えば以下のように、Javaでnestedなif文があるとします。
読みずらいですね。

```Java:NestedIf.java
static Result getResult(int a, int b, int c) {
    int csd = 0;
    int bd = 0; 
    int p = 0;
    int count = 0;

    if (a == 1) {
        csd = 5;
        bd = 5;
        p = 6;
        if (b == 1 && c != 1) {
            count = 1;
        }else {
            count = 20;       
        }
    } else {
        csd = 10;
        bd = 10;
        p = 10;
        count = 1;
    }
    return new Result(csd, bd, p, count);  // Result(int a, int b, int c, int d)
}
```

上記のJavaコードは、Elixirだとこのようにシンプルに書き直すことが出来ます。
nestedなif文は、複数の変数に対して判定する/しないといった場合分けが存在しますが、Elixirではパターンマッチで判定しない変数を``_``で値を無視することが出来ます。
非常に強力なパターンマッチによって、このようにシンプルな記述が実現できています。

```Elixir:case_switch.ex
defmodule CaseSwitch do
  def judge(a, b, c) do
    {csd, bd, p, count} = case {a, b, c} do
      {1, 1, 1} -> {sd, sd, sdi, 20}
      {1, 1, _} -> {sd, sd, sdi, 1}
      {1, _, _} -> {sd, sd, sdi, 20}
      {_, _, _} -> {dd, dd, dd, 1}
    end
  end
end
```


# パターンマッチとパイプライン演算子

Elixirは**データドリブンな言語**です。
もう何を言っているかわからねーと(ry ですが、これもソースコードを見ていきましょう。

例えばJava8以前では、このような書き方をしていました。
Listをfor文でループして、必要な情報はif文の条件でfilterして…という具合に。
※もちろんJava8以降はStreamとラムダ式によってこのような~~クソ~~コードは書かなくてもよくなりましたが。

```Java:Filter.java
static List<User> getUserList() {
    List<User> userList = Arrays.asList(
        new User("foo", 20, "Tokyo"),  // User(String name, int age, String address)
        new User("bar", 15, "Fukuoka"),
        new User("hoge", 18, "Tokushima")
    );
    final List<User> filteredList = new ArrayList<>();
    for (User user : userList) {
        if (user.getAddress().startsWith("T")) {
            filteredList.add(user);
        }
    }
    return filteredList;
}
```

これをElixirで書くと、以下のようになります。
Elixirに馴染みが無いと分かりずらいかもしれませんが、最後の``Enum``の部分がリストにfilterをしている部分です。

```Elixir:user_list.ex
defmodule UserList do
  def get_user_list() do
    user_list = [
      %{ name: "foo", age: 20, address: "Tokyo" },
      %{ name: "bar", age: 15, address: "Fukuoka" },
      %{ name: "hoge", age: 18, address: "Tokushima" }
    ]

    Enum.filter(user_list, (fn %{name: _name, age: _age, address: address} ->
       String.first(address) == "T" end))
  end
end
```

``user_list``は生成されたタイミングで

```
[%{.., .., ..}, %{.., .., ..}, %{.., .., ..}]
```

という形式になっています。また、``Enum.filter``で``user_list``を解析しているときは、一番外側の``[]``が外れて``%{.., .., ..}``という形式で対応付けています。
Elixirでは、「与えられたデータの形式」と「処理するデータの形式」が対応することを**保証しなければなりません。**
もし対応しないような記述をしてしまうと、たちどころにコンパイルエラーとなります。
したがって、**コンパイルのタイミングですでにパターンマッチは保証されている**ということになります。

また、更に``user_list.ex``のソースをこのように書き換えることが出来ます。

```Elixir:user_list.ex
defmodule UserList do
  def get_user_list() do
    (...中略...)

    Enum.filter(user_list, (fn %{name: _name, age: _age, address: address} ->
       String.first(address) == "T" end))
    |> Enum.filter(fn %{name: _name, age: age, address: _address} ->
      age < 20 end)
  end
end
```

追記したのは``|> Enum.filter(fn %{name: _name, age: age, address: _address} -> age < 20 end)``の部分なのですが、これは**その手前のfilterの処理結果を引き継いで(|>)、更にfilterの処理を実行**しています。
パイプライン演算子``|>``が「直前の処理結果を引き継ぐ」ということをしてくれます。
これはさながらLinuxのパイプと同じ役割です。Linuxコマンドに触れる機会が多い人にとっては、直感的でうれしい仕様です。
このように、

- 言語仕様でパターンマッチを義務付けている
- パイプライン演算子によって直前のデータ処理結果を引き継ぐ

ことから、**Elixirはデータドリブンな言語**だと言えます。


# まとめ

いかがだったでしょうか。OOPとElixirでは、そのパラダイムの違いから、大きくプログラミングスタイルが異なることがわかりました。
Elixirでは、

- ループ処理は、再帰処理とパターンマッチで実現
- 分岐処理は、ガード節とパターンマッチで実現
- パターンマッチとパイプライン演算子によって、データドリブンな記述が実現

しています。さらに**エレガントで簡潔、直感的な記述**のため、非常に読みやすい。
まだまだElixirの魅力はありますが、もし興味を持たれた方は以下のコミュニティへJOINをお願いしますｍｍ

https://fukuokaex.connpass.com/

https://pelemay.connpass.com/

では、2021/12/08の回は以上です。
ありがとうございました。