なぜこれを書いたか
誰かHaskellしか関数型を知らない僕にElixirの特徴(特にHaskellとどう違うか)を教えて欲しいです#elixir
— Tatsuki@Vim (@ttk_vim) 2017年5月22日
テクノさん案件だ
— Haskell (@aiya_000) 2017年5月22日
オッス!オラ、テクノ
— techno_tanoC📕 (@techno_tanoC) 2017年5月22日
案件が回ってきたので書きます。時間がないので雑な説明になります。雑すぎて公開するか悩みましたが、せっかくなので公開しておきます。先に謝っておきますごめんなさい。
自己紹介
趣味でプログラミングをやっている(ruby, railsを2年ほど書いていたのは過去の話)人です。
現在の本業は薬学部生です。平日は実習で潰れるのでここ最近全くプログラミングをしていません。
関数型プログラミングが大好きで、時間がある時期には大阪でShinosaka.hsというコミュニティで勉強会を開いています。
はじめに
一言に関数型言語と言っても多くの言語があります。「関数型言語の最右翼」とも言われている(要出典)Haskellから、ユーザーはエイリアンの見た目をしているというLisp系言語まで・・・なんかこう書くと変態しかいなさそうですね関数型プログラマって。
関数型言語が何であるのかはここでは深くは語りません。多分「ラムダ計算をベースにしている」とか「イミュータブルを基本にしている」とかそんな感じです。というか僕もよく分かっていないので誰かマサカリ投げてください。
ともかく、HaskellもElixirも一般的には関数型言語ということになっています。ナカーマ。
そんな仲間な言語であるHaskellとElixirですが、趣きは全く違います。今回はHaskellとElixirを比較したり、Elixirの特徴を挙げてみたりします。
思想の違い
ガッチガチの静的型付け言語であるHaskellに対して、Elixirは動的型付け言語です。
性質やデータを型として表現することでコンパイル時に多くのバグを発見するHaskellに対して、Elixir(Erlang)は「プログラムには必ずバグが出るものだ。全てのバグや実行時例外を把握してコードを書くことはほぼ不可能なので、エラーが出た時にどうするかを考えよう」という思想です。
具体的には処理をプロセスという実行単位に分割し、それぞれのプロセスが自分の処理だけに集中し、互いにメッセージを投げ合います。そしてどこかのプロセスがエラーで落ちるとスーパバイザという機構によって正常系に復帰します。
プロセスローカルな処理は普通にガツガツ関数で処理を書けばいいので、Haskellを書ける人ならドキュメントを見れば普通に書けると思います。
非同期なメッセージパッシングが関わる処理やプロセスデザインが肝であり鬼門です(多分)(僕もよく分かってない)。
パイプライン演算子
パイプライン演算子で処理を繋げるのも楽しいです。
# 特に意味のないコード1..1000# 1から1000を|>Enum.map(fn i -> i *2end) # それぞれ2倍して|>Enum.sum # 合計して|>Integer.to_string # 文字列にして|>String.length # 文字列の長さを出す
型システム
ElixirはHaskellほど型にこだわらないちょっと緩い感じの言語です(強い型付けではある)。一応defstruct
を使えば型っぽいものは作れますが、その実はただのMap型です。
defmoduleDogdodefstructname:"", age:0end %Dog{name:"Pochi", age:10} # %{}はMapリテラルで、%Dog{}は%{__struct__: Dog}のシンタックスシュガー
みたいにStructが作れます。ElixirにはHaskellの直和型にあたるものはありません。動的型付けなので「パターンマッチの時に適宜チェックしてやれば良い」ぐらいの感覚です。
animal = %Dog{name:"Pochi", age:5} case animal do %Dog{name: name} ->"#{name}: Bowbow," %Catl{name: name} ->"#{name}: Mew."end
型をゴリゴリ設計するHaskellerからすると物足りないかもしれませんが、データを単純な形で表現することによって難しいことを考えずにシャカシャカ書いていける感じが僕は好きです。
軽量プロセス
Elixir(Erlang)のプロセスは非常に軽量です。作られたばかりのプロセスのサイズは309 words(≒ 2.4kb)で、プロセスの起動にかかる時間はマイクロ秒レベルです。
そのためにElixirではオブジェクト指向言語でインスタンスを作るのと同じくらい軽いノリでプロセスを作ります。実際にプロセスを作るコードを書いてみます。
# 1から10を出力する1..10|>Enum.each(fn i -># Enum.eachで1から10のそれぞれに、spawn(fn-># spawnでプロセスを立てて、IO.puts i # 数字を出力するend) end)
Haskellもスレッドを簡単に立てることができますが何万スレッドも立てるのはなかなか難しいでしょう。
2017/05/24 GHCの場合スレッドは軽量スレッドのため、数万スレッドなら普通に作れるそうです。ご指摘感謝します。 また、単純な実行速度はHaskellの方が上なので、立てることができるスレッドやプロセス数がそのままパフォーマンスの高さになるわけではないことも付記しておきます。
ghcで数万の軽量スレッドなら簡単に作れますね / “Haskellerから見たElixir - てくのろじーたのしー” https://t.co/4D2pKJlXJk
— ruicc (@ruicc) 2017年5月24日
一方Elixirであれば100万プロセスぐらいであれば普通に動きます。5000万プロセスとかイケるみたいです。
Elixir(Erlang)が活きるのは高い稼働率や高い並列性(WebSocketを使うサービスやゲームサーバ)が求められるところだと思います。
アクターモデル
ざっくりと言うとオブジェクト指向のメッセージパッシングを全部非同期にしたような感じです。プロセス1つひとつをアクターと考え、互いにメッセージを非同期に投げつけ合いながら独立して動きます。
状態の持たせ方
HaskellではStateやSTやMVarやTVarやIORefなどを使って状態を持たせますが、Elixirではプロセスに持たせます。プロセス内で再帰することで状態を変えながら外部とメッセージパッシングする感じです。
マクロ
衛生的で手軽なマクロがあります。ASTを受け取ってASTを返すものです。ggればunlessの実装とかたくさん出てくると思うのでここでは説明しません(放棄
まとめ
Elixirはですね
基本的には関数型の、Erlangという言語の上で動いていまして、
若干ゃ型が、動的なところなので、
そういったところで扱いやすいように、
Elixir、あのパターンマッチで…
あとマクロも!あるので、柔軟に…構文を…作れるように、
アクターモデルぅ…ですかねぇ…(謎)
プロセスをスッと作るっことができるっ言語でして
けっこう並列処理が好きなので、
軽々と…100万プロセス5000万プロセスは余裕で動かしてくれますね(信頼)