Implementa typeclass Haskell con l'interfaccia C #

12

Sto cercando di confrontare le classi di tipi di Haskell e le interfacce di C #. Supponiamo che ci sia un Functor .

Haskell:

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

Come implementare questa classe di caratteri come interfaccia in C #?

Quello che ho provato:

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

Questa è un'implementazione non valida e in realtà sono stucked con il tipo generico F che deve essere restituito da fmap . Come dovrebbe essere definito e dove?

È impossibile implementare Functor in C # e perché? O forse c'è un altro approccio?

    
posta ДМИТРИЙ МАЛИКОВ 25.11.2013 - 15:42
fonte

2 risposte

8

Il sistema di tipi di C # manca di un paio di funzioni necessarie per implementare correttamente le classi di tipi come interfaccia.

Iniziamo con il tuo esempio, ma la chiave mostra un account più completo su cosa è e cosa fa un typeclass e poi prova a mappare quelli a C # bits.

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

Questa è la definizione della classe del tipo o simile all'interfaccia. Ora esaminiamo una definizione di un tipo e la sua implementazione di tale classe di caratteri.

data Awesome a = Awesome a a

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

Ora possiamo vedere molto chiaramente un fatto distinto delle classi di tipi che non puoi avere con le interfacce. L'implementazione della classe del tipo non fa parte della definizione del tipo. In C #, per implementare un'interfaccia, è necessario implementarla come parte della definizione del tipo che la implementa. Ciò significa che non è possibile implementare un'interfaccia per un tipo che non si implementa da soli, tuttavia in Haskell è possibile implementare una classe di tipo per qualsiasi tipo a cui si ha accesso.

Questo è probabilmente il più grande immediatamente, ma c'è un'altra differenza abbastanza significativa che rende l'equivalente C # davvero non funzionante quasi altrettanto bene, e ci stai toccando nella tua domanda. Riguarda il polimorfismo. Inoltre ci sono alcune cose relativamente generiche che Haskell ti permette di fare con classi di tipi che non si traducono in modo particolare, specialmente quando inizi a guardare la quantità di generismo in tipi esistenziali o altre estensioni GHC come gli ADT generici.

Vedete, con Haskell potete definire i funtori

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)

Quindi in consumo puoi avere una funzione:

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

Qui sta il problema. In C # come si scrive questa funzione?

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

Quindi c'è un paio di cose che non vanno nella versione C #, per una cosa non sono nemmeno sicuro che ti permetterà di usare il qualificatore <b> come ho fatto io, ma senza di esso I am certo che non invierà Show<> appropriatamente (sentiti libero di provare e compilare per scoprirlo, io no).

Il problema più grande qui però è che diversamente da sopra in Haskell dove avevamo il nostro Terminal s definito come parte del tipo e quindi utilizzabile al posto del tipo, a causa del C # che non ha il polimorfismo parametrico appropriato (che diventa super ovvio come Appena proverai ad interopizzare F # con C #) non puoi distinguere chiaramente o chiaramente se Right o Left sono Terminal s. La cosa migliore che puoi fare è usare null , ma cosa succede se stai provando a fare un tipo di valore a Functor o nel caso di Either dove stai distinguendo due tipi che entrambi hanno un valore? Ora devi usare un tipo e avere due valori diversi da controllare e passare da un modello all'altro per discriminare?

La mancanza di veri e propri tipi di somma, tipi di unione, ADT, qualunque cosa tu voglia chiamarli fa davvero molto di ciò che i typeclass ti fanno svanire perché alla fine del giorno ti permettono di trattare più tipi (costruttori) come singolo tipo, e il sistema di tipo sottostante di .NET semplicemente non ha questo concetto.

    
risposta data 25.11.2013 - 18:01
fonte
4

Ciò di cui hai bisogno sono due classi, una per modellare l'ordine superiore generico (il functor) e una per modellare il functor combinato con il valore libero 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);
}

Quindi, se usiamo l'opzione monad (perché tutte le monadi sono funtori)

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

Puoi quindi utilizzare i metodi di estensione statica per convertire da IF < Opzione, B > ad alcuni < A > quando è necessario

    
risposta data 15.03.2014 - 03:07
fonte

Leggi altre domande sui tag