Pythonのリスト内包表記を悪用してHaskellのdo構文のように使う

# 発端

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">調べてみたらPythonでflatMap相当の計算はlist comprehensionをネストすると書いてあった。<br>工夫すればScalaのfor comprehensionと同じようにOptionの連鎖がスッキリ書けるかも知れない？<a href="https://t.co/k0royEM0qr">https://t.co/k0royEM0qr</a></p>&mdash; SHIMADA (@_shimada) <a href="https://twitter.com/_shimada/status/1163714487406358533?ref_src=twsrc%5Etfw">August 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

PythonでflatMapを使いたくなって調べてみたら、PythonにはなんとflatMapがなく、そういう時はネストしたリスト内包表記使うといろいろなところに書いてあった

https://hydrocul.github.io/wiki/programming_languages_diff/list/flat-map.html

「ネストしたリスト内包表記」の字面を見ているとScalaのfor内包表記にそっくりだ。
見た目が似ているということは、同じような使い方ができるのではないか？と思い試してみた。

# Option Monad

Scalaのfor式で最も使われる率の高い（当社しらべ）Option Monadの合成をやってみる

例として以下のような処理を考えてみる
1.　入力された文字列を整数に変換する
2.　得られた数値からユーザーを検索する
3.　ユーザーに紐づくアイテムを検索する
4.　アイテムの名称を得る

あくまでも例なので「ユーザーとアイテムはJOINして一緒に検索しろよ」などの野暮なツッコミは遠慮してほしい。

1～4の処理を手続き的に並べると、間に失敗したときのチェックを挟まなくてはならないのでコードがごちゃごちゃしてしまう。

そういうとき、HaskellやScalaでは Maybe / Option Monadを使う。

Hakellの例

```haskell
name = do
    code <- readMaybe "234" :: Maybe Int
    user <- findUser code
    item <- findItem $ userId user
    return $ itemName item

print name  -- Just "keyboard"
```

Scalaの例

```scala
name = for {
    code <- parseToInt("1234")
    user <- findUser(code)
    item <- findItem(user.id)
} yield item.name

println(name)  // -> Some("keyboard")
```

parseToInt, findUser, findItemなどの各関数のどこかで失敗したときは、そこで処理が打ち切られて name には `Nothing` / `None` が代入される。


# Pythonでやってみる

これと同じことをPythonで書いてみる。
Maybe / Option がないので `Just 1` の代わりに `[1]` 、 `Nothing` の代わりに `[]` を使う。

まず必要な部品を作る。

## (1) 文字列を数字に変換する関数

```python
def parseInt(input: str) -> List[int]:
    try:
        return [int(input)]
    except ValueError:
        return []
```

## (2) 会員番号からユーザーを検索する関数

```python
def findUser(code: int) -> List[dict]:
    users = {
        2012: {"id": 1, "code": 2012, "name": "yuzu"},
        3308: {"id": 2, "code": 3308, "name": "yui"}
    }
    user = users.get(code)
    if user:
        return [user]
    else:
       return []
```

## (3) ユーザーIDからアイテムを検索する関数

```python
def findItem(userId: int) -> List[dict]:
    items = {
        1: {"userId": 1, "name": "keyboard"}
    }
    item = items.get(userId)
    if item:
        return [item]
    else:
        return []
```

## (4) 定義できた関数を組み合わせて実際に使ってみる

うまく検索できるとアイテム名がリストに入って返ってくる

```python
keybord = [item["name"]
    for code in parseInt("2012")
    for user in findUser(code)
    for item in findItem(user["id"])]

print(keybord)  # => ['keyboard']
```

数値以外の文字列を渡したときは変換に失敗して `[]` が返る

```python
what = [item["name"]
    for code in parseInt("WXYZ")  # <- 失敗する
    for user in findUser(code)
    for item in findItem(user["id"])]

print(what)  # => []
```

アイテムを持っていないユーザーを指定したときは2番目の検索に失敗して `[]` が返る

```python
guitor = [item["name"]
    for code in parseInt("3308")
    for user in findUser(code)
    for item in findItem(user["id"])]  # <- 失敗する

print(guitor)  # => []
```

ものすごく簡単に、do構文やfor式とほぼ同じ形で書くことができた。
ぱっと見で違うのは Scala では末尾にくるyield式が先頭にくることくらいだろうか。

# まとめ

失敗するかも知れない計算を表すOption Monadを要素数0ないし1のリストにエンコードすることで、Pythonのリスト内包表記を使って簡潔に合成することができた。

Option Monadもどきが書けるようになると、今度はEither Monadもどきが欲しくなるところだが、残念ながらPythonのリスト内包表記はリストしか返せず、計算を打ち切った時は空リストしか受け取れない。
この問題は今後の課題としたいと思う。