Haskell型クラスをC#インターフェイスで実装する


13

Haskellの型クラスとC#のインターフェイスを比較しようとしています。があるとしFunctorます。

ハスケル:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

この型クラスをC#のインターフェイスとして実装する方法は?

私が試したもの:

interface Functor<A, B>
{
    F<B> fmap(Func<A, B> f, F<A> x);
}

これは無効な実装であり、実際Fに返されるべきジェネリック型にこだわっていfmapます。どのように定義すべきか、どこで?

FunctorC#で実装することは不可能ですか?なぜですか?それとも別のアプローチがありますか?


8
:エリックリッペルトはこの答えではHaskellで定義されているC#の型システムは、ファンクタの高いkindedな性質をサポートするために、実際には十分ではないかについて少し話しstackoverflow.com/a/4412319/303940
KChaloux

1
これは約3年前です。何か変わった?
ДМИТРИЙМАЛИКОВ

4
全く何もC#でこれを可能にするために変更されていない、また私は、将来的にはその可能性が高いと思います
JK。

回答:


8

C#の型システムには、型クラスをインターフェイスとして適切に実装するために必要ないくつかの機能がありません。

あなたの例から始めましょうが、キーは、タイプクラスが何であり何をするのかについてのより完全な説明を示しており、それらをC#ビットにマップしようとしています。

class Functor f where
  fmap :: (a -> b) -> f a -> f b

これは、型クラス定義、またはインターフェースに類似しています。次に、型の定義と、その型クラスの実装を見てみましょう。

data Awesome a = Awesome a a

instance Functor Awesome where
  fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)

これで、インターフェイスで持ち得ない型クラスの1つの明確な事実を非常に明らかに見ることができます。型クラスの実装は、型の定義の一部ではありません。C#では、インターフェイスを実装するには、それを実装する型の定義の一部としてインターフェイスを実装する必要があります。これは、自分では実装していない型のインターフェイスを実装できないことを意味しますが、Haskellでは、アクセスできる型の型クラスを実装できます。

それはおそらくすぐに最大になりますが、別のかなり大きな違いがあり、C#の同等の機能が実際にはほとんど機能しないため、質問で触れています。ポリモーフィズムについてです。また、Haskellでは、特に実存型やジェネリックADTなどの他のGHC拡張機能のジェネリック性の量を調べ始めたときに、まったく翻訳されないタイプクラスを使用して、比較的一般的なことを行うことができます。

Haskellを使用すると、ファンクターを定義できます

data List a = List a (List a) | Terminal
data Tree a = Tree val (Tree a) (Tree a) | Terminal

instance Functor List where
  fmap :: (a -> b) -> List a -> List b
  fmap f (List a Terminal) = List (f a) Terminal
  fmap f (List a rest) = List (f a) (fmap f rest)

instance Functor Tree where
  fmap :: (a -> b) -> Tree a -> Tree b
  fmap f (Tree val Terminal Terminal) = Tree (f val) Terminal Terminal
  fmap f (Tree val Terminal right) = Tree (f val) Terminal (fmap f right)
  fmap f (Tree val left Terminal) = Tree (f val) (fmap f left) Terminal
  fmap f (Tree val left right) = Tree (f val) (fmap f left) (fmap f right)

次に、消費時に関数を使用できます。

mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar

ここに問題があります。C#では、この関数をどのように記述しますか?

public Tree<a> : Functor<a>
{
    public a Val { get; set; }
    public Tree<a> Left { get; set; }
    public Tree<a> Right { get; set; }

    public Functor<b> fmap<b>(Func<a,b> f)
    {
        return new Tree<b>
        {
            Val = f(val),
            Left = Left.fmap(f);
            Right = Right.fmap(f);
        };
    }
}
public string Show<a>(Showwable<a> ror)
{
    return ror.Show();
}

public Functor<String> mapsSomething<a,b>(Functor<a> rar) where a : Showwable<b>
{
    return rar.fmap(Show<b>);
}

C#バージョンにはいくつか問題があります.1つ<b>は、私がそこにいたように修飾子を使用できるかどうかさえ確信していませんが、それなしでは適切にディスパッチされないこと確かですShow<>(お気軽に試してくださいコンパイルして調べます;しませんでした)。

ただし、ここでの大きな問題は、上記のHaskellとは異なり、Terminals#を型の一部として定義し、型の代わりに使用できることです。これは、C#が適切なパラメトリック多型を欠いているためです(相互運用を試みるとすぐに明らかになります) F#with C#)右または左がTerminals かどうかを明確または明確に区別することはできません。できるのはuse nullだけですが、値の型を作成しようとしている場合、FunctorまたはEither両方とも値を保持する2つの型を区別する場合はどうでしょうか。今、あなたはあなたの差別をモデル化するためにチェックして切り替えるために1つのタイプを使用し、2つの異なる値を持っている必要がありますか?

適切な合計型、ユニオン型、ADT、それらを呼び出したいものの欠如は、タイプクラスがあなたに与えるものの多くを失います。 .NETの基礎となる型システムには、このような概念はありません。


2
私はHaskell(標準MLのみ)にあまり精通していないので、これがどの程度の違いをもたらすかはわかりませんが、C#で合計タイプをエンコードすることは可能です。
ドーバル14年

5

必要なのは2つのクラスです。1つは高次のジェネリック(ファンクター)をモデル化し、もう1つは自由な値Aを持つ複合ファンクターをモデル化します。

interface F<Functor> {
   IF<Functor, A> pure<A>(A a);
}

interface IF<Functor, A> where Functor : F<Functor> {
   IF<Functor, B> pure<B>(B b);
   IF<Functor, B> map<B>(Func<A, B> f);
}

したがって、Optionモナドを使用すると(すべてのモナドがファンクタであるため)

class Option : F<Option> {
   IF<Option, A> pure<A>(A a) { return new Some<A>(a) };
}

class OptionF<A> : IF<Option, A> {
   IF<Option, B> pure<B>(B b) {
      return new Some<B>(b);
   }

   IF<Option, B> map<B>(Func<A, B> f) {
       var some = this as Some<A>;
       if (some != null) {
          return new Some<B>(f(some.value));
       } else {
          return new None<B>();
       }
   } 
}

その後、静的拡張メソッドを使用して、必要なときにIF <Option、B>からSome <A>に変換できます。


私はとの難しさを持っているpure一般的なファンクタのインタフェースで:コンパイラがで文句を言いIF<Functor, A> pure<A>(A a);に「タイプはFunctortypeパラメータとして使用することはできませんFunctor方法の一般的なタイプでIF<Functor, A>何ボクシング変換やから型パラメータ変換はありません。FunctorとはF<Functor>。」これは何を意味するのでしょうか?そして、なぜpure2つの場所で定義する必要があるのでしょうか?さらに、pure静的であるべきではありませんか?
ニリエル

1
こんにちは。クラスを設計するとき、私はモナドとモナド変換子にほのめかしていたからだと思います。OptionTモナド変換器(HaskellのMaybeT)などのモナド変換器は、C#でOptionT <M、A>として定義されます。ここで、Mは別の汎用モナドです。M <Option <A >>型のモナドのOptionTモナドトランスフォーマーボックスですが、C#にはより高い種類の型がないため、OptionT.mapおよびOptionT.bindを呼び出すときに、より高い種類のMモナドをインスタンス化する方法が必要です。モナドMに対してM.pure(A a)を呼び出すことができないため、静的メソッドは機能しません
。– DetriusXii
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.