にほんごのれんしゅう

日本語として伝えるための訓練を兼ねたテクログ

C++のscalaz的kleisliの実装を試みる part1

最近,scalazの機能をC++になんとか輸入できないか,と日々悶々としています.

 悶々...scalaのあの便利機能ぶりが羨ましいのですよ

scalazの機能とは

たぶんhaskellから奪った機能群.scala特有の機能でポイントフリーに書いているのを見るとほんと,便利なんだなぁと思う.
operator >==>とかC++にないし,定義できないし...

kleisliとは

関数合成のこと.関数二つ以上を引数にとり,合成した関数が戻り値になるようにすること
scalazで書くと下記のようになる.
下記は「Scalaとか...」から引用させていただきました.

// 面倒なので内部処理省略。とにかくxもyも何か失敗するかもしれない処理  
val x: Int => Option[Int] = ???
val y: Int => Option[Int] = ???

//もしScala標準ライブラリのみで書く場合
val z = f.andThen(_.flatMap(x).flatMap(y))

import scalaz._, std.option._
// ScalazのKleisli使って書く場合
val z = Kleisli(f) >==> x >==> y //<-これを可能な限り再現する

 Kleisliという引数に関数合成の演算子を>==> x >==> yを用いて zという関数を構築した,と見れる.
とりあえず,C++で構築した例
 下記のコードでとりあえず関数の合成は可能になる.
残念なことに一つの型の処理する関数しか今のところkleisliでバインドできない.ほかの型に射影することこそが,kleisliの本義なのだから不十分な機能であろう.これは折をみて改善していく方針である.確かTMPでFIFOを作れたはずなので,できないはずはないのだ.

 どうでもいいが、最近,著者の体力を著しく奪う案件があるので,まともな思考ができない.カユウマとか言ってしまいそうだ.

namespace KOKUU2{
  class FuncWrapBase{
    public:
    FuncWrapBase(){};
    virtual int64_t FuncCall(int64_t in) = 0;
    virtual ~FuncWrapBase(){};
  };
  template<class Functor, class RESTYPE>
  class FuncWrap: public FuncWrapBase{
    public:
    Functor t_;
    FuncWrap(Functor t):t_(t){
    }
    int64_t FuncCall(int64_t in){
      return
        reinterpret_cast<int64_t>(
            t_(reinterpret_cast<RESTYPE>(in)));
    };
  };
  template<class RESTYPE>
  class Kleisli{
    std::vector<FuncWrapBase*> _flist;
    public:
    /** コンストラクタにtemplate-lambda引数食えない*/
    Kleisli(){};
    template<class F>
    Kleisli& map(F functor){
      //TODO デストラクタで全部のfunctorをデストロイする
      auto func = new FuncWrap<F, RESTYPE>(functor);
      _flist.push_back(func);
      return *this;
    }
    /** generic化の必要性 */
    template<class TYPE>
    void operator()(TYPE&& in){
      /** モナドのように動作することを期待するため,とりあえず送る*/
      //これスタック何ですよね,,,
      TYPE* state;
      for(std::vector<FuncWrapBase*>::iterator it = _flist.begin();
          it != _flist.end();
          ++it){
        if( it == _flist.begin() )
          state = reinterpret_cast<TYPE*>(
              (*it)->FuncCall(reinterpret_cast<int64_t>(std::move(&in))));
        else
          state = reinterpret_cast<TYPE*>(
              (*it)->FuncCall(reinterpret_cast<int64_t>(std::move(state))));
      };
    }
  };
};//endnamespace
int main(int argc, char const *argv[])
{
  //クライスリの圏のテスト
  auto hageMonad = [&](int64_t* x){std::cout << "1st:" << (*x) * (*x) << std::endl;
        return x;};
  auto sonMonad = [&](int64_t* x){std::cout << "2st:" << *x << std::endl;
       *x = 10 * (*x); return x;};
  auto masaMonad = [&](int64_t* x){std::cout << "3rd:" << (*x)/2 << std::endl; return x;};
  auto kl = KOKUU2::Kleisli<int64_t*>().map(hageMonad).map(sonMonad).map(masaMonad);
  kl(int64_t(3));

  auto hage1Monad = [&](std::string* x){std::cout << "1st:" << *x << std::endl;
        *x+=*x;return x;};
  auto hage2Monad = [&](std::string* x){std::cout << "2st:" << *x + "hage" << std::endl;
        *x += "hage"; return x;};
  auto hage3Monad = [&](std::string* x){std::cout << "3rd:" << *x + "chirakashite" << std::endl; *x += "chirakashite"; return x;};
  auto kl2 = KOKUU2::Kleisli<std::string*>()
              .map(hage1Monad).map(hage2Monad).map(hage3Monad);
  kl2(std::string("hageta!!"));
  return 0;
}

 下記が実行結果.型を横断しなければ動いていることが確認できるはずである.

gkobayas@ubuntu:~/dev/YUTORI$ ./a.o
1st:9
2st:3
3rd:15
1st:hageta!!
2st:hageta!!hageta!!hage
3rd:hageta!!hageta!!hagechirakashite

とりあえず,明日以降はdecltype, declvalで型に柔軟性を持たせることを目標とする.