HaskellをC++で書いてみる(多相型(Maybe型)とFunctor型クラス)

はじめに

前回、単相型(具体型)とEq型クラスを作ったので
今回は多相型とFunctor型クラスを作成します

多相型(polymorphic type)に関して

例えばHaskellのList型は
[1,2,3,4]でも[1.0,2.0,3.0,4.0]でも['1','2','3','4']でも
どんな型でもListと呼ばれます
これはその無数の型に対するList型を定義しているわけではなくて
特定の型を取って、そのList型をつくる型のテンプレートのようなものが
定義されているからです

これを多相型といいます
多相型は具体型を型引数にとることで具体型になりますが
その振る舞いは多相型に定義されています

例えば、List型は型引数Intを取ることで
Int型の値を複数保持できるInt-List型になります
この振る舞いや型クラスはList型の方に書かれてあります

まんまC++のclass template、もしくはC#ジェネリックですね

List同様にMaybe型も多相型です

Maybe型

「これは結果を返しそこなうかもしれない計算の型を表現しています」
で有名なMaybe型です。
ようは、Int型に対して0でも-2147483648でもなく
「計算が失敗した」と言う状態をもたせることができる型です
他にも、MapでKey対応するValueがなかった場合などを表現したりします

C++だとboostにある(次回のC++17ではstdに入るらしい)optional型、
C#だとOption型が該当しますね

Haskellでは以下のように定義されています

Maybe a = Nothing | Just a
--derivingは省略

C++で(あえて)書いてみると以下のようになります

template<typename A>
struct Maybe
{
  A    a_;
  bool none_;
  Maybe(A const& a):a_(a),none_(fales){}  // Just a
  Maybe(          ):a_( ),none_(true){}   // Nothing
};

しかしこれだけでは足りません
HaskellではNothingあるいはJust*でMaybe型を作成するので
C++も同じようにMaybe型を作成する関数を作ってあげないといけません
これはFactoryパターンで実装できそうです

template<typename A>
auto Just( A const& a ) -> Maybe<A>
{  return Maybe(a); }

template<typename A=int>
auto Nothing(         ) -> Maybe<A>
{  return Maybe( ); }
  • 直和型ではない件について
    HaskellのMaybeは直和型、つまりは複数の定義(Just aかNothing)
    のうちどちらか一方を値として持ちます。
    しかし今回C++で作成したMaybeは直積型で表現したメンバ変数がそれぞれ値を持ち、
    これらの組み合わせで、2つの状態(Just aなのかNothingなのか)
    を再現しているにすぎません
    そのため厳密には別物です。
    本来なら、Maybe型はNothing型とJust型の両方をとる(型安全な)共用体型(boost.variant)
    として定義し、両方を保持できるべきなのです

これをvariant型に置き換えるか否かは非常に考えどころですが
現状はこのまま進めていきます
(この先、困ったときに修正することにします。遅延評価というやつです)

Functor型クラス(というかfmap)に関して

Functorは型クラスで、fmap関数を持っています
というかFunctor型クラスである条件はfmap関数が定義されている事だけです
いわゆるデフォルト実装を持たない型クラスですので
前回同様に関数のインスタンス宣言と実装を追記します
インスタンスの自動導出はC++では私の能力ではちょっと難しい・・・)

まずはHaskellのMaybeにおけるfmap関数の実装を見てみます

instance Functor Maybe where
  fmap f (Just x) = Just (f x)
  fmap f Nothing  = Nothing

Just aかNothingでパターンマッチをしていますね
これは現在のMaybeでは扱えません
(JustとNothingを型としてMaybeが保有すればできたかもですが・・・)
べつの方法をとる必要があります。
今回は、普通にMaybe内部の値none_を3項演算子の分岐条件にし
fを適応する(Just(f a))か、しないか(Nothing)分岐して返すようにしました

//Fanctor
template<typename B>
friend auto fmap( std::function<B(A)> const& f, Maybe<A> const& a ) -> Maybe<B>
{ return (a) ? (Maybe<B>(f(a.a_))) : (Maybe<B>()); }

完成?

template<typename A>
struct Maybe
{
  private:
    A    a_;
    bool none_;
    Maybe(A const& a):a_(a),none_(false){}  // Just a
    Maybe(          ):a_( ),none_(true){}   // Nothing

    // Functor type class methed (fmap)
    template<typename B>
    friend auto fmap( std::function<B(A)> const& f, Maybe<A> const& a ) -> Maybe<B>
    {  return (a.none_) ? (Maybe<B>()) : (Maybe<B>(f(a.a_))); }
};

//値コンストラクタ(Factoryパターン)
template<typename B>
auto Just( B const& a ) -> Maybe<B>
{  return Maybe<>(a); }

template<typename B=int>
auto Nothing(         ) -> Maybe<B>
{  return Maybe<>( ); }

http://melpon.org/wandbox/permlink/s23nt3iWCVWeRhKi
http://melpon.org/wandbox/permlink/eMT4ofGS7tAdlpFm

おまけ:遅延評価

コンストラクタがわりのFactoryパターンを含め
すべてのMaybe型返し値を引数のないstd::function<Maybe<A>()>で返せば
遅延評価みたいになります

以下はそれを愚直に実装したものです

http://melpon.org/wandbox/permlink/vFna69sy9g4jC0r2

書いてて、IO<T>の実装をしたくなりましたので、近日いろいろ考察してみたいですね