Haskell typeclass'ı C # arayüzü ile uygulayın


13

Haskell'in tip sınıflarını ve C # arayüzlerini karşılaştırmaya çalışıyorum. Varsayalım Functor.

Haskell:

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

Bu tip sınıf C # arabirimi olarak nasıl uygulanır?

Ne denedim:

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

Bu geçersiz bir uygulama ve aslında Ftarafından döndürülmesi gereken genel türü ile sıkışmış fmap. Nasıl ve nerede tanımlanmalıdır?

FunctorC # 'da uygulamak imkansız mı ve neden? Ya da belki başka bir yaklaşım var mı?


8
Eric Lippert, C # 'ın tip sisteminin, Haskell tarafından bu cevapta tanımlandığı gibi Functors'ın daha yüksek türdeki doğasını desteklemek için gerçekten yeterli olmadığını biraz anlatıyor: stackoverflow.com/a/4412319/303940
KChaloux

1
Bu yaklaşık 3 yıl önceydi. Bazı şeyler değişti?
ДМИТРИЙ МАЛИКОВ

4
C # 'da bunu mümkün kılmak için hiçbir şey değişmedi, ne de gelecekte olacağını düşünüyorum
jk.

Yanıtlar:


8

C # 'ın tip sistemi, arabirim olarak tip sınıflarını düzgün bir şekilde uygulamak için gereken birkaç özelliğe sahip değildir.

Örneğinizle başlayalım, ancak anahtar, bir typeclass'ın ne olduğunu ve ne yaptığını daha kapsamlı bir şekilde gösteriyor ve ardından bunları C # bitleriyle eşlemeye çalışıyor.

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

Bu, tür sınıfı tanımıdır veya arabirime benzer. Şimdi bir tür tanımına ve bu tür sınıfın uygulanmasına bakalım.

data Awesome a = Awesome a a

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

Şimdi , arayüzlerle sahip olamayacağınız tip sınıflarının belirgin bir gerçeğini açıkça görebiliyoruz . Type sınıfının uygulanması, tür tanımının bir parçası değildir. C # 'da, bir arabirimi uygulamak için, arabirimi uygulayan tür tanımının bir parçası olarak uygulamalısınız. Bu, kendiniz uygulamadığınız bir tür için arabirim uygulayamayabileceğiniz anlamına gelir, ancak Haskell'de erişiminiz olan herhangi bir tür için bir tür sınıfı uygulayabilirsiniz.

Muhtemelen hemen en büyüğüdür, ancak C # eşdeğerini gerçekten de işe yaramaz hale getiren oldukça önemli bir fark daha vardır ve sorunuza dokunuyorsunuz. Polimorfizm ile ilgili. Ayrıca Haskell, özellikle varoluşçu türlerde veya Genel ADT'ler gibi diğer GHC uzantılarında jeneriklik miktarına bakmaya başladığınızda doğrudan tercüme edilmeyen tip sınıflarıyla ilgili olarak görece bazı genel şeyler var.

Görüyorsunuz, Haskell ile fonksiyonları tanımlayabilirsiniz

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)

Sonra tüketimde bir fonksiyona sahip olabilirsiniz:

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

Burada sorun yatıyor. C # 'da bu işlevi nasıl yazıyorsunuz?

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>);
}

Bu yüzden C # sürümünde birkaç şey yanlış, bir şey için <b>, orada yaptığım gibi niteleyiciyi kullanmanıza izin vereceğinden emin değilim , ancak onsuz uygun şekilde gönderilmeyeceğinden eminimShow<> (denemekten çekinmeyin öğrenmek için derledim;

Bununla birlikte, burada daha büyük sorun, Haskell'de, bizim türümüzün bir Terminalparçası olarak tanımlandığımız ve daha sonra tür yerine kullanılabilir olduğu için, C # 'nin uygun parametrik polimorfizmden yoksun olmasından dolayı (birlikte çalışmayı denediğiniz anda çok belirgin hale gelen) F # ile C #), Sağ veya Sol Terminals olup olmadığını net veya temiz bir şekilde ayırt edemezsiniz . Yapabileceğiniz en iyi şey kullanmaktır null, ancak Functorya bir değer türü a yapmaya çalışıyorsanız ya da Eitherher ikisi de değer taşıyan iki türü ayırt ettiğinizde ne olur? Şimdi ayrımcılığınızı modellemek için bir tür kullanmanız ve iki farklı değeriniz mi var?

Uygun toplam türlerinin, birleşim türlerinin, ADT'lerin eksikliği, onları ne adlandırmak istersen, tipik olarak size hangi tiplerin düştüğünü bir sürü yapar, çünkü günün sonunda birden fazla türe (yapıcılara) tek bir tür olarak davranmanıza izin verir, ve .NET'in altında yatan tip sisteminin böyle bir konsepti yoktur.


2
Ben Haskell (sadece Standart ML) çok bilgili değilim, bu ne kadar fark yaratıyor bilmiyorum, ama C # toplam türlerini kodlamak mümkün .
Doval

5

İhtiyacınız olan şey, biri yüksek dereceli jenerik (functor) modellemek ve diğeri birleşik functor'ı serbest değer A ile modellemek için iki sınıftır.

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);
}

Bu yüzden Option monadını kullanırsak (çünkü tüm monad'lar functor olur)

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>();
       }
   } 
}

Ardından, gerektiğinde IF <Option, B> 'den Bazı <A>' ya dönüştürmek için statik uzantı yöntemleri kullanabilirsiniz.


pureGenel functor arabirimi ile ilgili zorluklar var : derleyici ile şikayet IF<Functor, A> pure<A>(A a);"Tür genel yöntem türü Functortürü parametresi olarak kullanılamaz . Boks dönüştürme veya türü parametre dönüştürme için ." Ne anlama geliyor? Ve neden iki yerde tanımlamalıyız ? Dahası, statik olmamalı mı? FunctorIF<Functor, A>FunctorF<Functor>purepure
Niriel

1
Selam. Bence, sınıfı tasarlarken monad ve monad transformatörlere işaret ediyordum. OptionT monad transformatörü (Haskell'de Belki) gibi bir monad transformatörü, C #'da OptionT <M, A> olarak tanımlanır, burada M başka bir genel monaddır. OptionT monad transformatör kutuları M <Option <A>> tipindeki bir monadda kutular, ancak C # daha yüksek tür tiplerine sahip olmadığından, OptionT.map ve OptionT.bind çağırırken yüksek tür M monadını somutlaştırmak için bir yola ihtiyacınız vardır. Statik yöntemler işe yaramaz, çünkü herhangi bir monad M için M.pure (A a) '
yı çağıramazsınız
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.