C++ で Haskell の Either っぽい何かを作ってみた。


動機: boost::optionalだけじゃなくboost::eitherがほしい - Faith and Brave - C++で遊ぼう


Boost.Optional は C++ でも屈指の便利ライブラリですが、
「戻り値として使う」場合には、時々「失敗した」という状況の他に、失敗した状況を示すような値が返せると嬉しい場合があります。


つまり、関数本来の戻り値の代わりに、それとは別の型でエラー情報を返すことも出来る、
要するに Haskell で言うところの Either a b があるといい、ということです。
これを C++ で実現するためには、一見して Boost の Variant を使うことで

boost::variant<string, double> sqrt_either( double x )
{
  if( x < 0 ){ return "negative number!"; }
  
  return std::sqrt(x);
}

とかやればいいように思えますが、これだと、
boost::variant が作れない、という、どうしようもない弱点があります。


そこで、同じ型であっても格納出来るように改良した boost::variant、
すなわち either があるといいな、と思って、ちょっと作ってみました。
こんな感じで使います:

namespace gintenlib
{
  template<typename Left, typename Right>
  struct either;
  
  // 他にも left とか right とか apply_visitor とか apply_either とか
}

#include <iostream>

struct print_t
{
  typedef void result_type;
  
  template<typename T>
  void operator()( T const& x ) const
  {
    std::cout << x << std::endl;
  }
  
} print;
 
// エラー表示関数
void error( std::string const& what )
{
  std::cout << "Error: " << what << std::endl;
}
 
int main()
{
  using std::string;
  
  // Left と Right が違う場合
  // 構築は値から直接行える
  {
    typedef gintenlib::either<string, int> either_type;
    either_type const x = 1, y = "hoge";
    
    // Visitor の適用
    // Boost.Variant と同じように使える
    either_type z;
    apply_visitor( print, z );
    
    // apply_either は Haskell の either 関数相当
    // either の中身が Left  なら第一引数を、
    // either の中身が Right なら第ニ引数を、
    // 第三引数に適応する。
    z = x;
    apply_either( print, print, z );
    
    z = y;
    apply_visitor( print, y );
  }
  
  // left と right が同じ/相互変換可能な場合
  // left() と right() で明示的に指定する
  {
    typedef gintenlib::either<string, string> either_type;
    using gintenlib::left;
    using gintenlib::right;
    
    either_type const x = left("fail"), y = right("OK.");
    
    either_type z = x;
    
    // 今度は手で分岐してみる
    // このように if の条件部で right() や left() の戻り値を受ければ
    // 安全に分岐処理を行える(ただし参照なので一時変数を使った場合は注意が必要)
    if( boost::optional<string&> const r = z.right() )
    {
      std::cout << "right: " << *r << std::endl;
    }
    else if( boost::optional<string&> const l = z.left() )
    {
      std::cout << "left: " << *l << std::endl;
    }
    
    // でも基本的には apply_either を使った方がいい
    z = y;
    apply_either( &error, print, z );
    
    // 部分適用もできたりする
    // ( ADL の関係で名前空間修飾が必要)
    gintenlib::apply_either( &error, print )( x );
  }
}

実装は http://ideone.com/qURcu を御覧下さい。
やってることは単純に、Boost.Variant に持たせる型をラップして、区別出来るようにしているだけです。
ただ、それだけだと楽しくないので、 Left と Right が無関係の型の場合は、
ラップしていない生の値から暗黙変換が出来るように、 SFINAE を使って切り分けています。