11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ElixirでMonad、モナド (1)

Last updated at Posted at 2019-06-04

Elixirの型クラス周りのライブラリである Algae について紹介します
あくまで使い方について紹介するだけでモナドについての難しい話はしません

Algae

代数的データ型とそれに対する型クラスインスタンスの定義を含むライブラリです
Elixirの世界にMaybeやEitherをもたらしてくれるのでカジュアルに使っても
nilセーフになったり、パターンマッチヘルなどを回避できるなど恩恵があります

今回では定義されている代数的データや、defmacroで定義されているdo構文をみていきましょう

依存性

Algaeは前項で述べたとおり代数的データ型とそれに対する型クラスインスタンスの定義のライブラリです
型クラス機能自体の実装は type_class
各型クラス(Functor, Applicative, Monad. etc...)の実装は Witchcraft
にあります

do構文

do構文はWitchcraftの lib/witchcraft/chain.ex に定義されています
Haskellと違い Chain 型クラスを実装していれば使用することができます
(ただし、returnの使い方が後述のmonadマクロを使用する上、少し特殊です)

import Witchcraft.Chain
alias Algae.Maybe
alias Algae.Maybe.{Just, Nothing}

def f(nillable, n) do
  chain do
    a <- Maybe.from_nillable(nillable)
    b <- safe_div(a, n)
    Maybe.new(a + b)
  end
end

def safe_div(a, b) do
  if b != 0, do: %Just{just: a / b}, else: %Nothing{}
end
iex(31)> f(1, 3)
%Algae.Maybe.Just{just: 1.3333333333333333}
iex(32)> f(nil, 3)
%Algae.Maybe.Nothing{}
iex(33)> f(1, 0)
%Algae.Maybe.Nothing{}
iex(34)>

また、lib/witchcraft/monad.ex に定義されているmonad macroを使うことでreturnを使用することも可能になります

def f(nillable, n) do
  monad Maybe.from_nillable(nil) do
    a <- Maybe.from_nillable(nillable)
    b <- safe_div(a, n)
    return (a + b)
  end
end

monad macro第一引数として渡している Maybe.from_nillablereturn を呼び出したときに実際に呼び出される関数となります、from_nillable に渡している引数値は無視されるのでなんでもいいのですが、なんらかの値を渡さない場合は Maybe.from_nillable/0 の関数として認識されてしまうので必須です
returnにnil値を渡さないとわかっているのであれば Maybe.new() でも良いでしょう

do構文を使わずbind関数で書く場合はこうなります
(Haskellのbind (>>=) とは違い、bindのaliasは >>> になっています)

def f(nillable, n) do
  Maybe.from_nillable(nillable)
  >>> fn a -> safe_div(a, n)
  >>> fn b -> Maybe.new(a + b) end end
end

…見ての通りネストするほど end が多くなっていくのでdo構文で書くことをオススメします

ちなみに let による変数束縛もちゃんと使えます

def f(nillable, n) do
  chain do
    a <- Maybe.from_nillable(nillable)
    b <- safe_div(a, n)
    let c = 10
    Maybe.new(a + b + c)
  end
end

Maybe

  • Haskell
data Maybe a = Just a 
             | Nothing
  • Elixir(Algae)
defsum do
  defdata Nothing :: none()
  defdata Just    :: any()
end

defsum は直和 defdata は直積を定義するためのマクロです
Elixirには型パラメタの機能はないので Maybe a にあたる aany になります

  • Maybe.new/0 Maybe.new/1 / Maybe.from_nillable/1
    コンストラクタとしては二種類用意されています
    Maybe.new(nil) とした場合は %Just{just: nil} になってしまうので基本的には Maybe.from_nillable を使うことを推奨します

  • Maybe.from_maybe/2
    第一引数に Maybe 型を受け取りJustの場合はその中身を、Nothingの場合は第二引数の値を返します

Either

  • Haskell
data Either a b = Left a
                | Right b
defsum do
  defdata Left  :: any()
  defdata Right :: any()
end

Elixirには型パラメタの機能はない(ry

  • Right.new/1 / Left.new/1
    Eitherにおけるコンストラクタです

  • example

def fE(nillable, n) do
  chain do
    a <- case nillable do
           nil -> Left.new("nil is can't division.")
           n -> Right.new(n)
         end
    b <- safe_divE(a, n)
    Right.new(a + b)
  end
end

def safe_divE(a, b) do
  if b != 0, do: Right.new(a / b), else: Left.new("n is 0.")
end
iex(50)> fE(1, 3)
%Algae.Either.Right{right: 1.3333333333333333}
iex(51)> fE(nil, 3)
%Algae.Either.Left{left: "nil is can't division."}
iex(52)> fE(1, 0)
%Algae.Either.Left{left: "n is 0."}

State

newtype State s a = State { runState :: s -> (a,s) }  
@type runner :: (any() -> {any(), any()})
@type t :: %State{runner: runner()}

Maybe/Eitherと比較するとかなり特殊ですね
Stateの実態は、状態値 s を受け取って結果値 a と新しい状態 s を返す関数なのでこのような特殊な形になります
HaskellではStateを渡すときに runState で渡しますがAlgaeでは runner に(実態の関数に)渡します

Algae.State.state(fn x -> {x + 1, x} end).runner.(42)
  • State.new/1 / State.state/1
    any() -> {any(), any()} の関数を受け取ってState型を返す
    つまりコンストラクタです
    state関数はnew関数のエイリアスです

  • put/1 / get/0
    Stateモナドコンテキスト内で使える値のセットとゲットです
    と書くよりdo構文で使っているのを見るほうが理解できるでしょう

def fS() do
  init = State.state(fn s -> {0, s} end)
  chain do
    i <- init
    n <- State.get()
    State.put(99) ~> (fn _z -> i + n end)
  end
end
iex(65)> A.fS().runner.(3)
{3, 99}

( ~> はfmap( <$> )です)

その他
状態を変更する関数を受け取る modify
Stateモナドから結果値のみを取得する evaluate
Stateモナドから状態のみを取得する execute (使い道あるのかな?)
などがありますが割愛します


今回紹介した他 writer reader free などおなじみのモナドたちが定義されていますが
そちらの紹介はまた次の機会という形にします

Haskellのように強い型付けがないのでElixirの世界ではモナドを利用したとしても
完全に副作用の切り離しを保証できるものではありません
ですがdo構文などコードを完結に書く力は得られるので使う価値のないものでもありません
Elixirの世界にモナドを取り入れられるこのライブラリを僕は素晴らしいものだと思います

11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?