HaskellをC++で書いてみる(getLineとputStrLineとIO型とMonad型クラス)

はじめに

また性懲りもなく
今回は以前にもまして穴だらけですが
いつか理解できるまでの手前用のメモとして

IO型

(いつかリベンジしたい)
Haskellでは以下のように定義されています

--data IO a *->*
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

IO型は(GHCでは)newtypeで定義されています
しかし、私はIOの定義を理解できていません
今回は関数型(function型)を内部に保持する型として再現を試みましたが
これから勉強をして、この定義を理解したときにリベンジしたいと思います
(あ、絶対にしないやつだわ、これ)

C++では以下のようにしてみました

template<typename T>
struct IO
{
    std::function<T()> f_;

    IO(std::function<T()>const& f):f_(f){}
    IO(std::function<T()>&&     f):f_(f){}
    ~IO()=default;
    
    T operator*( )
    { return f_(); }
};

main(IO型)

-- main
main :: IO ()

実はmainは少しだけ他のIO()型とは異なるようで
mainの変数に束縛?(=)したIO()型の変数は実行時に評価されるようです
また(>>)演算子を使う事で、以前のIO()型変数を棄てて新しいIO()型変数を
再代入できるので、複数のIO()型変数を評価する事も可能なようです

私の頭ではIO()型とはぜんぜん別物ではないのか?となりました
というか、それ以外の解が思いつきませんでした・・・
ということでC++では別の型を作りました
(これもいつかリベンジしたいです)

// Main :: IO ()
struct Main : IO<void>
{
    static void run(IO<void>>const& io)
    { 
        a.f_();
    }
    Main():IO<void>([]{}){}
    Main& operator=(IO<void>&& i){ run(std::list<IO_>(l)); } 
    Main(std::initializer_list<IO_>&& l):IO<void>([]()->void{}){ run(std::list<IO_>(l)); 
};

ようは代入演算子で受取ったIO()型変数を
実行するだけの型です

getLine関数

-- getLine
getLine :: IO String

標準入力(コンソール)から値を貰い、文字列に変換します std::cinをラップしただけでいけますね

// (stdio) -> IO String 
auto getLine()
{
   return IO<std::string>( []{ std::string a; std::cin >> a; return std::to_string(a); } );
}

lambdaの中が結構冗長な感じになりました

putStrLn関数

-- putStrLn
putStrLn :: String -> IO ()

文字列を標準出力する関数です
std::coutをラップしただけでいけますね

// String -> IO ()
auto putStrLn( std::string const& a )
{
    return IO<void>( [&]{ std::cout << a; } );
}
auto putStrLn( std::string && a )
{
   return IO<void>( [a=std::move(a)]{ std::cout << a; } );
}

一応(無駄に)左辺値参照版と右辺値参照版を作りました

Monad型クラス

これだけでも標準入力HalloWorldできるんですが
ついでにMonad型クラスも作って継承させましょう

Monad型クラスでは(>>=)演算子return関数を定義してやる必要があります どちらも名前だけ作って実装は省略します (継承先で再実装するので)

class Monad m where
  return :: a -> m a
  (>>=)  :: m a -> ( a -> m b ) -> m b
--
  (>>)   :: m a -> m b -> m b
  fail   :: String -> m a

またMonadの型クラスになれる型は多相型です 多相型でなかった場合に、型エラーがでるよう、 コンストラクタ=deleteして 多相型用の部分特殊化を用意します 本来は右辺の中身を左辺に移す(>>)演算子や文字列を受取ってM<A>を返すfail関数が必要なのですが 今回は・・・その・・・面倒・・・申し訳なく・・・

template<typename Derived>
class Monad
{ Monad()=delete; };

template<template<typename...>class D,typename A>
class Monad<D<A>>
{
   template<typename B,typename C>
   friend auto operator>>=( Monad<D<A>> const& a, std::function<Monad<D<B>>(C)> const& f ) -> D<B>;
    
   template<typename B>
   friend auto return_( B const& a ) -> D<A>;
    
   //template<typename B>
   //friend auto operator>>( Monad<D<A>> const& a, Monad<D<B>> const& b) -> Monad<D<B>>
   //{
   //   return b;
   //}
};

次に継承先での実装(instance)を書きます

template<typename A>
struct IO : Monad< IO<A> >
{
    std::function<A()> f_;
    IO(std::function<A()>const& f):f_(f){}
    IO(std::function<A()>&&     f):f_(f){}
    ~IO()=default;
    
    A operator+( )
    { return f_(); }

    // instance  Monad A where
    template<typename B>
    friend auto operator>>=( IO&& a, std::function<IO<B>(A)>&& f ) -> IO<B>
    {
        return f( a.f_() );
    }   
    friend auto return_( A const& a ) -> IO<A>
    {
        return IO<A>( [&]{return a;} );
    }
};

使い方

int main()
{
   Main = getLine >>= putStrLine >> getLine >>= putStrLine
}

これで動いてくれたらさぞ面白かったですが 演算子の優先度を見ると 以下のようになっております

int main()
{
   ( Main = getLine ) >>= ( putStrLine >> getLine ) >>= putStrLine
}

(>>=)演算子を()で包んでもまだ以下のようになります

int main()
{
   Main = ( ( getLine >>= putStrLine ) >> ( getLine >>= putStrLine ) )
}

と言うわけで、考え直さないといけません

Mainの再現について

Mainなのですが、私は結局以下のようにしました

{
    static void run(std::list<IO_>const& l)
    { 
        for(auto& a : l) a.f_();
    }

    Main(std::initializer_list<IO_>&& l):IO<void>([]()->void{}){ run(std::list<IO_>(l)); }
};

ようはstd::initializer_listで複数のIO()を受取れるようになっています つまり、(>>)演算を使用したMain()への再代入を棄てて、 処理したいIO()型リストを受取とって順に実行するようにもういろいろと妥協しました

と言うわけで、今回のIOアクションは以下のように書きます

int main()
{
   Main = {
      getLine >>= putStrLine,
      getLine >>= putStrLine
   };
}


だいぶ良さそうになりました

だいぶ良さそうになったのですが
もちろんまだ失敗します(おい)

(後で書く)
http://melpon.org/wandbox/permlink/NHmKath7VR2z8B4r