# 1行コメントはシャープで始まります。 # 複数行コメントはありませんが、 # コメントを連続して書くことはできます。 # Elixirシェルを利用するには`iex`コマンドを使用します。 # モジュールをコンパイルするには`elixirc`コマンドを使用します。 # Elixirが正しくインストールされている場合、両コマンドはPATHに入っています。 ## --------------------------- ## -- 基本的な型 ## --------------------------- # これらは数字です 3 # 整数 0x1F # 整数 3.0 # 浮動小数点数 # アトムは自身の名前を値として持つ定数です。基本的に`:`で始まります。 :hello # アトム # タプルはメモリーの中に連続して格納されます。 {1,2,3} # タプル # `elem`関数を使うことで、タプルの要素にアクセスできます elem({1, 2, 3}, 0) #=> 1 # リストは連結リストとして実装されています。 [1,2,3] # リスト # リストのヘッドとテールは次の形でアクセスできます [head | tail] = [1,2,3] head #=> 1 tail #=> [2,3] # ElixirではErlangと同様、`=`は代入ではなくパターンマッチを表します。 # # 言い換えれば、右側の内容を左側のパターンを当てはめる指示を意味します。 # # 上記の例でヘッドとテールに正しくアクセスできるカラクリはここにあります。 # パターンマッチはそれぞれの内容がマッチしない場合、失敗します。 # この例では、それぞれのタプルのサイズが一致していません。 # {a, b, c} = {1, 2} #=> ** (MatchError) no match of right hand side value: {1,2} # 値にはバイナリーもあります <<1,2,3>> # バイナリー # 文字列と文字リスト "hello" # 文字列 'hello' # 文字リスト # 複数行文字列もあります """ 私は複数行 文字列です。 """ #=> "私は複数行\n文字列です。\n" # 文字列はすべてUTF-8でエンコードされています "héllò" #=> "héllò" # 文字列の実態はバイナリーで、文字リストはただのリストです。 <> #=> "abc" [?a, ?b, ?c] #=> 'abc' # Elixirでは、`?a`は`a`のASCIIコード整数を返します ?a #=> 97 # リストをつなげるには`++`を、バイナリーをつなげるには`<>`を使います [1,2,3] ++ [4,5] #=> [1,2,3,4,5] 'hello ' ++ 'world' #=> 'hello world' <<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>> "hello " <> "world" #=> "hello world" # レンジは`start..end`の形で表現されます(両端を含む) 1..10 #=> 1..10 lower..upper = 1..10 # レンジにもパターンマッチを適用できます [lower, upper] #=> [1, 10] # マップはそれぞれのキーと値を集めたものです genders = %{"david" => "male", "gillian" => "female"} genders["david"] #=> "male" # 次の例ではアトムをキーとして使用しています genders = %{david: "male", gillian: "female"} genders.gillian #=> "female" ## --------------------------- ## -- 演算子 ## --------------------------- # 単純な計算 1 + 1 #=> 2 10 - 5 #=> 5 5 * 2 #=> 10 10 / 2 #=> 5.0 # Elixirでは、`/`演算子は必ず浮動小数点数を返します。 # 整数として除算する場合は`div`を使用します div(10, 2) #=> 5 # 余りを計算するには`rem`を使用します rem(10, 3) #=> 1 # `or`、`and`や`not`など、ブーリアン演算子も存在します。 # これらの演算子は第一引数(左側)としてブーリアンを期待します。 true and true #=> true false or true #=> true # 1 and true #=> ** (BadBooleanError) expected a boolean on left-side of "and", got: 1 # さらに、Elixirにはあらゆる型に対応した`||`、`&&`、`!`演算子が存在します。 # `false`と`nil`を除いたあらゆる値は真値として扱われます。 1 || true #=> 1 false && 1 #=> false nil && 20 #=> nil !true #=> false # `==`、`!=`、`===`、`!==`、`<=`、`>=`、`<`、`>`を使うことで比較ができます 1 == 1 #=> true 1 != 1 #=> false 1 < 2 #=> true # `===`と`!==`は、整数と浮動小数点数を厳格に区別して比較します 1 == 1.0 #=> true 1 === 1.0 #=> false # Elixirの演算子は基本的に厳格ですが、異なる型を受け取る比較演算子は例外です 1 < :hello #=> true # これにより異なる型の集まりも作成できるようになります ["string", 123, :atom] # 異なる型同士にも順序は定義されています。ここでは詳しい順序を説明する代わりに # 「どのような順序であるかは重要ではない。何らかの順序が定義されているという # 事実自体が重要なのだ」というジョー・アームストロングの言葉を引用します。 ## --------------------------- ## -- 制御フロー ## --------------------------- # `if`式 if false do "ここには到達しません" else "ここにはします" end # パターンマッチは覚えていますか?Elixirの制御フローでたくさん登場します。 # `case`では値を複数のパターンに対して比較できます case {:one, :two} do {:four, :five} -> "これはマッチしません" {:one, x} -> "これはマッチし、`x`に`:two`をバインドします" _ -> "これはどんな値もマッチします" end # 内容に興味がない場合、`_`に値をバインドするのが一般的です。 # たとえば、リストの一番最初の要素のみを取り出したい時は [head | _] = [1,2,3] head #=> 1 # 可読性を高めたい場合はこのように書くこともできます [head | _tail] = [:a, :b, :c] head #=> :a # `cond`では複数の条件を同時に比較できます。 # `if`式を重ねるよりも、`cond`を優先して使いましょう。 cond do 1 + 1 == 3 -> "ここには到達しません" 2 * 5 == 12 -> "ここにもしません" 1 + 2 == 3 -> "ここにはします" end # 一般的には、常にマッチする条件を設定するため`true`を最後に置きます。 cond do 1 + 1 == 3 -> "ここには到達しません" 2 * 5 == 12 -> "ここにもしません" true -> "ここにはします(事実上のelse)" end # スローされた値をキャッチするには`try/catch`を使います。 # キャッチの有無を問わず実行される`after`句もサポートされています。 try do throw(:hello) catch message -> "#{message}を得ました。" after IO.puts("ここがafter句です。") end #=> ここがafter句です。 # ":helloを得ました。" ## --------------------------- ## -- モジュールと関数 ## --------------------------- # 無名関数(ドットに注目) square = fn(x) -> x * x end square.(5) #=> 25 # 句やガードは複数回指定できます。 # ガードは`when`キーワードで示され、パターンマッチの調整に利用されます f = fn x, y when x > 0 -> x + y x, y -> x * y end f.(1, 3) #=> 4 f.(-1, 3) #=> -3 # Elixirはさまざまなビルトイン関数を提供しています。 # 例として、これらの関数が現在のスコープで呼び出せます。 is_number(10) #=> true is_list("hello") #=> false elem({1,2,3}, 0) #=> 1 # 関数はモジュールとしてまとめることもできます。 # モジュール内では`def`を使用して関数を定義します。 defmodule Math do def sum(a, b) do a + b end def square(x) do x * x end end Math.sum(1, 2) #=> 3 Math.square(3) #=> 9 # 今回定義したMathモジュールをコンパイルするには`math.ex`として保存してから、 # ターミナルで`elixirc`を呼び出します。例:elixirc math.ex # モジュール内では`def`を使い関数を定義できるのと同様に、`defp`を使うことで # プライベート関数を定義できます。`def`で定義された関数は他のモジュールからも # 呼び出せますが、プライベート関数は同一のモジュール内からのみ呼び出せます。 defmodule PrivateMath do def sum(a, b) do do_sum(a, b) end defp do_sum(a, b) do a + b end end PrivateMath.sum(1, 2) #=> 3 # PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError) # 関数は複数回定義できるほか、ガードもサポートされています。 # 複数回定義された関数を呼び出すと、条件を満たす最初の関数が実行されます。 # 例:area({:circle, 3})は上のarea関数ではなく、2番目の関数を呼び出します defmodule Geometry do def area({:rectangle, w, h}) do w * h end def area({:circle, r}) when is_number(r) do 3.14 * r * r end end Geometry.area({:rectangle, 2, 3}) #=> 6 Geometry.area({:circle, 3}) #=> 28.25999999999999801048 # Geometry.area({:circle, "not_a_number"}) #=> ** (FunctionClauseError) no function clause matching in Geometry.area/1 # Elixirは原則的に不変であるため、再起が多く活用されます defmodule Recursion do def sum_list([head | tail], acc) do sum_list(tail, acc + head) end def sum_list([], acc) do acc end end Recursion.sum_list([1,2,3], 0) #=> 6 # Elixirのモジュールには属性を関連付けられます。ビルトインの属性に限らず、 # あなた独自のカスタム属性も指定できます。 defmodule MyMod do @moduledoc """ これはサンプルのモジュールのビルトイン属性です。 """ @my_data 100 # これはカスタム属性です。 IO.inspect(@my_data) #=> 100 end # パイプ演算子 |> を使うと、式の結果を次の関数の第一引数に渡すことができます。 Range.new(1,10) |> Enum.map(fn x -> x * x end) |> Enum.filter(fn x -> rem(x, 2) == 0 end) #=> [4, 16, 36, 64, 100] ## --------------------------- ## -- 構造体と例外 ## --------------------------- # 構造体はマップの拡張で、初期値、コンパイル時保証、ポリモーフィズムをElixir # に導入します。 defmodule Person do defstruct name: nil, age: 0, height: 0 end joe_info = %Person{ name: "Joe", age: 30, height: 180 } #=> %Person{age: 30, height: 180, name: "Joe"} # nameの値にアクセス joe_info.name #=> "Joe" # ageの値を更新 older_joe_info = %{ joe_info | age: 31 } #=> %Person{age: 31, height: 180, name: "Joe"} # 例外を扱うには`try`ブロックと`rescue`キーワードを組み合わせます try do raise "何かしらのエラー" rescue RuntimeError -> "ランタイムエラーをレスキューしました" _error -> "こちらは任意のエラーをレスキューします" end #=> "ランタイムエラーをレスキューしました" # あらゆる例外はメッセージを持ちます try do raise "何かしらのエラー" rescue x in [RuntimeError] -> x.message end #=> "何かしらのエラー" ## --------------------------- ## -- 並行性 ## --------------------------- # Elixirは並行性を実現するために、アクターモデルを採用しています。Elixirで # 並行処理を実現するために必要なものは、プロセスのスポーン、メッセージの送信、 # メッセージの受信の3つです。 # プロセスを開始するには、関数を引数として受け取る`spawn`関数を使用します。 f = fn -> 2 * 2 end #=> #Function spawn(f) #=> #PID<0.40.0> # `spawn`が返すpid(プロセスID)を使うことで、プロセスにメッセージを送る # ことができます。メッセージを送るには、`send`演算子を使います。しかし、 # メッセージは受け取らなければ意味がありません。`receive`を使って、これを # 実現しましょう # メッセージを受け取り、処理するためには`receive do`ブロックを使用します。 # ただし、`receive do`ブロックは基本的に単一のメッセージのみを処理します。 # 複数のメッセージを処理するには、`receive do`ブロックを持つ関数を再帰的に # 呼び出すことで、`receive do`ブロックを繰り返し評価する必要があります。 defmodule Geometry do def area_loop do receive do {:rectangle, w, h} -> IO.puts("Area = #{w * h}") area_loop() {:circle, r} -> IO.puts("Area = #{3.14 * r * r}") area_loop() end end end # モジュールをコンパイルし、`area_loop`をシェル内で実行します pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0> # 他の書き方もあります pid = spawn(Geometry, :area_loop, []) # recieve文のパターンに合致するメッセージを`pid`に送信します send pid, {:rectangle, 2, 3} #=> Area = 6 # {:rectangle,2,3} send pid, {:circle, 2} #=> Area = 12.56000000000000049738 # {:circle,2} # シェルそれ自身もプロセスで、`self`を使えばpidを取得できます self() #=> #PID<0.27.0> ## --------------------------- ## -- エージェント ## --------------------------- # エージェントは変化する値をトラッキングするプロセスです # `Agent.start_link`でエージェントを作成し、関数を入力します # この関数の戻り値がエージェントの初期状態になります {:ok, my_agent} = Agent.start_link(fn -> ["red", "green"] end) # `Agent.get`はエージェント名と現在の状態を受け取る`fn`を引数に取ります # この`fn`によって返される値が最終的な戻り値になります Agent.get(my_agent, fn colors -> colors end) #=> ["red", "green"] # 同じ方法でエージェントの状態を更新できます Agent.update(my_agent, fn colors -> ["blue" | colors] end)