読者です 読者をやめる 読者になる 読者になる

ScalaのOptionクラスをPHPに移植した

<?php

interface Option
{
    /**
     * Someであったらfalse、Noneだったらtrueを返す
     * @return bool
     */
    function isEmpty();
    
    /**
     * Someであったらtrue、Noneだったらfalseを返す
     * @return bool
     */
    function isDefined();
    
    /**
     * Someであったら値を返す。Noneであった場合nullを返す
     * @param ? 
     * @return ?
     */
    function get($default_val = null);
    
    /**
     * Someである場合に内部の値にコールバックが適用され、その結果がfalseならばNoneが返り、そうでなければSomeが返る
     * @param callable $callback 一つの引数を取って真偽値を返さなければならない
     * @return Option
     */
    function filter($callback);
    
    /**
     * Noneである場合にコールバックが呼ばれ、その結果が返される。
     * Someである場合は自身が返る。
     * @param callable $callback Optionを返さなければならない
     * @return Option
     */
    function orElse($callback);
    
    /**
     * Someだった場合は、$callbackは無視され値が返される。
     * Noneだった場合はコールバックが実行され、その結果が返される。
     */
    function getOrElse($callback);
    
    /**
     * Someである場合、内部の値をコールバックに適用し、その返り値を内部の値とする
     * @param callable $callback 一つの引数を取ってなんらかを返さなければならない
     * @return Option
     */
    function map($callback);
    
    /**
     * Someである場合、$callbackに値を渡して実行する。
     * @return Option
     */
    function each($callback);
}

final class Some implements Option
{
    protected $val;
    
    function __construct($val)
    {
        $this->val = $val;
    }
    
    function isDefined()
    {
        return true;
    }
    
    function isEmpty()
    {
        return false;
    }
    
    function get($_ = null)
    {
        return $this->val;
    }
    
    function filter($callback)
    {
        return call_user_func($callback, $this->val) ? $this : None::it() ;
    }
    
    function orElse($callback)
    {
        return $this;
    }
    
    function map($callback)
    {
        return new self(call_user_func($callback, $this->val));
    }
    
    function getOrElse($_)
    {
        return $this->val;
    }
    
    function each($callback)
    {
        call_user_func($callback, $this->val);
        return $this;
    }
}

final class None implements Option
{
    private function __construct() {} 
    
    function isEmpty()
    {
        return true;
    }
    
    function isDefined()
    {
        return false;
    }
    
    function get($default_val = null) {
        return $default_val;
    }
    
    function filter($callback)
    {
        return $this;
    }
    
    function orElse($callback)
    {
        $result = call_user_func($callback);
        if (!$result instanceof Option) throw new Exception('$callback must return instance of Option');
        return $result;
    }
    
    function map($callback)
    {
        return $this;
    }
    
    function getOrElse($callback)
    {
        return call_user_func($callback);
    }
    
    function each($callback)
    {
        return $this;
    }
    
    static function it()
    {
        static $obj = null;
        return $obj ? $obj : $obj = new self;
    }
}

Optionクラスは一言でいうと、何らかの値を返すかもしれない計算を表現するクラスである。
何言ってるかわからないと思うのでサンプルコードで説明する。

<?php
/**
 * @return Option
 */
function at(Array $arr, $key)
{
    return isset($arr[$key]) ? new Some($arr[$key]) : None::it();
}

上記のat関数は、配列の要素にアクセスする関数である。
配列$arrの中に$keyが存在すれば、Someインスタンスを返し、そうでなければNoneインスタンスを返す。つまりOption型を返す。
配列の要素にアクセスし、値が存在すればその値を、そうでなければ別の値を得る、という処理は以下のコードで表現できる。

<?php
$arr = array('foo' => 1, 'bar' => 2);

at($arr, 'foo')->get('hoge'); //=> 1
at($arr, 'foobar')->get('hoge'); //=> 'hoge'

要素が存在する場合のみに何らかの処理を行う場合は以下のようになる。

<?php
$arr = array('foo' => 1, 'bar' => 2);

at($arr, 'foo')->each(function($val) {
    // 処理
});

他にも上記のOption, Some, Noneのコードを見るといくつか機能があるので見てみるといいと思う。