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
しかし、何の型クラスのインスタンス化をしているのか分かり辛いですね・・・
Variadic Templateの(僕が今更ながら新しく知った)使い方
知った事など
- 継承させる
struct Base1{}; struct Base2{}; // すべての型引数を継承 template<typename... Args> struct Derived : Args... { int a; }; int main(){ Derived<Base1,Base2> a; }
- mapして包含する(直接保持は無理)
struct Base1{}; struct Base2{}; template<typename... Ts> struct holder { }; template<typename T> struct Map { }; // template<typename... Args> struct Derived { // 保持 typedef holder< Args... > hold_; // ERROR //typedef Map<Args...> type; // Args...をMapにmap // そのままは持てないのでラップする typedef holder< Map<Args>... > type; }; int main(){ Derived<Base1,Base2> a; }
- mapして継承させる
struct Base1{}; struct Base2{}; template<typename T> struct Map { }; // 継承 tempalte<typename... Args> struct Derived : Map<Args>... { }; int main(){ Derived<Base1,Base2> a; }
- もちろん複数のVaridic templateでも可
struct Base1{}; struct Base2{}; template<int N,typename T> struct Map { }; // 継承 tempalte<int...Ns, typename... Args> struct Derived : Map<Ns,Args>... { }; int main(){ Derived<1,2,Base1,Base2> a; }
- 継承させた値(value)を扱う
// Argsの一部 struct Base1{ int value_; }; // Argsの一部 struct Base2{ int value_; }; // 継承 tempalte<typename... Args> struct Derived : Args... { template<typename T> auto get(T& t) { return t.value; } }; int main() { Derived<Base1,Base2> a(); a.get<Base1>(); //Base1.value_ }
通常であれば、valueはBase1::valueとBase2::valueの2つあって区別できないのですが
get関数内でthisを継承元にキャストすることで、Base1の持つ変数、関数を直接操作することが可能になっています
(多重継承の場合は使えるのかな?)
本題
と言う事で、Map<N,T>を適応しつつ複数の型を継承することで
面白い事ができるようです
template<int N,typename T> struct Map { T value_; }; // 継承 tempalte<int... Ns, typename... Args> struct Derived : Map<N,Args>... { tempalte<int N> auto get() { return get_<N>(*this); } // 今回this(Derived型)はMap<1,int>,Map<2,double>,Map<3,char>を継承する // するとget_<1>のようにして数字を与えthisを引数にとると // どのMap<1,T>のT(即ちint)をthisの継承一覧から推論してくれる template<int N, typename T> static auto get_(Map<N,T>& t) { return t.value; } }; int main() { Derived<1,2,3,int,double,char> a(); a.get<1>(); // return int a.get<2>(); // return double a.get<3>(); // return char }
おもしろいですね
「Map<T,N>のどちらか一方が判明しているのなら、もう一方を推論できる」
のような挙動をしています
上のコードではget
Map<N,T>の第2型引数であるTを推論しています
とてもおもしろいです。私ははじめて知りました
tuple
面白半分でtupleを実装してみました
数字を自動生成してはいるものの、
いわゆるO(n)オーダーというナンセンスな実装です
http://melpon.org/wandbox/permlink/lTybRjzNIs0iNcSt
tuple<typename T, int N>
tuple_n<int,3>
でtuple<int,int,int>
になる型エイリアス
http://melpon.org/wandbox/permlink/2WpxZNuDV1qNTrX2
使い所不明です
map<T,U&>で参照型コンテナ
std::map<T,U&>
知らなかった
Class Templateでの型推論
function templateの型推論
function templateでは関数呼び出し時の引数によって型が推論されます
template<typename T> T pow(T a,T b) return (b!=1) ? a : pow(a*a,b-1); } main(){ int a=3,b=3 pow(a,b); //pow<int>と推論される }
class templateの型推論できない?
一方でclass templateは推論することができません
というか,引数とかないので推論するための要素をクラス外から与えられないです
しかしながら、ある特定の条件下では最小のtemplate引数で複数の型を推論させることができます
それがtemplate引数にclass templateを投げた場合です
これによって、ある程度class templateを利用するときの冗長性を排除する事ができます
class templateの冗長性
例えばContainer
その先頭の要素(T型)の値を返すメンバ関数を持っている場合を考えます
以下のように書くとしましょう
template<typename Container,typename T> class N { Container cont_; public: T get(){return cont_.front(); } }; int main() { N<vector<int>,int> n; }
まず、Containerがfront()
を持っているのか?と言うのはコンパイル時に検出できるので無視します
問題はN<vector<int>,int>
のようにint
を2回書かなければならないのが、非常に冗長という事です
これを解決する手法としてテンプレートテンプレートパラメータにする手法が挙げられます
template< template<typename...> class Container,typename T> class N { Container<T> cont_; public: T get(){return cont_.front(); } } int main() { N<vector,int> n; }
しかしこれだと元のclass templateや型引数が分からないtypedef
されたコンテナを渡せなくなります
理想としては、vector<T>
を渡しただけで、T
を推論してほしいところです。
class templateの部分的な型推論
class templateは与えられた型が何かのclass templateだった場合に
そのtemplateの型引数は推論することができます
(ちなみにこれを型推論と言ってよいのか、実はよくわかっておりません)
以下のように書くと勝手にvector<int>
のint
をT
に(こちらが指定することなく推論して)割り当てます
template<typename Container> class N { N(); } // いわゆるclass templateの部分特殊化 template<template<typename...> class Container,typename T, typename... Args> class N< Container<T,Args> > { Container<T> cont_; public: T get(){ return cont_.front(); } }; int main() { typedef vector<int> vec_int; N<vec_int> n; }
上記のようにclass templateの部分特殊化を利用しています
はじめにclass templateだろうがただのclassだろうが
なんでも受取れるようにprimary class template
(特殊化ではないもともとのclass template)を定義します
そして部分特殊化版を書きます
今回書いているのはvector
とかlist
とかに適合するやつです
こうするとvector<T>
やlist<T>
で部分特殊化されるようになります
さらにこの部分特殊化ではvector<int>
のint
は型引数T
に自動的に割り当てられます
これによってSTLコンテナの型引数を自動判別させて、冗長なコードを少なくすることができます
ちなみに、誤って普通のクラスを型引数に代入し、
primary class templateとなってしまった場合を抑制するために
コンストラクタに細工をするのを忘れないようにするとよいです
(上記はコンストラクタが生成できないようにしています)
typeid().name()の出力
g++ではtype_info::name()で出力される
型名が人類には早すぎる文字列で出力されています
#include<iostream> #include<typeinfo> int main(){ std::cout << typeid([](int)->int{}).name() << std::endl; // 型直接渡してもよい std::cout << typeid(int).name() << std::endl; }
> Z4mainEUliE0_ > i
ちょっと調べたところ
マングル(?)されているそうです
よく判りません
しかし、読めるようにするにはデマングリングすればよいらしいです!
その記述は以下のようにすればよいそうです!!
#include<iostream> #include<typeinfo> #include <cxxabi.h> int main(){ int status = 0; // 宣言しないといけない std::cout << abi::__cxa_demangle( typeid([](int)->int{}).name(), 0, 0, &status ) << std::endl; // 型直接渡してもよい std::cout << abi::__cxa_demangle( typeid(int).name(), 0, 0, &status ) << std::endl; }
> main::{lambda(int)#1} > int