FledglingRobin’s diary

Github: https://github.com/FledglingRobin

Elixirに手を出してみる

関数型言語Elixirに手を出してみた。エリクサーって名前がとても可愛い 。

Elixirとは何か

まずは公式サイトを見てみる。http://elixir-lang.org/

Elixir is a dynamic, functional language designed for building scalable and maintainable applications.

スケールできることとメンテナンスのしやすさに重きをおいた関数型言語らしい。

To learn more about Elixir, check our getting started guide. Or keep reading to get an overview of the platform, language and tools.

素直に従って、getting started guideを読んで見る。

Elixirのインストー

Introduction - Elixir を見てみる。

If you still haven’t installed Elixir, run to our installation page. Once you are done, you can run elixir –version to get the current Elixir version.

すると、installについては別ページで解説しているとのことなのでそちらを見に行く。

Installing Elixir - Elixirを見に行くと環境毎に手段が乗っている。今回はHomebrewから入れることにする。

$ brew install elixir

とくに問題なくinstallできた。

$ elixir --version
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.4.4

インタラクティブモード

最初に紹介されていたのがインタラクティブモードだった。

For now, let’s start by running iex (or iex.bat if you are on Windows) which stands for Interactive Elixir.

というわけで、素直にiexを起動してみる。

$ iex
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.4.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

例を参考に少し触ってみる。

$ iex
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.4.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
nil
iex(2)> 1+1
2
iex(3)> "hello" <> " " <> "world"
"hello world"
iex(4)> 

ちなみにインタラクティブモードはCtrl+Cの2度押しで抜けられる。

プログラムをファイルに書く

この辺の流れはお決まりだなあと思いつつ、サンプルを実行してみる。

$ cat hello.exs 
IO.puts "Hello world from Elixir"
$ elixir hello.exs 
Hello world from Elixir

ここから、getting started guide以外の資料を見ていく。 Basic types - Elixirには基本型 について記載がある。

Basic arithmetic Identifying functions Booleans Atoms Strings Anonymous functions (Linked) Lists Tuples Lists or tuples?

連結リストとタプルがちょっと気になる。

Anonymous functions

Anonymous functions can be created inline and are delimited by the keywords fn and end:

インラインの関数定義を実現するキーワードとして、fnendが使われているらしい。

iex(1)> add = fn a,b -> a + b end
#Function<12.118419387/2 in :erl_eval.expr/5>
iex(2)> add.(1, 2)
3
iex(3)> add.(3, 4)
7
iex(4)> add.(1, -1)
0

Functions are “first class citizens” in Elixir meaning they can be passed as arguments to other functions in the same way as integers and strings.

Elixirの関数は第一級関数だと補足がされている。関数型言語と聞いていたので、きっとそうくると思ってた。

Keep in mind a variable assigned inside a function does not affect its surrounding environment

関数内の変数は、関数外に影響を及ぼさない。下記の例だと、x = 123としたあとに、関数内でx = 256としているが、関数外のxは変わらず123を示している。

iex(9)> x = 123
123
iex(10)> (fn -> x = 456 end).()
456
iex(11)> x
123

#連結リスト

[]でリストを定義。++で連結。--で要素の削除。直感的。

[1, 2, 3, 4, 5]
iex(23)> list
[1, 2, 3, 4, 5]
iex(24)> list = list ++ [6, 7] 
[1, 2, 3, 4, 5, 6, 7]
iex(25)> list
[1, 2, 3, 4, 5, 6, 7]
iex(26)> list = list -- [1, 2]
[3, 4, 5, 6, 7]
iex(27)> list
[3, 4, 5, 6, 7]

また先頭要素の取得にhdが、先頭以外要素の取得にtlが用意されている。

iex(33)> hd(list)
3
iex(34)> tl(list)
[4, 5, 6, 7]

関数型言語であるStanderd MLにも同様の文法があったことを思い出す。書き方がなんとなく想像ついてきた。

ASCIIコードのリスト

iex(38)> [104, 101, 108, 108, 111]
'hello'

Elixirでは数字の羅列が文字列になることがある。印字可能なASCIIコードが渡されると、文字列として判断するのだそうだ。面白い。

iex(39)> i 'hello'
Term
  'hello'
Data type
  List
Description
  This is a list of integers that is printed as a sequence of characters
  delimited by single quotes because all the integers in it represent valid
  ASCII characters. Conventionally, such lists of integers are referred to as
  "charlists" (more precisely, a charlist is a list of Unicode codepoints,
  and ASCII is a subset of Unicode).
Raw representation
  [104, 101, 108, 108, 111]
Reference modules
  List
Implemented protocols
  IEx.Info, Collectable, Enumerable, Inspect, List.Chars, String.Chars

Keep in mind single-quoted and double-quoted representations are not equivalent in Elixir as they are represented by different types:

これも気をつけなきゃと思った。シングルクォートで囲った場合と、ダブルクォートで囲った場合、異なる型として判定される。

iex(6)> 'hello' == "hello"
false

つまり、下記の通りシングルクォートで囲むとListになるが、ダブルクォートの場合はStringで扱われるらしい。

iex(7)> i 'hello' 
Term
  'hello'
Data type
  List
Description
  This is a list of integers that is printed as a sequence of characters
  delimited by single quotes because all the integers in it represent valid
  ASCII characters. Conventionally, such lists of integers are referred to as
  "charlists" (more precisely, a charlist is a list of Unicode codepoints,
  and ASCII is a subset of Unicode).
Raw representation
  [104, 101, 108, 108, 111]
Reference modules
  List
Implemented protocols
  IEx.Info, Collectable, Enumerable, Inspect, List.Chars, String.Chars
iex(8)> i "hell"
Term
  "hell"
Data type
  BitString
Byte size
  4
Description
  This is a string: a UTF-8 encoded binary. It's printed surrounded by
  "double quotes" because all UTF-8 encoded codepoints in it are printable.
Raw representation
  <<104, 101, 108, 108>>
Reference modules
  String, :binary
Implemented protocols
  IEx.Info, Collectable, Inspect, List.Chars, String.Chars

Tuple

Listの他に、Tuple型が提供されている。

iex(16)> tuple = {:ok, "hello"}     
{:ok, "hello"}
iex(17)> put_elem(tuple, 1, "world")
{:ok, "world"}
iex(18)> tuple
{:ok, "hello"}
iex(19)> tuple = put_elem(tuple, 1, "world")
{:ok, "world"}
iex(20)> tuple
{:ok, "world"}
iex(21)> tuple = put_elem(tuple, 0, :error) 
{:error, "world"}
iex(22)> tuple                             
{:error, "world"}

パターンマッチ

Elixirについて語っていた方が、パターンマッチの良さについて言及してらしたのを思い出しつつ。Elixirではパターンマッチが利用できる。Tupleのパターンマッチは下記の通りになる。

iex(25)> {:ok, result} = {:ok, 13}
{:ok, 13}
iex(26)> result
13

Listでも同様に利用できる。

iex(35)> [a, b, c] = [0, 1, 2]
[0, 1, 2]
iex(36)> a
0
iex(37)> b
1
iex(38)> c
2

|で区切ることで、先頭要素とそれ以外、とすることができるらしい。

iex(51)> list = [1, 2, 3]
[1, 2, 3]
iex(52)> [0 | list] 
[0, 1, 2, 3]

下記のように[h|t]で先頭要素hと残りtを取得できる。

iex(54)> [h | t] = list
[1, 2, 3]
iex(55)> h
1
iex(56)> t
[2, 3]

pin

Use the pin operator ^ when you want to pattern match against an existing variable’s value rather than rebinding the variable:

再代入ではなく、既存の値を使いたいときに^を使うらしい。今のところ用途にピンとこない。とりあえず、下記の通り再代入しないようになるようだ。

iex(60)> x = 1 
1
iex(61)> x = 2
2
iex(62)> ^x = 3
** (MatchError) no match of right hand side value: 3

下記の通りパターンマッチングによる代入もきかなくなる。

iex(63)> {x, y} = {1, 2}
{1, 2}
iex(64)> x
1
iex(65)> {x, y} = {3, 4}
{3, 4}
iex(66)> {^x, y} = {5, 6}
** (MatchError) no match of right hand side value: {5, 6}

分岐

case, cond, and if - Elixir には分岐について記載されている。分岐分が掛ければ、お決まりのフィボナッチ数列導出関数がかける気がする。

case文

doからendの間に、パターンと結果の組を記述することができる。

iex(1)> case {1, 2, 3} do
...(1)>  {1, 2, x} -> "Good"
...(1)>  {1, x, y} -> "So-So"
...(1)>  _ -> "Bad"
...(1)> end
"Good"
iex(2)> case {1, 4, 5} do    
...(2)>  {1, 2, x} -> "Good" 
...(2)>  {1, x, y} -> "So-So"
...(2)>  _ -> "Bad"          
...(2)> end        
"So-So"
iex(3)> case {6, 7, 8} do    
...(3)>  {1, 2, x} -> "Good" 
...(3)>  {1, x, y} -> "So-So"
...(3)>  _ -> "Bad"          
...(3)> end                  
"Bad"

cond文

case is useful when you need to match against different values. However, in many circumstances, we want to check different conditions and find the first one that evaluates to true. In such cases, one may use cond

状態で分岐させたいときにはcaseのパターンマッチはやや使い勝手が悪い気がする、と思っていたら次に別の分岐構文が 載っていた。

iex(7)> cond do   
...(7)>   0 + 1 == 2 -> "this will not be true"
...(7)>   3 * 4 == 5 -> "nor this"
...(7)>   6 - 7 == -1 ->"this will"            
...(7)> end
"this will"

これは他の言語で言うelse if文に近いとあった。なるほど。

Keyword list

Keywords and maps - Elixirによると、特定の形のtupleをKeyword listとしているらしい。

iex(11)> list = [{:a, 0}, {:b, 1}]
[a: 0, b: 1]
iex(12)> list ++ [c: 3]
[a: 0, b: 1, c: 3]
iex(13)> [a: 4] ++ list
[a: 4, a: 0, b: 1]
iex(14)> new_list = [a: 5] ++ list
[a: 5, a: 0, b: 1]
iex(15)> new_list[:a]
5
iex(16)> new_list[:b]
1

ここまでのまとめ

Elixirは関数型言語。文法は、今のところ突飛な感じはない。長くなってきたので、一度記事をここで切ります。