2017-10-10 3 views
0

Insbesondere versuche ich meine Functor Typklasse mit Applicative zu erweitern.Scala: eine Trait-Methode auf eine implizite Klasse im Objekt des Elternattributs verschieben

trait Functor[F[_]] { 
    def fmap[A, B](r: F[A], f: A => B): F[B] 
} 

object Functor { 
    implicit class FunctorOps[A, F[_]: Functor](xs: F[A]) { 
    def fmap[B](f: A => B): F[B] = implicitly[Functor[F]].fmap(xs, f) 
    } 

    implicit def SeqFunctor: Functor[Seq] = new Functor[Seq] { 
    def fmap[A, B](r: Seq[A], f: A => B) = r map f 
    } 
} 

trait Applicative[F[_]] extends Functor[F] { 
// What I want to do, but this *does not* work. 
    def fmap[A, B](r: F[A], f: A => B): F[B] = Functor.FunctorOps[A, F](r).fmap(f) 

    def pure[A](x: A): F[A] 
    def fapply[A, B](r: F[A], f: F[A => B]): F[B] 
} 

object Applicative { 
    implicit class ApplicativeOps[A, F[_]](a: F[A])(implicit F: Applicative[F]) { 
    def fapply[B](f: F[A => B]): F[B] = F.fapply(a, f) 
    } 

    implicit def SeqApplicative: Applicative[Seq] = new Applicative[Seq] { 
    def pure[A](x: A) = Seq(x) 
    def fapply[A, B](xs: Seq[A], fs: Seq[A => B]): Seq[B] = xs.flatMap(x => fs.map(_(x))) 
    } 
} 

Der Kern ist es mir fmap für all Applicative s umsetzen müssen, aber es sollte wirklich das gleiche Verfahren, wie es in meiner FunctorOps Klasse definiert. Wie mache ich das auf die sauberste Art und Weise?

Antwort

1

Sie haben den Applicative[F] <: Functor[F] Teil richtig, aber Sie sollten wirklich darüber nachdenken, was das bedeutet. Dies bedeutet, dass eine Instanz von Applicative[F]auch die Methoden für Functor[F] bereitstellt. Das heißt, Sie können nicht beide implicit val listFunctor: Functor[List]; implicit val listApplicative: Applicative[List] haben, denn dann ist der Compiler verwirrt, wenn Sie nach implicit param: Functor[List] fragen. Du solltest nur Letzteres haben. Was Sie versuchen dann unsinnig zu tun ist, weil Sie die Functor Instanz für F in Bezug auf sich selbst sind die Definition (weil die Applicative[F] sollte sein die Functor[F]), und Sie am Ende mit zwei Functor[Seq] s unabhängig.

Was Sie können tun, ist implementieren fmap in Bezug auf pure und fapply:

trait Applicative[F[_]] extends Functor[F] { 
    override def fmap[A, B](r: F[A], f: A => B): F[B] = fapply(r, pure(f)) 

    def pure[A](x: A): F[A] 
    def fapply[A, B](r: F[A], f: F[A => B]): F[B] 
} 

dann die Functor[Seq] Instanz entfernen und halten Sie Ihre Applicative[Seq], wie es ist (oder fmap überschreiben, wenn Sie möchten). Sie haben jetzt ein anderes Problem, da die implizite Suche ein bisschen umgekehrt wird, da die Instanz tatsächlich in object Applicative ist, aber das Korrigieren der impliziten Auflösung weniger mühsam ist, als die Konsistenz der separaten Instanzen Functor und Applicative zu erzwingen. In diesem Fall, wo Seq eine Art Begleiter, dessen Objekt, das Sie nicht kontrollieren können, cats zumindest etwas tut, wie

package instances { 
    trait SeqInstances { 
    implicit val seqFunctor: Functor[Seq] = ??? 
    } 
    package object seq extends SeqInstances 
    package object all extends SeqInstances 
         with AAAInstances 
         with BBBInstances 
         with ... 
} 

FYI: Ich schlage vor, Ihre typeclass Methoden

def fmap[A, B](r: F[A])(f: A => B): F[B] 
def fapply[A, B](r: F[A])(f: F[A => B]): F[B] 

und es kann currying sein schön ein flip fapply in ApplicativeOps

def ylppaf[I, B](f: F[I])(implicit ev: A =:= (I => B)) 
    : F[B] = F.fapply(a.fmap(ev))(f) 
+0

ich tun wollen müssen, was ich basiert auf der Idee macht einen gegebenen TypC der Verlängerung Lass, ohne seine Quelle zu ändern. Also: 1) Ich kann nichts über meinen 'Funktor'-Code ändern, einschließlich des Entfernens der impliziten Klasse. Was ist, wenn ich eine zufällige Typklasse erhalte, die ich erweitern möchte? 2) 'fmap' (oder welche Funktion ich erben von der Typklasse, die ich gegeben bin) ist möglicherweise nicht so einfach zu definieren in Bezug auf die Funktionen meiner typeclass. – allidoiswin

+1

2) ist strittig. Wenn Sie die Methoden der Oberklasse nicht in Bezug auf die Unterklassen definieren können, lassen Sie sie einfach abstrakt und lassen Sie den Implementierer dazu kommen (wie in Haskell, wo es nur * (portabel) ist.). 1) Wenn Sie einige existierende, nicht modifizierbare Superklassen-Instanzen haben, müssen Sie nur Ihre Subklasseninstanz definieren, um sie auf die Superklasse zu verschieben (Sie können diese in ihrer eigenen Hilfsklasse abstrahieren ('Helper [F [_]] (f : Functor [F]) {...} '), aber * setze diese Einschränkung nicht auf die Unterklasse selbst) und stelle sicher, dass die Unterklassen- und Oberklassen-Instanzen nicht zusammen importiert werden. – HTNW

Verwandte Themen