dry-monadsを使ってRubyでモナドの夢を見る

## はじめに
皆さんはRuby、好きですか？僕は好きです。
皆さんはモナド、好きですか？僕は好きです。

好きなものと好きなもの、どっちも使いたくなるのが人間の性。
どうにかしてRubyでモナドを使いたい！

ということで、Rubyでモナドを使って、Rubyでよく書くありがちなコードをいい感じにしていきます。

## モナドってなに？
モナドはHaskellなどの関数型言語で使われる概念です。
細かい定義は他の記事に任せますが、簡単にいってしまうと「書いたコード（文字通り）よりも外の世界から受ける影響に安全にアクセスする方法」です。こういうのをプログラミングでは「[副作用](https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0))」って言ったりします。
モナドについては[ここ](https://www.infoq.com/jp/articles/Understanding-Monads-guide-for-perplexed/)と[ここ](https://qiita.com/suin/items/0255f0637921dcdfe83b)の記事が個人的に勉強になりました。

モナドが力を発揮する場面としてよくあげられるのが「IO」です。IOはまさに「自分が書いたコードの外から受ける影響」ですよね。
入力ではコードを実行するまでどんな値が入ってくるかわかりません。冷静に考えるとこれは結構怖いことだったりします。書いたコードは自分でいくらでもバグらないように修正できますが、外部からの影響には「備える」ことしかできません。
そんな時にモナドを使うと、「外界の影響」を箱に詰めてブラックボックス化しながら、コードとしてはやりたいことを素直に記述するだけでうまいコードを書くことができるようになります。

ここまでの説明を読むだけでも、モナドはなんだかいいもののように感じませんか？（良いものです）
残念ながらRubyには組み込みの文法としてのモナドは存在しません。（今後も多分入らない）
しかし、型安全やバリデーションをシステムに提供してくれるライブラリ群、[dry-rb](https://dry-rb.org/)に[dry-monads](https://dry-rb.org/gems/dry-monads/1.0/)というgemがあります！
このgemを使ってモナドの力をRubyに加えてみます。

## 実践
dry-monadsは関数型言語のモナドをRubyでも扱えるようにしたgemですが、正確にいうと関数型言語におけるモナドとは異なります。
詳しいことは省きますが、結論から言うとモナド則を満たさない記述ができてしまうからです（動的型付け言語だから仕方がないのかもしれない）
しかし、それでもモナドが提供してくれる恩恵を得ることはできます。

言葉で書いてよくもわからないと思うので、早速手続き型プログラミングで書いた場合とモナドを使用して書いた場合のコードを載せます。

手続き型言語ではIO処理ではよくガード条件を使って外部からの影響に備えますね。
例えばRailsのActiveRecordとかを使っているとよくこんな感じのコードを書くんじゃないでしょうか？（ちょっと大げさに書いてます）

```rb
user = User.find_by(id: user_id)
address = Address.find_by(id: address_id)
    
if user.present? && address.present?
  if user.update(address: address)
    ...
  else
    ...
  end
else
  ...
end
```

このように、手続き型で素朴に記述すると`User.find_by` が存在しているかどうか、取得した`User`と関連をもつ`Article`が存在するかどうかを想定したコードになります。

これに`dry-rb`を適用して書くとこうなります。

```rb
require 'dry/monads'

extend Dry::Monads[:maybe]

result = Maybe(User.find_by(id: user_id)).bind do |user|
  Maybe(Address.find_by(id: address_id)).fmap do |address|
    user.update(address: address)
  end
end.to_result

if result.success?
...
else
...
end

```

このようにモナドを使うことで、`user`がなんの値になろうが`article`がなんの値になろうが途中の処理は全てモナドが隠してくれます。これによってコードを書く上では最終結果に対して`success?`が`true`になるかどうかだけを考えれば良くなります。（`to_result`メソッドを用いて`Result`モナドに変換しています）思考がスッキリするのを感じますね。
また、ruby2.7で使うことができるようになるパターンマッチを用いると、↓のようにResultへのキャストをする必要もなく記述できるようになります

```rb
require 'dry/monads'

extend Dry::Monads[:maybe]

result = Maybe(User.find_by(id: user_id)).bind do |user|
  Maybe(Address.find_by(id: address_id)).fmap do |address|
    user.update(address: address)
  end
end

case result
in Some(_)
...
in None()
...
end
```

この例ではオプショナル型に近いような`Maybe`モナドを使いましたが、そのほかにもモナドはたくさんあるのでそれぞれの使い方を紹介します

### Maybe
例でも用いたMaybeは、`nil`を受け取った際には`None()`を返し、それ以外の時は`Some(value)`を返します。
これを用いることで最終結果が`Some` or `None`になるため、最後にその検証だけをすればよくなります。
https://dry-rb.org/gems/dry-monads/1.0/maybe/
コード再掲

```rb
require 'dry/monads'

extend Dry::Monads[:maybe]

result = Maybe(User.find_by(id: user_id)).bind do |user|
  Maybe(Address.find_by(id: address_id)).fmap do |address|
    user.update(address: address)
  end
end

case result
in Some(_)
...
in None()
...
end
```

### Result
ResultはMaybeに似たようなモナドです。Maybeが`nil`を`None`に変換してくれたのに対し、Resultは自分で`Success`と`Failure`の定義をする必要があります。
https://dry-rb.org/gems/dry-monads/1.0/result/

```rb
require 'dry/monads'

extend Dry::Monads[:result]
    
result = find_user(user_id).bind do |user|
  find_address(address).fmap do |address|
    user.update(address: address)
  end
end

case result
in Success(_)
...
in Failure()
...
end


def find_user(user_id)
  user = User.find_by(id: user_id)
  user.present? ? Success(user) : Failure()
end

def find_address(address_id)
  address = Address.find_by(id: address_id)
  address.present? ? Success(address) : Failure()
end
```

### Try
Tryはエラーハンドリングに使うことができるモナドです。
これを用いることで、エラー処理を`call`内に閉じ込めることができます。
https://dry-rb.org/gems/dry-monads/1.0/try/

```rb
require 'dry/monads'

class UpdateUser
  include Dry::Monads[:try]

  attr_reader :user_id, :address_id

  def initialize(user_id, address_id)
    @user_id = user_id
    @address_id = address_id
  end

  def call
    Try { User.find(user_id) }.bind do |user|
      Try { Address.find(address_id) }.bind do |address|
        Try { user.update!(address: address) }
      end
    end
  end
end

result = UpdateUser.new(user_id, address_id).call

case result
  in Try::Value(_)
    ...
  in Try::Error(_)
    ...
end

```

### List
Listは配列に対するイテレーションに使うことができるモナドです。
マップ処理などをモナドの世界に閉じ込めることができます。
https://dry-rb.org/gems/dry-monads/1.0/list/

```rb
require 'dry/monads/list'

M = Dry::Monads

M::List[1, 2].bind { |x| [x - 1] } # => List[0, 1]
M::List[1, 2].bind(-> (x) { [x, x * 2] }) # => List[1, 2, 2, 4]

M::List[1, nil].bind { |x| [x - 1] } # => raise Error
```

### Task
Taskは非同期処理に対して使うことができるモナドです。
これを用いることで、JSの`async`、`await`のような処理を行うことができるようになります。
https://dry-rb.org/gems/dry-monads/1.0/task/

```rb
require 'dry/monads'

class AsyncTask
  include Dry::Monads[:task]

  def call
    task1 = Task { fetch_task1 }
    task2 = Task { fetch_task2 }

    task1.bind { |t1| task2.fmap { |t2| [t1, t2] } }
  end

  def fetch_task1
    sleep 3
    'task1'
  end

  def fetch_task2
    sleep 2
    'task2'
  end
end

async_task = AsyncTask.new

task = async_task.call

task.fmap do |t1, t2|
  puts "Task: #{t1}"
  puts "Task: #{t2}"
end

sleep 10
puts 'done'

```

## まとめ
状況に応じてこれらのモナドを使うことで、やりたいことを素直に記述したコードがかけるようになります。
少しでもご興味を持っていただけたら幸いです。



## 参考文献
- https://qiita.com/suin/items/0255f0637921dcdfe83b
- https://dry-rb.org/gems/dry-monads/1.0/
- https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%8A%E3%83%89_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)
- https://techracho.bpsinc.jp/hachi8833/2017_11_16/47847