C ++型クラス



型クラスなしでC ++でモナドを実装する方法についてはすでに説明しました 。 例としてモナドを使用して、型クラスを実装する方法を示したいと思います。

この手法はScalaで広く使用されていますが、C ++でも使用できます。 ライブラリの統一された記述の難しさの実例としてそれを簡単に説明し、今度はその実装を実証します。

型クラスはHaskellやMercurryのような宣言型言語で使用されるだけでなく、非常に古典的なGoとRustにも反映されることに注意してください。

この手法は、Common LispおよびClojureのマルチメソッドの実装にも適しています。



私はもう6年間C ++を取り上げていないので、コードはイデオロギーではなく、新しい(有用な)機能を使用していないかもしれません。

さらに、メモリ管理の問題を完全に無視します。C++実践者は私よりもうまくいくでしょう。

コードはgcc 4.7.3でテストされました。





型クラスの基本的な考え方は、インターフェイスの実装をデータ構造から分離することです。 同じインターフェイスを使用して、まったく異なるデータ構造を操作できます。また、データ型ごとに個別に実装する必要があります。 このインターフェイスを使用するコードは、その実装の詳細を知る必要はありません。

複雑性モナドでは、このインターフェイスが別の型によってパラメーター化されたジェネリック型に対して実装されることを追加します。



インターフェイスの実装はどこかに保存する必要がありますが、これにはクラスのみがあります。

#include <stddef.h> template <template<typename _> class M> class monadVTBL { public: template<typename v, typename x> static M<v> bind(M<x>, M<v>(*)(x)); template<typename v> static M<v> mreturn(v); }; template<template<typename _> class M, typename v, typename x> M<v> bind(M<x> i, M<v>(*f)(x), monadVTBL<M> *tbl = NULL) { return monadVTBL<M>::bind(i, f); } template<template<typename _> class M, typename v> M<v> mreturn(vi, monadVTBL<M> *tbl = NULL) { return monadVTBL<M>::mreturn(i); }
      
      





ご覧のとおり、実装は、デフォルト値を持つ追加のパラメーターとしてそれを使用する関数に渡されます。 この場合、必要なのはこのパラメーターの型のみなので(常にNULLです)、ローカル変数に転送できます。 このパラメーターを使用すると柔軟性が増し、多少の労力をかけてテンプレートをインスタンス化するためのメモリを節約できます(関数はvoid *を介して一般化された関数の実装を継承するクラスで非表示にする必要があります)。マルチメソッドの実装に役立ちます。



 template <template<typename _> class M> M<char> inc(char c) { return mreturn<M,char>(c+1); }
      
      





モナドを使用したかなり単純な関数。

ハスケルで、彼女は外を見る

 inc :: (Monad m) => Char -> m Char inc c = return (succ c)
      
      





ここに戻るとは、C ++とはまったく異なるものを意味します。



それでは、IOモナドの実装に移りましょう。 この場合、モナドインターフェイスが動作するオブジェクトはI / O操作です。 Haskellではこれらは通常の値であり、C ++ではクラスによってモデル化されます。

 #include<stdio.h> template<typename v> class IOop { public: virtual v run() = 0; }; template<typename v> class IOm { public: IOop<v> *op; IOm(IOop<v> *o) { op = o; } v run() { op->run(); } };
      
      





runメソッドはこの操作を実行します(Haskellでは、メインオブジェクトのランタイムシステムによって実際に呼び出されます)。

IOmコンテナクラスは、可変サイズの操作の種類を非表示にするために必要です。

ご覧のとおり、これらのクラスはモナドとは関係がなく、モナドについては何も知りません。 これは、インターフェイスを知る必要がある通常のクラスよりも型クラスの重要な利点です。



 class getChar: public IOop<char> { public: getChar() {} virtual char run() { return getchar(); } } _getChar[1]; IOm<char> getChar(_getChar); typedef class unit { } unit; unit Unit; class _putChar: public IOop<unit> { char v; public: _putChar(char c) { v = c; } virtual unit run() { putchar(v); return Unit; } }; class IOm<unit> putChar(char c) { IOm<unit> o(new _putChar(c)); return o; };
      
      





ただし、2つの特定のI / O操作は、標準入力から文字を取得し、その文字を標準出力に出力することです。 また、文字を出力する操作に変換する関数。



 template<typename v> class mconst: public IOop<v> { vr; public: mconst(vx) { r=x; } virtual v run() { return r; } };
      
      





このクラスは、単項演算「return」を実装します。この場合、常に定数を「導入」する入出力演算が作成されます。



 template<typename v, typename i> class mbind: public IOop<v> { IOop<i> *s; IOm<v> (*f)(i); public: v run() { return (*f)(s->run()).run(); } mbind(IOop<i> *x, IOm<v> (*g)(i)) { s=x; f=g; } };
      
      





そして、この操作 ">> ="は、モナドを新しいモナドのジェネレータにリンクします。



 template<> class monadVTBL<IOm> { public: template<typename v, typename i> static IOm<v> bind(IOm<i> x, IOm<v>(*f)(i)) { IOm<v> b(new mbind<v,i>(x.op,f)); return b; } template<typename v> static IOm<v> mreturn(vx) { IOm<v> r(new mconst<v>(x)); return r; } };
      
      





そして、ここで最も重要なことは、IO用のモナドの実装の特殊化です。



 template<typename i> IOm<unit> ignNL(iv) { return bind<IOm,unit,char>(mreturn<IOm,char>('\n'),putChar); }
      
      





これは、前のモナドの結果を無視して '\ n'を出力するIOジェネレーターです。

 ign :: a -> IO () ign _ = putChar '\n'
      
      





IO(およびパーサーなどの他のモナド)の場合、以前のモナドを無視することはかなり一般的な操作であり、そのための関数があります。

 (>>) :: (Monad m) => ma -> mb -> mb a >> b = a >>= \_ -> b
      
      







それでは、すべての仕組みを確認しましょう。

 bind<IOm,unit,unit>(bind<IOm,unit,char>(bind<IOm,char,char>(getChar,inc),putChar),ignNL<unit>).run();
      
      





標準入力から文字を読み取り、インクリメントし、印刷し、改行を印刷します。



そして今、モナドについてさらに詳しくない別のクラスのモナドインターフェイスを実装しています。



 #include<vector> template<typename v> class myvector: public std::vector<v> { };
      
      





残念ながら、std :: vectorテンプレートには2つのパラメーターがあります(2番目のパラメーターは割り当てポリシーを担当し、デフォルトで置き換えることができます)。 最新のgccでは、1つのパラメーターを持つテンプレートを待機しているテンプレートに渡すことは許可されていません(メモリが適切に機能する場合、以前はそのような厳密性はありませんでした)。 したがって、単純なラッパーを作成する必要があります。



 template<> class monadVTBL<myvector> { public: template<typename v, typename i> static myvector<v> bind(myvector<i> x, myvector<v>(*f)(i)){ myvector<v> e; for(typename myvector<i>::iterator it = x.begin(); it != x.end(); ++it) { myvector<v> c = f(*it); for(typename myvector<v>::iterator i = c.begin(); i != c.end(); ++i) { e.push_back(*i); } } return e; } template<typename v> static myvector<v> mreturn(vx) { myvector<v> e; e.push_back(x); return e; } };
      
      





std :: vectorモナドの機能は、HaskellのListモナドの機能に似ています。



仕組みを試してみます。



  myvector<char> x; x.push_back('q'); x.push_back('w'); x.push_back('e'); myvector<char> z = bind<myvector,char,char>(x,inc); for(typename myvector<char>::iterator i = z.begin(); i != z.end(); ++i) { std::cout << *i; }
      
      





このような機能を実現するには、ファンクター型のクラスで十分ですが、より複雑な例を思いつきたくありませんでした。



All Articles