HaskellをC++で書いてみる(型クラス)
はじめに
お勉強のために
Eqを創ってみる
型クラスとは
私は現時点では「Concept」のようだという印象を受けております
「その型(クラス)はどの関数で使用できるのか」とドキュメントであり かつ静的チェッカーの役割を担っているものだとEqとは?
等価性を確認できる型クラス、つまりは
==関数と/=関数の引数にできる型である事を明示しています
C++的に言うと、メンバ関数operator==とoperator!=を有している
と言ったところでしょうか?
(最もoperator==などは普通はfriend関数で定義しますが)
以下のように定義してあります
--定義 class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x /= y = not (x == y) --(1) x == y = not (x /= y) --(2)
(1)(2)に注目します
(==)の定義と(/=)の定義が無限再帰しているように見えます
意味が分かりませんね
しかしこれは、どちらか一方の定義を上書きしてあげれば止まります
つまり・・・そういうことです
- 型クラスを使う
オレオレ型にEq型クラスを属させる
Eq型クラスにオレオレ型を属させる
-- オレオレ型のPointちゃんの型と値コンストラクタを定義 data Point = Point Int Int deriving Eq -- (Eq)型クラスに属します -- Eq型クラスの(==)関数だけをPoint型で再定義(?)します instance Eq Point where Point x y == Point x y = True -- (==)だけを定義すると -- 比較してみる main = do print $ (Point 3 1) /= (Point 4 2) -- (/=)が自動的に定義される
CRTPを使ったMixinに似ていますね
C++で書いてみる
- 型クラスはCRTPで定義する
#include<iostream> template<typename Derived> class Eq { friend auto operator==(Derived x,Derived y) -> bool { return !(x!=y)} friend auto operator!=(Derived x,Derived y) -> bool { return !(x==y)} }; // ↑ これ(Eq型クラス)はDataモジュール(ライブラリ)として提供する // ↓ オリジナルの型をEq型クラスに従属させる struct Point : Eq<Point> { Point(int a,int b):a_(a),b_(b){} private: int a_; int b_; friend auto operator==(Point x,Point y) -> bool { return (x.a_==y.a_)&&(x.b_==y.b_); } }; int main(){ std::cout<<( Point(1,2)!=Point(3,4) )<<std::endl; }
しかしC++仕様ではfriend関数をオーバーライドする事はできません
残念ながらすべてをHaskell同様に記述する事は難しいようです
結局、オーバーライドしたかったfriend関数はCRTPのBaseクラスのほうでは
コメントアウトしなければなりません
#include<iostream> template<typename Derived> class Eq { //friend auto operator==(Derived x,Derived y) -> bool // { return !(x!=y); } friend auto operator!=(Derived x,Derived y) -> bool { return !(x==y); } }; // ↑ これ(Eq型クラス)はDataモジュール(ライブラリ)として提供する // ↓ オリジナルの型をEq型クラスに従属させる struct Point : Eq<Point> { Point(int a,int b):a_(a),b_(b){} private: int a_; int b_; //public: friend auto operator==(Point x,Point y) -> bool { return (x.a_==y.a_)&&(x.b_==y.b_); } }; int main(){ std::cout << ( Point(1,2)!=Point(3,4) ) << std::endl; }
(追記)
friend関数をtemplateにして実装し、
その上でDerived側で特殊化すると上手く行くそうです
まあ記述量増えたけどご愛嬌と言う事で・・・、
むしろ関数テンプレートの特殊化がinstance化のようで
割と有りな気がします
(追記)
基底クラス(型クラス)のfriend関数を関数templateにして
派生クラス(作った型)のfriend関数を非関数templateにしてもよさそうです
以下はソレに修正してあります
#include<iostream> template<typename Derived> class Eq { template<typename D> friend auto operator==( Eq<D> const& x, Eq<D> const& y) -> bool { return !( static_cast<D const&>(x) != static_cast<D const&>(y) ); } template<typename D> friend auto operator!=( Eq<D> const& x, Eq<D> const& y) -> bool { return !( static_cast<D const&>(x) == static_cast<D const&>(y) ); } }; // ↑ これ(Eq型クラス)はDataモジュール(ライブラリ)として提供する // ↓ オリジナルの型をEq型クラスに従属させる struct Point : Eq<Point> { Point(int a,int b):a_(a),b_(b){} private: int a_; int b_; // インスタンス化 friend auto operator==( Point const& x, Point const& y ) -> bool { return (x.a_==y.a_)&&(x.b_==y.b_); } }; int main(){ std::cout << ( Point(1,2)==Point(3,4) ) << std::endl; std::cout << ( Point(1,2)!=Point(3,4) ) << std::endl; }
http://melpon.org/wandbox/permlink/XzHZ8jGWaE0isNiV
http://melpon.org/wandbox/permlink/bwWIjgq3ADl1ZrJN
http://melpon.org/wandbox/permlink/mopMms3mnhcb9u2C
http://melpon.org/wandbox/permlink/Z6cUDfUrjyBkJRcg
しかし、何の型クラスのインスタンス化をしているのか分かり辛いですね・・・