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で型に柔軟性を持たせることを目標とする.