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

しかし、何の型クラスのインスタンス化をしているのか分かり辛いですね・・・