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
};
}
だいぶ良さそうになりました
だいぶ良さそうになったのですが
もちろんまだ失敗します(おい)