Elixir版?②「プログラミングHaskell第2版」

[fukuoka.ex／kokura.ex](https://fukuokaex.connpass.com/)のpiacereです
ご覧いただいて、ありがとうございます :bow:

[前回](https://qiita.com/piacerex/items/bb8d805cceea1f7bb1d2)に引き続き、[「プログラミングHaskell 第2版」](https://www.amazon.co.jp/exec/obidos/ASIN/4908686076/onelinebiz-22)の第2章を、Elixirで解釈しつつ、HaskellやElixirのTIPSを追記してみようと思います（本コラムの目次は、書籍の目次を流用しています）

今回は、HaskellやElixirに共通する大きな特徴である「リスト操作」の比較がメインになります
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/0bfc5205-c33f-8ab4-9243-962d7275544b.png)](https://www.amazon.co.jp/exec/obidos/ASIN/49086hobo86076/onelinebiz-22)

内容が、面白かったり、役に立ったら、「いいね」よろしくお願いします :wink:

<br>:shamrock::shamrock::shamrock::shamrock::shamrock:<b>　お知らせ：11/10（日）、ElixitとHaskellの登壇します　</b>:shamrock::shamrock::shamrock::shamrock::shamrock:

[<b>「関数型プログラミングカンファレンス2019 in Japan」</b>](https://fpc2019japan-event.peatix.com/)で、本コラムシリーズ＋αの内容で、35分の登壇をします

Haskellの神様、Edward Kmettさんや、GHC開発者のSimon Peyton Jones（SPJ）さん、Rustの有名人Pyry Kontioさんや原 将己さん、ElmのSeiya Izumiさんと、なんだか夢のような共演です

https://fpc2019japan-event.peatix.com/
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/f0b2ffea-f9ca-dfcb-c779-7e45f8f23969.png)](https://fpc2019japan-event.peatix.com/)

なお、この前日11/9（土）に開催される[「HaskellDay 2019」](https://techplay.jp/event/727059)も参加です（コチラは登壇ではありません）

https://techplay.jp/event/727059
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/f2623e40-3d50-2195-932a-8e5d0ebd6bd1.png)](https://techplay.jp/event/727059)

# 本コラムの検証環境、事前構築のコマンド

本コラムは、以下環境で検証しています（Windowsで実施していますが、Linuxやmacでも動作する想定です）

- Windows 10
- Elixir 1.9.1　 　　　　 ※<b>[最新版のインストール手順はコチラ](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99elixir%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)</b>
- [GHC 8.8.1](https://www.haskell.org/ghc/download_ghc_8_8_1.html)

# 第2章：はじめの一歩

## 2.1 GHC

GHCには、コンパイラと対話インタプリタ（REPL）の「GHCi」が含まれています

Elixirも同様に、コンパイラと対話REPLの「iex」が含まれています

## 2.2 インストールして利用開始

GHC同様、Elixirも総合情報サイトである、https://elixir-lang.org/ から無料で入手できます
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/120b21a1-b774-639c-7eaf-f1888901b1ff.png)](https://elixir-lang.org/)

各プラットフォーム毎の詳細は、[こちらの入門コラム](https://qiita.com/piacerex/items/6714e1440e3f25fb46a1)をご参考ください

インストール後、GHCは、ghciコマンドでREPLが起動できます

```
> ghci
GHCi, version 8.8.1: https://www.haskell.org/ghc/  :? for help
Prelude> 2 + 3 * 4
14
Prelude> (2 + 3) * 4
20
Prelude> sqrt (3 ^ 2 + 4 ^ 2)
5.0
```

Elixirは、iexコマンドでREPLが起動できます

ちなみにElixirには、演算子で、べき乗が無いので、:math.powで代用しています

```
> iex
iex(1)> 2 + 3 * 4
14
iex(2)> (2 + 3) * 4
20
iex(3)> :math.sqrt(:math.pow(3, 2) + :math.pow(4, 2))
5.0
```

なお、演算子の結合順位は、HaskellとElixirで違いはありません

## 2.3 プレリュード

Haskellのリスト操作は、「プレリュード」と呼ばれる組み込み関数で提供されます

```haskell:Haskell
Prelude> head [1, 2, 3, 4, 5]
1
Prelude> tail [1, 2, 3, 4, 5]
[2,3,4,5]
Prelude> [1, 2, 3, 4, 5] !! 2
3
Prelude> take 3 [1, 2, 3, 4, 5]
[1,2,3]
Prelude> drop 3 [1, 2, 3, 4, 5]
[4,5]
Prelude> length [1, 2, 3, 4, 5]
5
Prelude> sum [1, 2, 3, 4, 5]
15
Prelude> product [1, 2, 3, 4, 5]
120
Prelude> [1, 2, 3] ++ [4, 5]
[1,2,3,4,5]
Prelude> reverse [1, 2, 3, 4, 5]
[5,4,3,2,1]
```

Elixirでも、組み込み関数か、ビルトインモジュールの[Enumモジュール](https://hexdocs.pm/elixir/Enum.html)で実現できます（productのみ、Enumで用意していないため、reduceで積み上げ乗算しています）

Elixirも、Haskell同様、引数を囲むカッコは、省略可能です

```elixir:Elixir
iex> hd [1, 2, 3, 4, 5]
1
iex> tl [1, 2, 3, 4, 5]
[2, 3, 4, 5]
iex> Enum.at [1, 2, 3, 4, 5], 2
3
iex> Enum.take [1, 2, 3, 4, 5], 3
[1, 2, 3]
iex> Enum.drop [1, 2, 3, 4, 5], 3
[4, 5]
iex> length [1, 2, 3, 4, 5]
5
iex> Enum.sum [1, 2, 3, 4, 5]
15
iex> Enum.reduce [1, 2, 3, 4, 5], &(&1 * &2)
120
iex> [1, 2, 3 ] ++ [ 4, 5]
[1, 2, 3, 4, 5]
iex> Enum.reverse [1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
```

Haskellが、第一引数がリストとは限らない一方、Elixirは、全ての関数で、第一引数は、リストを取るのが最大の違いで、これはElixirのパイプにも影響します

なお、モジュール表記の無かったhd／tl／lengthは、ビルトインモジュールの[Kernelモジュール](https://hexdocs.pm/elixir/Kernel.html)に定義されており、上記の通り、Kernelは省略可能ですが、実際は、以下のようなコードが実行されています

```elixir:Elixir
iex> Kernel.hd [1, 2, 3, 4, 5]
1
iex> Kernel.tl [1, 2, 3, 4, 5]
[ 2, 3, 4, 5 ]
iex> Kernel.length [1, 2, 3, 4, 5]
5
```

「++」によるリスト連結だけで無く、「--」によるリスト削除も可能です（各要素毎にマッチさせるため、順序が異なっていても削除できますし、重複する値は、1つずつマッチして削除します）

```elixir:Elixir
iex> [1, 2, 3] -- [2]
[1, 3]
iex> [1, 2, 3] -- [2, 3]
[1]
iex> [1, 2, 3] -- [3, 2]
[1]
iex> [1, 2, 3] -- [3, 2, 1]
[]
iex> [1, 2, 2, 3] -- [2]
[1, 2, 3]
iex> [1, 2, 2, 3] -- [2, 2]
[1, 3]
```

###  付録B「B.8 リスト」

書籍では、使用頻度の高い定義を付録Bに収録（リスト操作以外も含む）していますが、全部の対比を書くと大変なので、「B.8 リスト」に限定して、HaskellとElixirのコード対比を下記にまとめます（なお、上記紹介済の重複は省略します）

```haskell:Haskell
Prelude> last [1, 2, 3, 4, 5]
5
Prelude> filter even [1, 2, 3, 4, 5]
[2,4]
Prelude> takeWhile (<= 3) [1, 2, 3, 4, 5]
[1,2,3]
Prelude> init [1, 2, 3, 4, 5]
[1,2,3,4]
Prelude> dropWhile (<= 3) [1, 2, 3, 4, 5]
[4,5]
Prelude> splitAt 3 [1, 2, 3, 4, 5]
([1,2,3],[4,5])
Prelude> repeat [1, 2]
[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],[1,2],Interrupted.
Prelude> replicate 3 [1, 2]
[[1,2],[1,2],[1,2]]
Prelude> iterate (+ 1) 1
[1,2,3,4,5,6,7,8,9,10,11,Interrupted.
Prelude> zip [1, 2, 3] [ 4, 5, 6]
[(1,4),(2,5),(3,6)]
Prelude> map (+ 1) [1, 2, 3, 4, 5]
[2,3,4,5,6]
```

上記Haskellと同等のElixirコードは、以下です

Elixirで無限リストを扱うには、[Streamモジュール](https://hexdocs.pm/elixir/Stream.html)を使います

repeat／replicate／iterateは、全て、Stream.iterate（とEnum.take）でカバーできます

```elixir:Elixir
iex> List.last [1, 2, 3, 4, 5]
5
iex> import Integer
iex> Enum.filter [1, 2, 3, 4, 5], &is_even(&1)
[2, 4]
iex> Enum.take_while [1, 2, 3, 4, 5], & &1 <= 3
[1, 2, 3]
iex> Enum.drop [1, 2, 3, 4, 5], -1
[1, 2, 3, 4]
iex> Enum.drop_while [1, 2, 3, 4, 5], & &1 <= 3
[4, 5]
iex> Enum.split [1, 2, 3, 4, 5], 3
{[1, 2, 3], [4, 5]}
iex> Stream.iterate([1, 2], & &1) |> Enum.take(11)
[[1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2]]
iex> Stream.iterate([1, 2], & &1) |> Enum.take(3)
[[1, 2], [1, 2], [1, 2]]
iex> Stream.iterate(1, & &1 + 1) |> Enum.take(11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
iex> Enum.zip [1, 2, 3], [4, 5, 6]
[{1, 4}, {2, 5}, {3, 6}]
iex> Enum.map [1, 2, 3, 4, 5], &(&1 + 1)
[2, 3, 4, 5, 6]
```

Enum.take_whileでできることは、Enum.filterで再現可能です

```elixir:Elixir
iex> Enum.take_while [1, 2, 3, 4, 5], & &1 <= 3
[1, 2, 3]
iex> Enum.filter [1, 2, 3, 4, 5], & &1 <= 3
[1, 2, 3]
```

Enum.dropで末尾を削除（-1で指定）の代わりに、List.delete_atを使うことも可能です

```elixir:Elixir
iex> Enum.drop [1, 2, 3, 4, 5], -1
[1, 2, 3, 4]
iex> List.delete_at [1, 2, 3, 4, 5], -1
[1, 2, 3, 4]
```

Elixirの無限リストは、Haskellのrepeatやiterateのように、その場で実体化はせず、関数のままでいます

実体化するには、Enum.takeやEnum.to_list等を使います

```elixir:Elixir
iex> Stream.iterate(1, & &1 +  )
#Function<65.35756501/2 in Stream.unfold/2>
iex> Stream.iterate(1, & &1 + 1) |> Enum.take(10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
iex> Stream.iterate(1, & &1 + 1) |> Enum.to_list
（この場合、無限に続くため、表示がされない）
```

ミリ秒単位で0オリジンの生成をしたいシチュエーションでは、Stream.intervalを使います（タイマー処理を作るときに便利です）

```elixir:Elixir
iex> Stream.interval(1000) |> Enum.take(10)
（1秒 x 10個分、待つ）
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

この他にも、Stream.transformを使うと、条件判定付きの高度な無限リスト処理ができますが、これは[「Elixir無限ストリームでズンドコキヨシ」](https://qiita.com/piacerex/items/4670f59d9f6d601fe2a8)というコラムで紹介しています

## 2.4 関数適用

数学の下記表記は、前半が関数適用、後半が乗算を表します

```math
f(a, b) + c d
```

Haskellでは、関数適用に空白文字を使い、乗算は演算子*を使うため、こう書きます

```haskell:Haskell
Prelude> a = 1
Prelude> b = 2
Prelude> c = 3
Prelude> d = 4
Prelude> f a b = a + b
Prelude> f a b + c*d
15
```

Elixirでも、これはほぼ同様ですが、引数の間にカンマが必要なのが差になります

```elixir:Elixir
iex> a = 1
iex> b = 2
iex> c = 3
iex> d = 4
iex> defmodule Sample, do: def f a, b, do: a + b
iex> import Sample
iex> f a, b + c*d
15
```

なお、Elixirの無名関数で書くと、関数名の後の「.」とカッコが省略できないので、以下のような複雑な記載になります

```elixir:Elixir
iex> f = fn a, b -> a + b end
#Function<12.99386804/2 in :erl_eval.expr/5>
iex> f.(a, b) + c*d
15
```

数学とHaskell、Elixirの記載の違いは、以下のようになります

Haskellは、引数の間と、関数適用を、どちらも空白文字で区別しないため、fの前にgを関数適用する場合、「f (g x)」と記載しなければならない一方、Elixirは、引数の間は「,」が必須であり、空白を混同することは無い仕様のため、「f g x」と記載可能です

書籍では、上記説明となっていますが、実際は、カッコとカンマの組み合わせで、数学式に近い定義が、HaskellもElixirも可能です

<nobr>

| 数学       |Haskell書籍|Elixir書籍相当|Haskell数学類似 |Elixir数学類似  |
|------------|-----------|-------------|----------------|---------------|
| f(x)       | f x       | f x         | f(x)           | f(x)          |
| f(x, y)    | f x y     | f x, y      | f(x, y)        | f(x, y)       |
| f(g(x))    | f (g x)   | f g x       | f(g(x))        | f(g(x))       |
| f(x, g(y)) | f x (g y) | f x, g y    | f(x, (g(y))    | f(x, (g(y))   |
| f(x)g(y)   | f x * g y | f x * g y   | f(x) * g(y)    | f(x) * g(y)   |

</nobr>

ちなみにHaskellでは、カッコ有とカッコ無の関数は、別物として扱われるので、注意してください

```haskell:Haskell
Prelude> f a b = a + b
Prelude> f 2 3
5
Prelude> f(2, 3)
<interactive>:3:1: error:
    ? Non type-variable argument in the constraint: Num (a, b)
      (Use FlexibleContexts to permit this)
    ? When checking the inferred type
        it :: forall a b. (Num a, Num b, Num (a, b)) => (a, b) -> (a, b)
```

```haskell:Haskell
Prelude> f(2, 3) = a + b
Prelude> f 2 3
<interactive>:3:1: error:
    ? Non type-variable argument in the constraint: Num (t1 -> t2)
      (Use FlexibleContexts to permit this)
    ? When checking the inferred type
        it :: forall t1 t2.
              (Num t1, Num (t1 -> t2), Num (t1 -> t2, t1 -> t2)) =>
              t2
Prelude> f(2, 3)
5
```

また、「カッコはあるけど、カンマは無い」という表記は、エラーになります

```haskell:Haskell
Prelude> f(a b) = a / b
    parse error (possibly incorrect indentation or mismatched brackets)
```

## 2.5 Haskellプログラム

ファイルによる関数定義と、GHCiからの呼出、GHCiコマンド、命名規則、レイアウト規則、タブ文字、コメントについての記載ありますが、9章や10章でファイルを使ったサンプルがあるため、ここでは割愛します

# オマケ：Elixirのパイプ演算子

Elixirは、パイプ演算子「|>」を使うことで、第一引数を前の結果から渡すことができるため、今回のサンプルは、以下のように書き換えできます

「データから始め、データにどんな操作を施すか」が、分かりやすく記載できます

```elixir:Elixir
iex> [1, 2, 3, 4, 5] |> hd
1
iex> [1, 2, 3, 4, 5] |> tl
[2, 3, 4, 5]
iex> [1, 2, 3, 4, 5] |> Enum.at 2
3
iex> [1, 2, 3, 4, 5] |> Enum.take 3
[1, 2, 3]
iex> [1, 2, 3, 4, 5] |> Enum.drop 3
[4, 5]
iex> [1, 2, 3, 4, 5] |> length
5
iex> [1, 2, 3, 4, 5] |> Enum.sum
15
iex> [1, 2, 3, 4, 5] |> Enum.reduce &(&1 * &2)
120
iex> [1, 2, 3, 4, 5] |> Enum.reverse
[5, 4, 3, 2, 1]
```

```elixir:Elixir
iex> [1, 2, 3, 4, 5 ] |> List.last
5
iex> import Integer
iex> [1, 2, 3, 4, 5] |> Enum.filter &is_even(&1)
[2, 4]
iex> [1, 2, 3, 4, 5] |> Enum.take_while & &1 <= 3
[1, 2, 3]
iex> [1, 2, 3, 4, 5] |> Enum.drop -1
[1, 2, 3, 4]
iex> [1, 2, 3, 4, 5] |> Enum.drop_while & &1 <= 3
[4, 5]
iex> [1, 2, 3, 4, 5] |> Enum.split 3
{[1, 2, 3], [4, 5]}
iex> [1, 2, 3, 4, 5] |> Enum.map &(&1 + 1)
[2, 3, 4, 5, 6]
```

# 終わり

HaskellとElixirのリスト操作は、無限リストも含め、ほぼ同じことが実現できました（第一引数が必ずリストを取るElixirと、そうでないHaskellの違いはありますが）

また、関数適用については、引数の間に「,」が必須なElixirと、そうで無く空白文字で代用できるHaskellでは、その後のカッコの扱いが変わってくることも分かりました

次回は、書籍の第3章「型と型クラス」を扱います

# p.s.「いいね」よろしくお願いします

ページ左上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png)や![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください！:tada: