LoginSignup
4
4

More than 3 years have passed since last update.

たぶんニシキヘビの中にいる (原題: Maybe in Python)

Last updated at Posted at 2020-01-14

(翻訳記事じゃ) ないです。

Maybe はいいですよね。 値がある、という状態と値がない、という状態を非常に扱いやすくしてくれます。 リリンが生み出した文化の極みです。

なので、Python で似たようなことができないかと思い実装してみました。

本体はこちらにあげてあります。 ぜひ遊んでみてマサカリを投げるといいと思います。

仕様

一部は “null安全でない言語は、もはやレガシー言語だ” を露骨に意識した構成になっておりますので、合わせてお読みいただくと良いと思います。

基本

この Maybe は抽象クラスMaybeを継承した具象クラスSomethingNothingとして実装されています。 Somethingは値があることを示し、Nothingは値が欠落していることを示します。

just関数はSomethingのインスタンスを作成します。 nothingNothingの (唯一の) インスタンスです。

from maybe import just, nothing

a = just(42)
b = nothing

print(a)
print(b)
Just 42
Nothing

Maybe はプロパティhas_valueを持ち、SomethingTrueを、NothingFalseを返します。 またSomethingはそれ自体 Truthy であり、Nothingはそれ自体 Falsy です。

print(a.has_value)
print(b.has_value)

print('-----')

if a:
    print('a is Truthy')
else:
    print('a is Falsy')

if b:
    print('b is Truthy')
else:
    print('b is Falsy')
True
False
-----
a is Truthy
b is Falsy

Maybe は値を入れる箱と考えることが出来ます。 Maybe 自体も値ですから、Maybe は入れ子構造になりえます。 Maybe は “深さ” という概念をもちます。 裸の値の深さは 0 です。 Nothingの深さは 1 です。 Somethingの深さは “中身の深さ + 1” です。 深さはdep関数に渡すことで得られます。

from maybe import dep

a2 = just(a)
b2 = just(b)
print(a2)
print(b2)

print('-----')

print(dep(42))
print(dep(a))
print(dep(b))
print(dep(a2))
print(dep(b2))
Just Just 42
Just Nothing
-----
0
1
1
2
2

Maybe は等値比較ができます。 Somethingは互いがSomethingであり、その中身が等値であれば等しいとします。 入れ子構造ならば再帰しながら掘っていくことになります。 Nothingは互いがNothingであれば等しいとします。

print(a == 42)
print(a == just(42))
print(a == a2)
print(a == b)
print(b == nothing)
print(b == b2)
print(b2 == just(nothing))
False
True
False
False
True
False
True

Maybe バインディング (Null チェックに伴うキャスト)

Maybe[T]Tとして使うには、

  1. その Maybe が値を持つことを確認し、
  2. Maybe[T]Tに変換しなければいけません。

if 文と後述する強制アンラップ演算子を使用することで以下のように使うことは可能です。

m = just(913)
if m.has_value:
    print(m.i + 555)
1488

しかし、このふたつの工程が分離していると、以下のような思わぬハプニングが発生しえます。

m = just(913)
if m.has_value:
    print(m + 555) # アンラップ忘れ、Maybe[int] と int の加算はできない
TypeError: unsupported operand type(s) for +: 'Something' and 'int'
m = nothing
print(m.i + 555) # Nullチェック忘れ、m が Nothing の場合ぬるぽが飛ぶ
maybe.NullpoError: it GOT no value.

実は Maybe はイテレーション可能で、Somethingは中身の値を一回だけ生成し、Nothingはなにも生成せずにStopIteration()例外を発生させます。 これを利用すると、for 文を使用して以下のように Null チェックとキャストを同時に行えます。

m = just(913)
for v in m:
    print(v + 555) # ここは m が Something のときだけ実行される
1468

ちょうど Swift の Optional バインディングと似た使い心地になるので、私はこれを “Maybe バインディング” と呼んでいます。 Optional バインディングとの違いは、シャドーイング (ブロック内で同じ変数名を使用すること) ができない点と、値が欠落している場合との分岐が少々煩雑になる点です。

順に見ていきましょう。 もしシャドーイングのようなことをする場合、Python の変数スコープの関係上、コメントの行でmMaybe[int]からintに変わってしまいます。

m = just(913)
print(m) # ここでは Maybe[int]
for m in m:
    print(m + 555)
print(m) # ここでは int
Just 913
1468
913

すでに Null チェックをしているのだからこのほうが便利だと思いますか? しかしそれは罠です。 もしmの値が欠落している場合Maybe[int]のままです。 あなたはこの先 “Maybe” という同じコンテキストでmを扱うことは許されず、intのように見えるmnothingである可能性に怯えながら過ごさなければならないのです。 これではなんのための Maybe かわかりません。 単にNoneを使用しているのと大差がない (どころかより面倒になっている) じゃないか。

つづいての相違点です。 値があるときだけ実行したいのではなく、値があるときとないときで処理を分岐したい場合があるでしょう。 Swift の Optional では次のように書けます。

let m: Int? = nil
if let v = m {
    print(v + 555)
} else {
    print("ひょっとして君は俺が嫌いなのかな?")
}

ifelseの対応は美しく、他の追随を許しません。 そしてこのふたつのブロックは文法上対になっています。

しかし Maybe ではこのように書かざるを得ません。

m = nothing
for v in m:
    print(v + 555)
if not m.has_value:
    print("ひょっとして君は俺が嫌いなのかな?")

forifの対応というのはおそらく生まれてこの方誰も見たことがないでしょう。 それにこのふたつのブロックは隣接して書いてあるだけで文法上の対でもありません。 さらに言えば、if not m.has_valueとはずいぶんと長いです。 これについてはもともとNothingが Falsy ですからif not mでもよいのですが、すこし文意が読み取りづらい欠点もあります。

とはいえ、Null チェックとキャストが分離されているよりも遥かに安全であり、書きやすさもあります。 基本的には.iの使用は禁止し、Maybe バインディングを使用するのがよいでしょう。

強制アンラップ演算子 (from !, !!)

strからintへの変換を試みて、失敗したらnothingを返す関数safe_intを考えます。

def safe_int(s):
    try:
        return just(int(s))
    except ValueError:
        return nothing

ところで、この関数にとあるサイトで利用する “年齢” テキストボックスから飛んできた文字列が入るとします。 普通は HTML 側で数字しか入力できないように制限しますし、前段の JavaScript で前処理なども行うでしょう。

そういう場合、事実上この関数がnothingを返す可能性はないと言えます。 それでもMaybe[int]intとして使えないから長々と Maybe バインディングを書かなければいけないのでしょうか? それではNoneを使うほうがマシということになります。

そういうときにこそ強制アンラップ演算子.iを使用すればよいのです。 ちなみに演算子とか言っていますが実態はプロパティです。

s = get_textbox('年齢')
age = safe_int(s).i # .i によって age には整数が代入される
if age >= 20:
    ...

手間がなく便利に思えますが、もしnothingに対して.iを呼び出すと古き良きアレが元ネタのNullpoErrorが飛びます。 繰り返しになりますが、強制アンラップ演算子はあくまでも “危険な (Unsafe)” 操作であると考え、ロジック上絶対にnothingになりえない箇所でのみ使用するべきです。

Null 条件演算子 (from ?., ?->)

ここにMaybe[list]があるとします。 あなたはこの中にいくつ0があるか調べたいと思いました。 Maybe ということはnothingな可能性もあると思いますが、大抵の場合リスト自体が存在しないとしたら、期待するのはやはりnothingでしょう(いやいや0が欲しいって人もいる? そういう方は次の “Null 癒合演算子” も見てください)。

愚直に書けば次のようになります。

l = get_foobar_list('hogehoge')

for l0 in l:
    num_0 = just(l0.count(0))
if not l:
    num_0 = nothing

確かに書けますが……はっきり言って煩雑です。 もしllistであればnum_0 = l.count(0)ですむ処理を、4 行もかけて書きたくありませんよね。

Null 条件演算子.q.を使えば簡潔に書けます (例によってホントはプロパティです)。

l = get_foobar_list('hogehoge')
num_0 = l.q.count(0)

また、添字に対しても同様に.q[]が利用できます。

l = get_foobar_list('hogehoge')
print(l.q[3])

# 以下の代わりに使える
# for l0 in l:
#     print(just(l0[3]))
# if not l:
#     print(nothing)

なぜこのようなことができるのでしょう? .qは、SafeNavigationOperatorという抽象クラスを継承したオブジェクトを返します。 SafeNavigationOperator__getattr____getitem__をオーバーライドしています。値をもつ場合SomethingSNOが返されます。 こちらは値の情報を保持していて、__getattr____getitem__は中身の値のそれをjustにくるんで返します。 値が欠落している場合NothingSNOが返され、__getattr____getitem__はただnothingを返します。

ここで勘のいい方は疑問に思うかもしれません。 some_list.countはメソッドであり、呼び出し可能オブジェクトです。 maybe_list.q.countは関数をさらに Maybe でくるんで返されるわけです。 それをl.q.count(0)のように扱えるということは、Maybe 自体呼び出し可能オブジェクトなの?

実はそのとおりです。 Somethingを呼び出すと中身を呼び出し、その結果をjustにくるんで返します。 Nothingを呼び出すと単にnothingを返します。 この仕様によって先に挙げたようなことが可能になっています。 この仕様はあくまでも.q.と同時に使用するためのものです。 それ以外で使用するのは、個人的には魔術的に思うので控えたほうがよいと思います (後述する Maybe 同士の四則演算などを実装していない理由に繋がります)。

ところで、?.を実装している言語の中でも、いわゆる “オプショナルチェーン” を採用している場合では Null が見つかった時点で終了してしまいます (いわば短絡評価) が、.q.の場合はnothingのまま処理を続けます。 nothingに対して.q.をしてもnothingが出るだけで、伝播した結果として得られるのが Null 相当物であることにかわりはありませんが、この違いについてはしっかり認識しておく必要があるかもしれません。

ちなみに、__setattr__はオーバーライドしていないのでfoo.q.bar = bazみたいなことはできません。 これは私の技術的な限界であり、__setattr__をオーバーライドしたときに生じる無限ループをどうしても解決できなかったためです。 助力願いてぇ……。 foo.q[bar] = bazのほうはできると思いますが対称性が崩れるのでいまのところ実装していません。

Null 癒合演算子 (from ?:, ??)

Null の場合にデフォルト値で埋めたいというのは Null を扱う上で最も多い要求でしょう。

もちろん Maybe バインディングで書くこともできますが……

l = get_foobar_list('hogehoge')
num_0 = l.q.count(0) # ちなみにここでは num_0 は Maybe[int] で……

for v in num_0:
    num_0 = v
if not num_0:
    num_0 = 0

# ここまで抜けると num_0 は int になります。

ちゃんと Null 癒合演算子1>>が用意されています。

l = get_foobar_list('hogehoge')
num_0 = l.q.count(0) >> 0 # num_0 は int

Null 癒合演算子の動作は一見シンプルに見えます (し、実際シンプルです) が、少々注意が必要です。

左辺の値が欠落しているときは話はカンタンで、右辺をそのまま返します。

一方で左辺が値を持つときは興味深い (Interesting の訳語的な意味で) 動作をします。 左辺が右辺より深いときは、左辺の中身と右辺を再度癒合します。 右辺が左辺より深いときは、左辺をjustでくるんでから再度癒合します。 同じならば左辺を返します。 ただし、右辺が裸の値だった (深さ 0 の) ときは、右辺をjustでくるんで再度癒合し、その中身を返します。

外から見ると似たような動きをするor演算子の方は左辺が Truthy ならそのまま返すのにくらべてずいぶんと複雑な動きです。 なぜこんなことをしているのかというと、再帰呼び出しの結果として必ず右辺と同じ深さの値を返すためです。

“デフォルト値を設定する” という文脈において期待するのは、値があろうとなかろうと同じように扱えることでしょう。 Maybe は入れ子可能なため、いくぶんか煩雑な動作が必須になります。 ただしそれは内部の話であり、使う側は “常に同じ構造の値が返る” というシンプルな扱いができるでしょう。

たとえば、複数の Maybe があったとき、それらの構造にとらわれず、以下のようにして “最右辺と同じ構造 (この場合裸の値) の、一番早く値が現れる Maybe” を得ることができます。

x = foo >> bar >> baz >> qux >> 999

一方、短絡評価ではないので右辺が評価されてしまう点には注意してください。

ちなみに、この演算子は右ビットシフト演算子をオーバーロードして実装されています。 そのため、演算の優先順位も同一となります。 つまり、加法演算子よりも低く、比較演算子よりも高いです2。 これは C#3、Swift4、Kotlin5 の Null 癒合演算子の優先順位に近いですが、一方で PEP 505 で提案されている None-aware 演算子の??がほとんどの二項演算子よりも高い優先順位を持つ6のとは大きく異なります。 PEP 505 が正式採用される日が来たら (来ない気もしますが) 注意してください。 また、自動的に>>=も定義されています。

a = just(42)
b = nothing

print(a >> 0 + 12) # 42 ?? 0 + 12 なら 54 となる(ハズ)

print('-----')

a >>= 999
b >>= 999

print(a)
print(b)
42
-----
42
999

余談も余談ですが、オーバーロード先として>>を選んだのは、

  1. 手書きの “?” が “>” から下に伸ばして打点するように書く人がいる (形状の類似)
  2. キーボードにおいて “?” と “>” は隣り合っている (入力操作の類似)
  3. 複数並べて書いたとき、左から順に値をチェックしていくのを視覚的にも理解しやすい (処理ロジックの可視化)
  4. 他の言語における Null 癒合演算子の優先順位に近い (混乱しにくい)

などが理由です。 下に行くほど合理的な理由に見えますが、恐ろしいことに下に行くほど後付けの理由であり、筆者が適当に Maybe を作っていたのがよくわかります。

mapメソッド (from map)

Null 条件演算子には実は弱点があります。 それは、Maybe オブジェクトにしか使用できない点です。 SafeNavigationOperatorを引数に与えることもできません。

どういうことかというと、こういうことはできません。

l = just([1, 2, 3, ])

s = .q.sum(l) # 文法エラー
s = sum(l.q) # 弾け飛ぶ例外

# どうしてくれんのこれ
# オアァァァアァァアァーーーーーー♥

Maybe バインディングで書くことはできますがやっぱり煩雑です。

l = just([1, 2, 3, ])

for l0 in l:
    s = just(sum(l0))
if not l:
    s = nothing

これを Maybe オブジェクト側から何とかするために、mapメソッドが存在しています。 mapメソッドを使用すると以下のように書けます。

l = just([1, 2, 3, ])

s = l.map(sum)

mapメソッドに渡された関数は結果をjustでくるんで返します。 値が欠落していれば当然nothingが返ります。 関数にはラムダ式も使えるので、以下のようなこともできます。

a = just(42)
print(a.map(lambda x: x / 2))
Just 21.0

mapメソッドに関しては語るべきことはあまりありません (とくにモナドをご存じの方にとっては)。

bindメソッド (from flatMap)

さっき作ったsafe_int関数を思い出してください。 strを引数にとってMaybe[int]を返すやつです。 mapを使ってこれをMaybe[str]に適用してみましょう。

s1 = just('12')
s2 = just('hogehoge')
s3 = nothing

print(s1.map(safe_int))
print(s2.map(safe_int))
print(s3.map(safe_int))
Just Just 12
Just Nothing
Nothing

safe_intが Maybe を返し、さらにmapメソッドがjustでくるみ直すので入れ子になってしまいました。 たぶんこのプログラムを書いた人はこの結末は望んでいなかったでしょう (入れ子に “できる” こと自体は表現力を高めます。 たとえば JSON のパースの結果、nullが入っていたならjust(nothing)、そもそもそのキーが存在していなかったならnothingと表現できたりするので)。

いままで秘密にしていましたがjoin関数を使うと入れ子を 1 段潰すことができます。 自然変換 μ ってやつです。

from maybe import join

print(join(s1.map(safe_int)))
print(join(s2.map(safe_int)))
print(join(s3.map(safe_int)))
Just 12
Nothing
Nothing

そして、なにより望まれているのは、“mapしてjoin” をひとつにすることです。 bindメソッドがあればそれが使えます。

print(s1.bind(safe_int))
print(s2.bind(safe_int))
print(s3.bind(safe_int))
Just 12
Nothing
Nothing

Haskell を使いこなす変態方にはmapメソッドが<$>演算子でbindメソッドが>>=演算子といえばすぐに分かるでしょう。 裸の値を返す関数にはmap、Maybe を返す関数にはbindと覚えるといいでしょう。

do関数 (from do記法)

mapbindは当然ひとつの Maybe オブジェクトから呼び出すので、2 引数以上をとる関数に使うのには少々骨が折れます。

lhs = just(6)
rhs = just(9)

ans = lhs.bind(lambda x: rhs.map(lambda y: x * y))
print(ans)
Just 54

ちなみに内側がmapなのはx * yで裸の値を返す関数のため、外側がbindなのはmapメソッドの返り値が Maybe だからといえます。 覚えていますか?

do関数を使用するとこれを簡単に書けます。

from maybe import just, nothing, do

lhs = just(6)
rhs = just(9)

ans = do(lhs, rhs)(lambda x, y: x * y)
print(ans)
Just 54

do関数は引数がすべて値を持つときだけ中身を取り出して関数を実行します。 ひとつでも値が欠落しているときはnothingを返します。 doという名前の由来は Haskell ですが、アルゴリズムは極めて反モナド的です。 許し亭ゆるして。

ちなみにdo関数の引数は Maybe のみしか取ることができません。 一部の引数として裸の値を使用したいときはjustでくるみましょう。

そのほかこまごましたこと

コンテナとしての Maybe

Maybe はコンテナでもあり、len関数とin演算子が使用できます。

len関数はSomethingは常に1を、Nothingは常に0を返します。

a = just(42)
b = nothing
a2 = just(a)
b2 = just(b)

print(len(a))
print(len(b))
print(len(a2))
print(len(b2))
1
0
1
1

in演算子は、右辺の中身が左辺に等しいとき、Trueを返します。 それ以外のときはFalseを返します。 Nothingは常にFalseを返します。

print(42 in a)
print(42 in b)
print(42 in a2)
print(42 in b2)
True
False
False
False

さきほど述べたように、Maybe はイテレート可能です。 そのため、たとえばリストへの変換が可能です。 @koher 氏は以下のように述べましたが、まさにその通りの結果になります。

Optional は中身が空かもしれない箱でした。別の見方をすれば、 Optional を最大で一つしか要素を入れられない Array と考えることができます。

   — SwiftのOptional型を極める

al = list(a)
bl = list(b)

print(al)
print(bl)
[42]
[]

この辺の仕様を使う機会があるのかは謎です。

ネイティブの世界とつながる

Maybe には一対になった関数とメソッド、maybe_fromto_nativeがあります。 関数とメソッドですが一対です。 これらは Python のネイティブな型と Maybe をつなげる働きをします。

maybe_fromは、ネイティブな値をとって Maybe を返します。 justとの違いは、Noneが与えられたときにjust(None)ではなくnothingが返ること、Maybe が与えられたときにそのまま返すことです。 この関数は返り値の欠損をNoneで示す既存の関数やメソッドの返り値を、まとめて Maybe という共通のコンテキストで利用するために使用できます。

# 辞書になかったとき価格の代わりに None を返す既存の関数
def get_price(d, name, num, tax_rate=0.1):
    if name in d:
        return -(-d[name] * num * (1 + tax_rate)) // 1
    else:
        return None

# ...

from maybe import maybe_from
p = get_price(unit_prices, 'apple', 5) # int が来るかもしれないし None が来るかもしれない
p = maybe_from(p) # p は unit_prices に 'apple' があるかどうかに関わらず Maybe[int]

to_nativeメソッドは逆に、Maybe を ネイティブな値にします (ただし、ネイティブなといっても中身がそもそも何らかのライブラリで用意されているオブジェクトの場合は単にそれが出ます)。 .iと違い、nothingならNoneを返します (だからといって “安全な” アンラップ演算子として濫用してはいけません) し、ネストしていても再帰的に掘っていき必ず裸の値を返します。 mapは引数が欠落しているとき全体を欠落とみなしますが、こちらは値がないという意味としてNoneを与えることを期待する関数の引数に使用できます。 また、ネイティブな値が返ることを利用して JSON のシリアライズ方式として指定できます (ただしネイティブなといっても略)。

import json

from maybe import Maybe

def json_serial(obj):
    if isinstance(obj, Maybe):
        return obj.to_native()
    raise TypeError ("Type {type(obj)} not serializable".format(type(obj)))

item = {
    'a': just(42),
    'b': nothing
}

jsonstr = json.dumps(item, default=json_serial)
print(jsonstr)
{"a": 42, "b": null}

PyMaybe との違いとか “ただ面倒なだけじゃん?” に対する応答とか

Python で Maybe というと、既存のライブラリとして “PyMaybe” というものがあります。 PEP 505 でも言及されている有名所さんのようです。

こちらの大きな特徴は、Maybe をまるで裸の値のように取り回せるというところです。 具体的には README.rst にある以下のコードが参考になるでしょう。 maybeはこちらでいうところのmaybe_fromor_elseはこちらでいうところの>>演算子にあたると思ってください。

>>> maybe('VALUE').lower()
'value'

>>> maybe(None).invalid().method().or_else('unknwon')
'unknwon'

Maybe でくるんである値に対して、裸の値に対するメソッドをコールしています。 また Null に対するメソッドコールは無視されて Null が伝播しているのがわかります。 私の Maybe のように一旦取り出してとか、.q.とかする必要がありません。 四則演算などの Dunder メソッドも実装されているようです。

なんだ、こちらのほうが便利じゃないか。 余計なエラーも出ないし……とお思いの方も多いと思います。

しかし、私がそのような仕様で Maybe を実装したのには理由があります。 それは、まさに “エラーを発生させるため” なのです。

Maybe[T]Tの違いを正しく意識しないとすぐエラーが出るというのは、逆に言えばその違いを意識する手助けになります。 混同するとすぐエラーが出るし、文字列に変換すると余計なJustという接頭辞がつきます。 それは、“正しく書かないと正しく動かない” ということを保証するためのものです。 間違えていたらすぐ気づくのです (参照: マジレスすると『Optional(2018)年』を恐れる必要はない)。 これが Maybe を直接呼び出してほしくない理由でもあります (違いを隠蔽してしまうので。 ホントは文字列変換も消してしまいたいけど……)。

Null 安全とは、ぬるぽを起こさない仕組みであると一般的には理解されますし、究極的な目的がそこであることは間違いないでしょう (と、いきなり Null 安全について語り始めましたが、私の Maybe の型ヒントはダメダメなので Null 安全のために利用することは事実上不可能です)。 しかし、本当に Null 安全に必要なものは “Nullable と Non-null が区別されること” です。 なぜ Java がだめだったのでしょう? それはあらゆる参照型にnullを代入でき、逆の立場から言えばどんな参照型の値もnullである可能性があることでした。 Tが Null かもしれないということは、つまりTと書いているのにTとして使えないことを意味します。 すべきです。 そこへきて Nullable と Non-null の垣根をさらに取り払うことは—TでないものをTのように扱えるようにするということは—むしろ、危険なことなのです。

Objective-Cには、 nil に対するメソッドコールはエラーにならずに無視されるという悪しき仕様があります。

(中略)

しかし、この便利さと引き換えにObjective-Cは恐ろしいリスクも背負っています。本来、 nil のチェックをしなければならない箇所で nil チェックを忘れてしまったけれども、たまたまうまく動いてしまうことがあります。そしてある日、特定の条件が重なったり、ちょっとした修正を行ったときにそのバグが顕在化するのです。

   — 今すぐObjective-CをやめてSwiftを使おう

Objective-C の nil の扱いは一見安全なように見えて、問題の発覚を遅らせ、解決をより難しくしているだけです。潜在バグを生みやすいという点で、僕は Objective-C 方式は最悪だと考えています。かつて、 Objective-C で書いたプログラムを Java に移植したときに、 Java で NullPointerException が発生して初めて Objective-C 側の nil ハンドリングが不適切だったことに気付くということが何度もありました。

   — null安全でない言語は、もはやレガシー言語だ

もちろん、それが Null 安全のために生産性を落とさなければいけないというわけではありません。 Nullable な変数に対する Null チェックは “かつての言語だって” 必要なことでしたし7、この Maybe ではむしろそれを簡略化するための様々な方法を実装しました。 もし Null チェックなしにぶっつけ本番したければ、そのための Unsafe な操作さえあります。 この辺は数年前に語り尽くされた話ではありますが。

とはいえ、Python を好んで使用する方はそういったガバガバ性は百も承知でとにかく “書きたいことを素直に書ける” ことを重んじているとは私も思います。 ですので、PyMaybe の思想が間違っているとは口が裂けても言えませんっつーか私こそ世間に反逆するMaybe[T]erroristであると言えます。 ですが、まあ半分お遊びというか、自分が書きたいものを書ききったということでどうかお目こぼしください。


  1. 一般的に Null Coalescing Operator の定訳は “Null 合体演算子” ですが、“合体” だと “欠けた部分を埋める” というニュアンスに乏しいので、“傷口がふさがる” という意味の “癒合” を使用しています。 

  2. https://docs.python.org/ja/3/reference/expressions.html#operator-precedence 

  3. https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/ 

  4. https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations 

  5. https://kotlinlang.org/docs/reference/grammar.html#expressions 

  6. https://www.python.org/dev/peps/pep-0505/#the-coalesce-rule 

  7. こんな一個人製の Maybe では非現実的な話ですが、もしもnothingのみを使い、組み込みのNoneを使用しないように (発生しうるときはすぐにmaybe_fromでくるむように) できれば、裸の値には一切 Null チェックをする必要がなくなります。 実はこれが Null 安全な言語の最も大きな利点で、言語仕様にこのような仕組みが組み込まれている Null 安全な言語は “必要な Null チェック” さえすればよくなります。 一般的な “Null 安全な言語では Null チェックを強制される” という認識は正しくはあるのですが、感覚としては真逆です。 参考: 【アンチパターン】全部nil(null)かもしれない症候群 

4
4
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
4
4