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

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

先日9/7（土）に開催した、「ElixirConf JP 2019」の準備・運営にかかりきりで、約3ヶ月弱ぶりの投稿です（ElixirConf JP 2019のレビューは追ってまとめます）

さて最近、Haskellerの方々と、ご一緒することが、ちょくちょくあります

ご存知の方は、ご存知ですが、元々、私の関数型初体験は、Haskellでした … それから[幾つものプログラミング言語を経由](https://twitter.com/piacere_ex/status/1174188853227401218)して、現在、Elixirに落ち着いており、恐らく、Elixir以外の言語を選ぶことは、よほどのことが無い限り、無いと思います

とはいえ、言語ヲタとして、他言語に学ぶことは多くあり、今回は、先月出たばかりの[「プログラミングHaskell 第2版」](https://www.amazon.co.jp/exec/obidos/ASIN/4908686076/onelinebiz-22)の第1章を、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)

余談ですが、私は、[「Haskellによる並列・並行プログラミング」](https://www.amazon.co.jp/exec/obidos/ASIN/4873116899/onelinebiz-22)という本も好きで、Elixirでのマルチコアプログラミングは、こういった部分からも刺激を受けています
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/291628cd-9db2-c570-1e30-6e766dd520c7.png)](https://www.amazon.co.jp/exec/obidos/ASIN/4873116899/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.8.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)

# 第1賞：導入

## 1.1 関数

本のP3、「Haskellにおける関数は、1つ以上の引数を取って1つの結果を返す、変換器です」と、しょっぱなから、[「プログラミングElixir」](https://www.amazon.co.jp/exec/obidos/ASIN/4274219151/onelinebiz-22)と全く同じ趣旨のプログラミングに対する主張です
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/06c128b0-ce7f-8c72-59b7-803e80e67e6c.png)](https://www.amazon.co.jp/exec/obidos/ASIN/4274219151/onelinebiz-22)

double関数の例は、簡単過ぎるので割愛

## 1.2 関数プログラミング

Haskellで、「[..]」で数値リストを生成し、「sum」でリストの総和を出すサンプルが、本のP4～6に記載されています

これを、GHCのREPLである「GHCi」を使って実行してみます

```haskell:Haskell
Prelude> sum [1..5]
15
```

このように、Haskellのリスト操作系関数は、「プレリュード」と呼ばれる組込関数で定義されています

Elixirでも、``..`` で値の範囲を示すもの（リストでは無くレンジと言います）が生成でき、それを「Enum.to_list」でリストに変換し、「Enum.sum」でリストの総和を出せます

ElixirのREPLである「iex」を使って実行してみます

```elixir:Elixir
iex> Enum.sum Enum.to_list 1..5
15
```

Haskellのリスト操作系関数に相当するものが、Elixirでは、Enumモジュールで実装されています

なおEnum.sumは、リストだけで無く、レンジも引数に取れるので、以下のように書けます

```elixir:Elixir
iex> Enum.sum 1..5
15
```

これは、Elixirにおける「多相性」で、defprotocol／defimplによって実現しています

## 1.3 Haskellの特徴

本に書かれている特徴を、Elixirで照らし合わせると、以下の通りです

- 簡潔なプログラム（第2章と第4章） … Elixirでも同様の効果があり、他言語よりも、2～10倍短く書けます
- 強力な型システム（第3章と第8章） … Elixirは動的型付のため、Haskellほど型システムが厳密では無いですが、コンパイル時型チェックが走り、他の動的型付言語とは一線を画し、更にDyalyzerによる静的チェックもできれば、多相性や多重定義もdefprotocol／defimplでナチュラルに実現できます
- リスト内包表記（第5章） … 同様のメカニズムがElixirにもあります
- 再帰関数（第6章） … 再帰およびパターンマッチとガードは、Elixirでも利用可能です（が、私の周りのElixir勢およびHaskell勢は再帰を推奨していません、詳細は別途）
- 高階関数（第7章） … Elixirでも、高階関数と、それを利用したDSL構築はもちろんできます
- 作用を持つ関数（第10章と第12章） … Haskellは、関数の純粋性を犠牲にしないメカニズムが多くあり、Elixirでも類似した実装は可能ですが、よりシンプルなメカニズムも提供されています
- 汎用的な関数（第12章と第14章） … 関手、アプリカティブ、モナド、Foldable、Traversableは、Elixirでも実現できます（が、それが有用かは議論の余地ありで、上述の「よりシンプルなメカニズム」で代行するケースが大半です）
- 遅延評価（第15章） … Elixirでは、データストリームに限定された、Streamモジュールによる遅延評価があります
- 等式推論（第16章と第17章） … ここはElixirがカバーできていない領域です（が私達の研究ではカバー範囲になります）

## 1.4 歴史的背景

本のP8～9には、Haskellの前身となった1930年代のラムダ計算や1950年代のLisp誕生、1987年のHaskell誕生といった歴史がリストされていますが、Elixirは、創始者であるJosé Valimが、Haskellをはじめ、Clojure、OCaml等の様々な関数型言語を研究し、Erlang/OTPに出会った結果、作られた言語なので、ある意味、Haskellの歴史の先に出た派生であるといっても過言ではありません

ですので、Haskellの歴史の出来事の中から、Elixirで採用されなかった対象を見ていくと、もう一段、Elixirのことを楽しむことができます

## 1.5 Haskellの妙味

### 1.5.1 数値を足し合わせる

再帰によるリスト総和が、本に書かれています（なお、sumだとプレリュードのsumと齟齬りそうなので、sum2と変えています）

```haskell:Haskell
Prelude> sum2 [] = 0; sum2 (n:ns) = n + sum2 ns
```

リストが無くなるまで下の関数が繰り返され、リストが空になると、上の関数が呼ばれ、再帰が終わります

なお、GHCiで複数行の関数定義をすると、エラーになります（ファイルに複数行で定義し、GHCi起動時に指定すれば、エラーにはなりません）

```haskell:Haskell
Prelude> sum2 [] = 0
Prelude> sum2 (n:ns) = n + sum2 ns
Prelude> sum2 [1, 2, 3]
*** Exception: <interactive>:41:1-25: Non-exhaustive patterns in function sum4
```

GHCi内で、複数行の関数定義したい場合は、``:{`` で始め、``:}`` で終えてください

```haskell:Haskell
Prelude> :{
Prelude| sum2 [] = 0
Prelude| sum2 (n:ns) = n + sum2 ns
Prelude| :}
Prelude> sum2 [1, 2, 3]
6
```

Elixirでも、同等の再帰関数が作れますが、無名関数定義内で、自身を呼び出すことができないため、モジュール内で定義する「名前付き関数」を使い、関数呼出時はモジュール名指定します（以降はコチラで記述します）

```elixir:Elixir
iex> defmodule Sample do
...>   def sum2([]),       do: 0
...>   def sum2([n | ns]), do: n + sum2 ns
...> end
iex> Sample.sum2 [1, 2, 3]
6
```

importを使うと、モジュール名指定が省略でき、Haskellと同等の呼出記述にできます（以降の説明では省略します）

```elixir:Elixir
iex> import Sample
iex> sum2 [1, 2, 3]
6
```

このように、モジュールを挟んではいますが、Haskellと似たような書き方ができることがお分かりかと思います

その後、本では型定義について書かれていますが、これは、sumにおけるaは、Num型aのリストを、Num型aに変換する関数であることを示しています

```haskell:Haskell
sum2 :: Num a => [a] -> a
```

Elixirでも、ErlangのDyalizerという静的型チェックツール（Elixirだと[Dyalixir](https://github.com/jeremyjh/dialyxir)で使用可能）に食わせられる型チェック構文があり、number型のリストをnumber型に変換する関数であることを示しています

```elixir:Elixir
@spec sum2(list(number)) :: integer
```

なお、「::」の構文／意味が、Haskellのソレとはだいぶ異なるので、ご注意ください

ちなみに、この型定義を明確にしていなくても、Elixirは、numberに対してのみ有効な ``+`` 演算子が効かない型が来た場合、ランタイムエラーで検出します

```elixir:Elixir
iex> Sample.sum2 [1, 2, 3]
6
iex> Sample.sum2 [1, "a", 3]
** (ArithmeticError) bad argument in arithmetic expression
```

### 余談：Elixirの無名関数で定義すると、どうなるか？

Elixirの無名関数定義内では、自身を呼び出すことができないため、おのずと高階関数で書くことになります

```elixir:Elixir
iex> sum2 = fn
...>   [], _f -> 0
...>   [n|ns], f -> n + f.(ns, f)
...> end
iex> sum2.([3, 2, 1], sum2)
```

下記のように、無名関数内で関数を定義し、高階関数の呼出をラップする書き方もできますが、ちょっと複雑です

```elixir:Elixir
iex> sum2 = fn
...>   n -> func = fn
...>     [], _f -> 0
...>     [n|ns], f -> n + f.(ns, f)
...>   end
...>   func.(n, func)
...> end
iex> sum2.([3, 2, 1])
6
```

### 1.5.2 数値を整列する

再帰によるクイックソートが、本に書かれています

```haskell:Haskell
Prelude> :{
Prelude| qsort []       = []
Prelude| qsort ( x:xs ) = qsort smaller ++ [x] ++ qsort larger 
Prelude|                  where 
Prelude|                    smaller = [a | a <- xs, a <= x]
Prelude|                    larger  = [b | b <- xs, b >  x]
Prelude| :}
Prelude> qsort [5, 1, 3]
[1,3,5]
```

リストが無くなるまで下の関数の大小比較と連結が繰り返され、リストが空になると、上の関数が呼ばれ、再帰が終わります

Elixirでも、同等の再帰関数が作れますが、Haskellのようなwhere句は無いため、smallerやlargerを、for句で記載することになります

```elixir:Elixir
iex> defmodule Sample do
...>   def qsort([]),         do: []
...>   def qsort([x|xs]), do: qsort(for a <- xs, a <= x, do: a) ++ [x] ++ qsort(for b <- xs, b > x,  do: b)
...> end
iex> qsort [5, 1, 3]
[1, 3, 5]
```

比較的、似たような構文で記述できることがお分かりでしょう

無名関数で、スマートに書き直すこともできます

```elixir:Elixir
iex> defmodule Sample do
...>   def qsort([]),    do: []
...>   def qsort([x|xs]) do
...>     smaller = fn x, xs -> for a <- xs, a <= x, do: a end
...>     larger  = fn x, xs -> for b <- xs, b > x,  do: b end 
...>     qsort(smaller.(x, xs)) ++ [x] ++ qsort( larger.(x, xs))
...>   end
...> end
iex> qsort [5, 1, 3]
[1, 3, 5]
```

もしくは、下記のように、``defp`` によるモジュール内関数で書き直すこともできます

```elixir:Elixir
iex> defmodule Sample do
...>   def  qsort([]),      do: []
...>   def  qsort([x|xs]),  do: qsort(smaller(x, xs)) ++ [x] ++ qsort(larger(x, xs))
...>   defp smaller(x, xs), do: for a <- xs, a <= x, do: a
...>   defp larger( x, xs), do: for b <- xs, b > x,  do: b
...> end
iex> qsort [5, 1, 3]
[1, 3, 5]
```

for句では無く、Enumで書くこともできます

```elixir:Elixir
iex> defmodule Sample do
...>   def  qsort([]),      do: []
...>   def  qsort([x|xs]),  do: qsort(smaller(x, xs)) ++ [x] ++ qsort(larger(x, xs))
...>   defp smaller(x, xs), do: Enum.filter xs, & &1 <= x
...>   defp larger( x, xs), do: Enum.filter xs, & &1 >  x
...> end
```

本ではこの後、Haskellが、「数字リストだけで無く、文字列のような、順序を持つ型であれば、何であれ適用可能」という記述がありますが、これはElixirも同様です

```elixir:Elixir
iex> qsort [3, 2, 1]
[1, 2, 3]
iex> qsort 'cba'
'abc'
```

なお、Elixirでは、``'`` で括った文字列は、charlistとして順序を持ったリストとして扱われますが、``"`` で括った文字列は、リストとして扱われないため、上記qsortが適用できません（が、実用上は、数値と文字列を同一視する処理はあまり書かず、むしろ混同しなくて良い仕様と個人的に捉えています）

```elixir:Elixir
iex> is_list('abc')
true
iex> is_list("abc")
false
iex> qsort "cba"
** (FunctionClauseError) no function clause matching in Sample.qsort/1
```

### 1.5.3 アクションを逐次に実行する

関数のリストを受け取って、順次実行し、その戻りをリストで返す関数が、本に記載されています

```haskell:Haskell
Prelude> :{
Prelude| seqn []         = return []
Prelude| seqn (act:acts) = do x <- act
Prelude|                      xs <- seqn acts
Prelude|                      return (x:xs)
Prelude> :}
```

関数に、入力された1文字を返すgetCharを3つ渡すと、3回、入力が促され、3文字を連結したものが返されます（Windowsだと挙動がこうならず、文字入力後に打鍵するEnterも1文字と数えてしまいますが…）

```haskell:Haskell
Prelude> seqn [getChar, getChar, getChar]
a
b
c
"abc"
```

これをElixirで表現すると、以下のようになります

なお、getCharに相当する引数無入力が無いため、IO.getnを想定した書き方になっています（IO.getnも、WindowsだとEnterをやはり1文字と数えてしまうため、ここでは、2文字を受け取る呼び出し方で対処しています）

```elixir:Elixir
iex> defmodule Sample do
...>   def seqn(acts), do: for act <- acts, do: act.("", 2)
...> end
```

関数に、入力された2文字を返す&IO.getn/2を3つ渡すと、3回、入力が促され、2文字x3つを連結したリストが返されます

```elixir:Elixir
iex> seqn [&IO.getn/2, &IO.getn/2, &IO.getn/2]
a
b
c
["a\n", "b\n", "c\n"]
```

上記Haskellと同じような記載にしたいなら、getChar相当を下記のように定義します

```elixir:Elixir
iex> defmodule Sample do
...>   def seqn(acts), do: for act <- acts, do: act.()
...> 
...>   def getchar(), do: IO.getn("", 2) |> String.first |> String.to_charlist |> List.first
...> end
```

そうすると、Haskellとほぼ同じ記載／挙動が行われます

```elixir:Elixir:Elixir
iex> seqn [&getchar/0, &getchar/0, &getchar/0]
a
b
c
'abc'
```

本では、この続きとして、この関数の型と汎用性について解説がありますが、Elixirは、最初から動的型付言語なので汎用的です

また、この関数は、入出力といったアクションに特化している訳では無く、もっと広い作用を扱え、本にも例があるような、「変数の値を変えること」「計算が失敗すること」「ログファイルを書き出すこと」等に利用できる点も、Haskellと同様です（IOやモナドの解説については、後述する章の中で扱うとしましょう）

このような感じで、Haskellの妙味は、Elixirでも味わうことができます

# 終わり

ここまでは、とても簡単な例ではありますが、思った以上に、HaskellとElixirの類似性が高いことがお分かりかと思います

これからElixirを学ぶ上で、Haskellを学ぶべきかどうかは、順序が逆の私からは、よく分かりませんが、同じようなマインドが根底にあることを知ったり、どういった部分で相違点があるのかを把握することで、Elixirの各機能が、なぜ提供されているかについて、より深い理解をすることはできると考えます

[次回は、書籍の第2章を扱います](https://qiita.com/piacerex/items/e9215d12018288c7595b)

# 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: