2014-11-09 13 views
8

Ich versuche, Extension-Methoden zu einer bestehenden Klasse Elem in Scala bereitzustellen. Ich möchte aber auch, dass die Operationen für alle M[Elem] verfügbar sind, solange eine Scalaz Functor für M im Anwendungsbereich ist. Das Verhalten ist immer, die Operation auf den Funktor anzuwenden, indem map verwendet.Scala implizit für beliebig tief Functor Zusammensetzung

import scalaz._ 
import Scalaz._ 

class Elem 

implicit class Ops[F[_]: Functor, A <% Elem](self: F[A]) { 
    def foo = self.map(_ => "bar") 
} 

val elem = new Elem 

// TODO (nice to have): can we avoid this "explicit implicit" conversion? 
implicit def idOps[A <% Elem](self: A) = new Ops[Id, A](self) 

elem.foo      // bar 
Option(elem).foo    // Some(bar) 
List(elem).foo     // List(bar) 

Ich möchte noch weiter gehen und meine Erweiterungsmethoden zur Verfügung stellen beliebig tief functors zu, wie List[Option[Elem]] und Option[Option[Option[Elem]]]. Ich war in der Lage eines impliziten schreiben Ops für die Zusammensetzung von zwei functors Bereitstellung, aber ich war nicht in der Lage, es zu willkürlichen Verschachtelung Tiefen zu verallgemeinern:

// TODO: can we improve this to provide arbitrarily deep functor composition? 
implicit def compositeOps[F[_]: Functor, G[_]: Functor, A <% Elem](self: F[G[A]]) = { 
    implicit val FG = implicitly[Functor[F]].compose[G] 
    new Ops[({ type FG[X] = F[G[X]] })#FG, A](self) 
} 

List(Option(elem)).foo   // List(Some(bar)) 
Option(List(Option(elem))).foo // doesn't compile 

Gibt es eine Möglichkeit, dies zu erreichen?

Antwort

3

Sie können einen impliziter Helfer rekursiv zur Verfügung stellen:

sealed trait Helper[FA] { 
    type A 
    type F[_] 
    def w(fa: FA): F[A] 
    val f: Functor[F] 
} 
trait Helper1 { 
    implicit def nil[A] = new Helper[A] { 
    type A = A 
    type F[X] = X 
    def w(a: A) = a 
    val f = implicitly[Functor[Id]] 
    } 
} 
object Helper extends Helper1 { 
    implicit def cons[FA1, RA](implicit u: Unapply[Functor, FA1]{type A = RA}, 
    rest: Helper[RA]) = new Helper[FA1] { 
    type A = rest.A 
    type F[X] = u.M[rest.F[X]] 
    def w(fa: FA1) = u.TC.map(u.apply(fa))(rest.w) 
    val f = rest.f.compose(u.TC) //or the other way around, I can never remember 
    } 
} 
implicit def compositeOps[FA, A1](self: FA)(
    implicit helper: Helper[FA]{type A = A1}, conv: A1 => Elem) = { 
    implicit val FG = helper.f 
    new Ops[helper.F, helper.A](helper.w(self)) 
} 
+0

Danke für die Antwort! Ich habe zwei Probleme mit Ihrer Lösung gefunden: 1) Ich brauche wirklich eine implizite Umwandlung von 'A' in einen festen' Elem' Typ wie im Beispiel. Gibt es irgendeinen Weg, damit und mit deinem 'Helfer' zu arbeiten, außer dass das' A => Elem' als 'val' im Körper von' Helfer' enthalten ist?2) Der Compiler kann keine "Helfer" -Instanzen für Unterklassen der Funktoren selbst finden, z. Ich kann "Ops" nicht auf "Einige" anwenden. Gibt es eine Möglichkeit, diese Beschränkung aufzuheben? Mir ist bewusst, dass dies schon bei Scalaz-Klassen der Fall ist, aber in meinem Fall ist es besonders wichtig, dass ich das kann. –

+0

1) '<%' ist nur Zucker für ein implizites 'A => Elem'; Ich habe das zu den CompositeOps im Beispiel hinzugefügt. 2) Es ist nicht wirklich möglich; Dies ist eine andauernde Debatte in scalaz, aber wenn wir Dinge als co/kontravariant deklarieren würden, würde dies zu Fällen führen, in denen "Any" abgeleitet wurde anstelle von was ein Typfehler sein sollte. Es ist möglich, einen "Functor [Some]" zu deklarieren, aber das wird Sie wahrscheinlich in Schwierigkeiten bringen. Der empfohlene Ansatz besteht darin, "intelligente Konstruktoren" wie scalazs "some" zu verwenden, die die entsprechenden Typen (in diesem Fall "Option [A]" und nicht "Some [A]") zurückgeben. – lmm

+0

Ich wusste von '<%', aber ich wusste nicht, wie man den Typ von 'A1' erhält und dachte nicht an' {type A = A1} '. Und ich denke, ich werde dann das Problem mit den Unterklassen behandeln. Vielen Dank! –

2

Wir haben eine Eigenschaft erstellen, um darzustellen, was Sie

trait DeepFunctor[X, A] { 
    type Result[_] 
    def map[B](x: X)(f: A => B): Result[B] 
} 

Hinweis mögen, dass es X erlaubt in ein Result[B] abgebildet werden. X könnte an dieser Stelle alles sein.

Die einfachste Version ist, wo wir sagen, dass X gleich A ist. Das Ergebnis sollte dann Id[B] lauten. Es ist ein Merkmal, um sicherzustellen, dass es eine niedrige Priorität hat.

trait LowerPriorityDeepFunctor { 
    implicit def identity[A] = 
    new DeepFunctor[A, A] { 
     type Result[x] = Id[x] 
     def map[B](x: A)(f: A => B) = f(x) 
    } 
} 

Beachten Sie, dass es keine Notwendigkeit für die Functor von Id zu fragen ist.

Die komplexere Version ist wo X ist einige Container, für die eine Functor definiert ist.

object DeepFunctor extends LowerPriorityDeepFunctor { 
    implicit def deep[F[_], X, A](
    implicit F: Functor[F], inner: DeepFunctor[X, A]) = 
    new DeepFunctor[F[X], A] { 
     type Result[x] = F[inner.Result[x]] 
     def map[B](x: F[X])(f: A => B) = F.map(x)(inner.map(_)(f)) 
    } 
} 

Das Ergebnis der deep Methode ist ein DeepFunctor für F[X]. Da wir nichts über X wissen, fordern wir eine DeepFunctor für X an. Dies wird rekursiv nach DeepFunctor Instanzen suchen, bis es identity erreicht. Jetzt

Ihre Ops Klasse wird relativ einfach

implicit class Ops[X](self: X) { 
    def foo[A](implicit F: DeepFunctor[X, A]) = F.map(self)(_ => "bar") 
} 

Beachten Sie, dass die _ jetzt vom Typ A. Wenn Sie auf einen bestimmten Typ einschränken möchten, können Sie A als A <: SomeType definieren. Wenn Sie eine implizite Konvertierung unterstützen möchten, können Sie ein zusätzliches implizites Argument ev: A => SomeType verwenden. Wenn Sie A einen bestimmten Typ machen möchten, können Sie den A entfernen und SomeType direkt in den DeepFunctor einfügen.

+0

Das sieht nach einer sauberen und relativ leicht verständlichen Lösung aus! Die einzige Sache, die mich etwas stört ist, dass ich einen impliziten 'DeepFunctor' in jeder Methode und nicht in der Klasse selbst anfordern muss, da ich viel mehr als eine Operation wie' foo' habe und ich alle ihre Signaturen ändern müsste . Ich habe versucht, 'A' und' F' in die Klassensignatur zu bringen, aber ich hatte kein Glück, da das Ergebnis für jede Methode vom Typ 'DeepFunctor' abhängig ist und der Client-Code seinen Typ nicht in die ursprüngliche Funktorkomposition auflöst. Siehst du irgendeinen Weg, wie ich das vermeiden kann? Wenn nicht, ist das in Ordnung :) –

+0

@ RuiGonçalves Ja, es scheint das Problem wird verursacht durch 'Typ Ergebnis [x] = F [inner.Result [x]]'. Ich denke, das ist ein Compiler-Bug, aber ich weiß es nicht genau. Wenn das Implizite in die Klasse selbst verschoben wird, ist der Typ "Result [x]" verloren. Ich habe versucht, den 'Result'-Typ in den' DeepFunctor [X, A, R [_]] 'zu verschieben, aber das hat die implizite Auflösung durcheinander gebracht. Theoretisch ist dies eine Stack-Overflow-Frage für sich. Ich habe jedoch keine Zeit mehr, um ein einfaches reproduzierbares Beispiel zu erstellen, das dieses Problem zeigt. – EECOLOR