Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

「値が無い」ということをどう表現するか、あるいはOption型について

はじめに

 現在、「イケている」と呼ばれる関数型言語について、大抵の場合は「Option型」と呼ばれる型が実装されていたりします。具体的には、OCamlとScalaであり、HaskellにもMonadの一つとしてMaybe型というものがあります。

 この「Option型」とは何か、といえば、要するに「値があるかどうかわからない型」という型であり、値として「値を持っていない」か、「値を持っている」という二つのパターンをとります。そして、「値を持っている」という状態のときだけその値に対してアクセスすることができます。

 とはいえ、これだけだと何が嬉しいのかさっぱりわかりませんので、その辺りについて、少しだけメモしておきます。

「値が無い」ということを表現すること

 素朴に考えて、「何らかの値が無い」ということを示すためには、二つの方法で表現することが出来ます。

  1. 値が無い場合は、None(Pythonでいうところの「値」がないという表現)を返す
  2. 値が無い場合は、「値が無い」という例外を投げる

 それぞれ二つのパターンについて検証してみます

値が無いときはNoneを返すとする場合

 まず、最初の「None」を返すというのは素朴であると思うのですが、しかし、これは「ある関数の結果としてNoneを返している」のか、それとも「値の欠落としてNone」が存在しているのか、というのがわかりにくくなる欠点というものがあります。そこで簡単にPythonのスクリプトを書いてみましょう。

def return_none():
    return None


def not_return():
    pass


if __name__ == '__main__':
    print return_none() is not_return() # True

 この時、「関数が結果を返し忘れた結果としてのNone」と、「明示的にNoneをreturnした関数」は、少なくともPythonの場合は等価のものとして扱われるが、これは少々具合が悪いように感じます。というのも、「値としてのNone」と、「値が欠落していることの表現としてのNone」は明らかに違うものだから。この問題は、実は既にリレーショナルデータベース(具体的にはMySQLやPostgreSQLで)混乱を引き起こしている。興味がある人はNullのはなし(up用)のスライドを見ると詳しい。

 もちろん、Pythonのように、あらゆる「欠落」を「None」として表現するから問題なのではないかという考え方も出来ます。例えば、JavaScriptにはundefinedとnullという二つの値が存在しています。このことにより、"とりあえずは"Pythonのような「関数が結果を返し忘れた結果としてのNone」と「明示的なnullをreturnする」というのは区別されるようには見えます。

function notreturn() {
}

function nullreturn() {
    return null;
}

var foo = notreturn();
var bar = nullreturn():

 この場合であるならば、「値が欠落したものとしてのundefined」と、「値が無いことを示すためのnull」として分けることが出来ました。とりあえず、Pythonみたいに、「値が欠落しているのか」、それとも「明示的に「何も無い」という値を返しているのか」という区別はすることが出来るようになりました。JavaScriptのundefinedが変数であり、書き換え可能であるということを除けば、ですね。

 ただ、null, undefinedの場合も少し弱いと言えば弱いのです。確かに、全てに対してNoneを返すよりはよっぽどいいとは思うのですが、このnullにはとんでもない性質がある。具体的にはJavaScriptの動かないコード (中級編) nullが0以上0以下と認識されてしまう - 主に言語とシステム開発に関してを参照するとわかりやすいかもしれない。

 このように、実は「値が欠落したもの」と「値が無いものとして返す」ということを値自体で考えることは、案外難しい。たぶん、JavaScript的には、undefinedとnullで分けるという方法が一番穏便かもしれないが、やはり、その値が「もしかしたら値が入っていたかもしれないけれども、たまたま入っていなかったもの」というような情報を付加しているとは言いがたいという側面もある。

値が無いときはエラーを発生させる

 例えば、djangoのORMでデータベースにアクセスする場合は、下のようになる。

AnythingModel.objects.get(pk=1)

 getを使った場合は、データベースに該当するレコードが存在しないか、あるいは複数ある場合にエラーが起きる。というのも、getを使う場合は、一つのレコードを習得するという意図がある場合に使われるという前提があるからだ。そこで、話をわかりやすくするために、今回はget methodであるレコードを取得しようとして無かった場合として考えてみよう。

from django.db import models


class AnythingEmailModel(models.Model):
    email = models.EmailField(unique=True)


def get_object(email):
    try:
        email = AnythingEmailModel.objects.get(email=email)
    except AnythingModel.DoesNotExist:
        email = AnythingEmailModel.objects.create(
            email=email)
    finally:
        return email

 該当するレコードが存在しない場合は、エラーを吐き出すかどうかというのは、それこそフレームワークの作者の思想によるものであるとしか言いようがない部分があるかと思われます。djangoの場合は、レコードが存在しないときは、エラーを吐き出し、注意を促すことで、「存在しなかったときの処理」というものに注意を向けさせるという側面があるように見えます。

 しかし、この方法は、自分が見る限り、少し「暴力的」であるようにも感じます。確かに、「あるレコードは存在している筈だ。しかし存在していなかった。だからこれは例外である」という考え方も出来るのですが、しかし値がとりだせないが故に「例外」というのも変な話ではありますし、この場合の例外は「もしかしたら存在していないかもしれない」という可能性の表現なので、どちらかといえば「存在しているかもしれないし、存在していないかもしれない」という形で抽象化するほうが好きではあります。

第三の道・Option型による表現

 さて、二つの方法を検証してみました。

 まず最初の問題は何かと言えば、「値のみで、『それが返り値か、それとも値の欠落か』を表現することは難しい」ということでした。もう一つは、「例外処理は一つの方法であり、開発者に対して『その値が存在しないときはどうするのか』を表現するのにはいいが、しかしもっとスマートに『その結果はもしかしたら値が無いかも知れないよ」ということを表現できないか、ということでした。

 そこで出てきたのがOption型です。

 OCaml及びScalaに出てくるOption型なのですが、この型の表現は、要するに「この型は、値が入っているときもありますが、値が入っていないときもあります」という宣言のための型です。具体的な実装としては、値としてNothingとSome(x)がある場合が多いです。Nothingは文字通り、「値が無い」ということであり、Some(x)は、値を「Some」という形でWrapしているということです。(このあたりの実装については各言語によって違うと思われます)

 しかし、なんでこんな面倒くさい過程を取るのでしょうか。別に「None or 値」でいいじゃないか、という真当な考えが出てくると思いますが、先ほども述べたように、まず一つに、その値が「正常な処理をした結果、値が無かった」ということを明示することができるという側面があります。

 また、JVM上で動くScalaの場合、忌まわしき「NullPointerException」から開放されるという役割もあるそうです(実はあまりJavaをやったことがないので、その辺についてはあまり詳しくないのですが……)

 もう一つ、参考になる情報があります。『ふつうのHaskellプログラミング』には、次のようなことが書いてあります。

Maybe(NothingかSome(x)のどちらかを示す型)はそれ単独でもそれなりに便利なのですが、なによりモナドとして使ったときに真価を発揮します。

MaybeモナドはMaybeを返す関数を何度も連続して使わなければいけないときに遣うと便利です。さきほどのlookup関数を使って言えば、lookup関数で検索した結果に対してさらにlookup関数を使いたいときです。

Maybeモナドとして遣うと、Maybeを返す関数を連続して、すべての関数が(Just x)を返したときにだけ、(Just x)を返す関数が書けます。言い換ええると、同数を連続して適用しているうちにどこかでNothingになったら、そのあとの処理をとりやめにできるのです。

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

 どういうことかといえば、f(x) -> g(y) -> h(z) という処理があった場合、あるいはf(x) -> f(x) -> f(x) といった再帰を行う場合に、途中で失敗した場合に、一回処理をpendingしておいて、最後にNothingの場合において、処理をどのようにするのかということを決定するといった作りにすることが出来ます。こうすることによって、関数は、例外のことを考えず、自分の処理だけに集中できるというメリットが生まれます。

 で、この辺りの疑似挙動をPythonで書いていたのですが、長くなってしまったので、Gistにあげておきますので、下のURLを参照してください。

まとめ

 最後に「値が無い」という表現について、下のように簡単にまとめられるでしょう。

  • 「値が無い」ということを、値で示す場合、「欠落値と結果としての値を区別する必要があるか」を調べる
  • 「値が無い」ときにエラーを発生させる場合、その値が存在しないときにシステムに影響が出る場合が望ましい(無闇にエラーを吐き出さない)
  • 「値が無い」ときに、ScalaやOCamlのような、Option型の何らかのラッパーを用意すると、連続した関数の処理を書くときにクリアーになる

 という感じでしょう。もちろん、Option型にも欠点があるとはおもうのですが、とりあえず自分の宿題として残しておこうと思います。

参考