はじめに

こんなネストした辞書型のインスタンス(以下単に辞書型)があるとする。

1
2
3
4
5
d = {
"a": {
"b": 1
}
}
  • 辞書型のネストが深いときPythonではいちいちd["a"]["b"]と書かなきゃいけなくてめんどくさい。d.a.bって書きたい
  • 途中にKeyErrorになったときに最後までNoneを伝播して返してほしい。

こんなPythonの不満を解決するのにMaybeモナドは最適である。

実装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Just(object):
def __init__(self, a):
object.__setattr__(self, 'a', a)

def __call__(self, default=None):
a = object.__getattribute__(self, 'a')
return default if a == None else a

def __getattribute__(self, key):
a = object.__getattribute__(self, 'a')
if isinstance(a, dict) and (key in a):
return Just(a[key])
else:
return Just(None)

def __getitem__(self, f):
return Just(f(object.__getattribute__(self, 'a')))

Pythonで実際に使うにはモナドから値を取り出す必要があるが、普通のメソッドにするとキーを探してしまう。そこで__call__特殊メソッドを使うことで()で取り出すことができるようにする。

また、モナドの中身に関数を適用したくなったとする。これも先程と同じように、__getitem__特殊メソッドを使うことで[]で関数を適用させようということである。

これにより、次のようなことが可能となる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
d = {
"a": {
"b": 1
}
}

print(Just(d).a()) # {'b': 1}
print(Just(d).a.b()) # 1
print(Just(d).a.b.c()) # None

print(Just(d).b.c()) # None
print(Just(d).b.c('Default')) # 'Default'

print(Just(d).a.b[lambda n:n*10]()) # 10