2017-05-15 1 views
0

J'ai deux fonctions avec exactement la même implémentation - une seule gère Option et l'autre gère Seq. Je voudrais utiliser des génériques pour écrire ceci comme une seule fonction qui gère Iterable, tout en gardant le type concret dans le code appelant - si c'est possible?Utilisation d'Iterable avec les génériques scala

def f[T](a: Seq[Failure \/ T]): Failure \/ Seq[T] = { ??? }
def g[T](b: Option[Failure \/ T]): Failure \/ Option[T] = { ??? }

La mise en œuvre n'a pas d'importance, mais pour le contexte, ils se traduisent d'une collection de résultats (dont chacun peut soit avoir réussi (T) ou échoué (Failure)) soit un échec ou une collection complète de résultats réussis. \/ est juste la version de scalaz d'Either.

Je cherche à faire quelque chose comme ceci:
def f[I[T] <: Iterable[T]](results: I[Failure \/ T]): Failure \/ I[T] = { ??? }

Répondre

2

Sur le dessus de ma tête et pas testé, donc des excuses pour les fautes de frappe.

import scala.collection.generic.CanBuildFrom 

def combine[M[X] <: Iterable[X], T](
    input: M[Failure \/ T] 
)(
    implicit cbf: CanBuildFrom[Nothing, T, M[T]] 
): Failure \/ M[T] = { 
    def inner(builder: Builder[T, M[T]], els: M[T]): Failure \/ M[T] = { 
    els.headOption match { 
     case Some(\/-(right)) => inner(builder += right, els.tail) 
     case Some(-\/(left)) => -\/(left) 
     case None => \/-(builder.result()) 
    } 
    } 
    inner(cbf(), input) 
} 

Quelque chose comme ça, vous avez une récursion intérieure que « circuits courts » lorsque le premier échec se trouve.

1

Vous pouvez faire quelque chose comme ceci (voir la mise en œuvre pour Future.sequence à titre d'exemple):

def f[T, M[X] <: Iterable[X]](results: M[Failure \/ T): Failure \/ M[T] 

Vous aurez probablement quelques-uns CanBuildFrom aussi.

3

Dans FP, cette tendance se traduit par une interaction entre une collection traversable (comme Seq ou Option) et un foncteur applicative (comme Failure \/ ?).

La mise en œuvre générique (à l'aide scalaz) est alors

import scalaz._ 
import scalaz.syntax.traverse._ 

def f[F[_]: Traverse, G[_]: Applicative, T](a: F[G[T]]): G[F[T]] = a.sequence 

Sur le site d'appel, vous feriez

import scalaz.std._ 

type FailureOr[A] = Failure \/ A 

val x: Option[FailureOr[Int]] = ??? 
val y: List[FailureOr[Int]] = ??? 
val z: Vector[FailureOr[Int]] = ??? 

f[Option, FailureOr, Int](x) 
f[List, FailureOr, Int](y) 
f[Vector, FailureOr, Int](z) 

// or just directly 

import scalaz.syntax.traverse._ 

x.sequence 
y.sequence 
z.sequence 

Notez que je List et Vector au lieu de Seq. Cela est dû au fait que scalaz ne fournit pas d'instance implicite Traverse pour Seq. Alors que conceptuellement Seq est traversable, il est préférable (pour des raisons de performances) d'implémenter les opérations Traverse spécifiquement pour des implémentations concrètes de Seq telles que List ou Vector. Si vous voulez vraiment, vous pouvez écrire votre propre instance de Traverse[Seq], sachez juste qu'il sera sous-optimal pour certaines implémentations de Seq.