Usar límites de contexto "negativamente" para garantizar que la instancia de clase de tipo esté ausente del ámbito
tl; dr: ¿Cómo hago algo como el código hecho a continuación?
def notFunctor[M[_] : Not[Functor]](m: M[_]) = s"$m is not a functor"
Los 'Not[Functor]
', siendo la parte hecha aquí.
Quiero que tenga éxito cuando la 'm' proporcionada no es un Functor, y falla el compilador de lo contrario.
Resuelto: omita el resto de la pregunta y continúe con la siguiente respuesta.
Lo que estoy tratando de lograr es, en términos generales, "evidencia negativa".
El pseudo código se vería así:
// type class for obtaining serialization size in bytes.
trait SizeOf[A] { def sizeOf(a: A): Long }
// type class specialized for types whose size may vary between instances
trait VarSizeOf[A] extends SizeOf[A]
// type class specialized for types whose elements share the same size (e.g. Int)
trait FixedSizeOf[A] extends SizeOf[A] {
def fixedSize: Long
def sizeOf(a: A) = fixedSize
}
// SizeOf for container with fixed-sized elements and Length (using scalaz.Length)
implicit def fixedSizeOf[T[_] : Length, A : FixedSizeOf] = new VarSizeOf[T[A]] {
def sizeOf(as: T[A]) = ... // length(as) * sizeOf[A]
}
// SizeOf for container with scalaz.Foldable, and elements with VarSizeOf
implicit def foldSizeOf[T[_] : Foldable, A : SizeOf] = new VarSizeOf[T[A]] {
def sizeOf(as: T[A]) = ... // foldMap(a => sizeOf(a))
}
Manten eso en mentefixedSizeOf()
es preferible cuando sea relevante, ya que nos ahorra el recorrido sobre la colección.
De esta manera, para tipos de contenedores donde soloLength
se define (pero noFoldable
), y para elementos donde unFixedSizeOf
Se define, obtenemos un rendimiento mejorado.
Para el resto de los casos, repasamos la colección y sumamos los tamaños individuales.
Mi problema es en los casos donde ambosLength
yFoldable
se definen para el contenedor, yFixedSizeOf
Se define para los elementos. Este es un caso muy común aquí (por ejemplo,:List[Int]
tiene ambos definidos).
Ejemplo:
scala> implicitly[SizeOf[List[Int]]].sizeOf(List(1,2,3))
<console>:24: error: ambiguous implicit values:
both method foldSizeOf of type [T[_], A](implicit evidence$1: scalaz.Foldable[T], implicit evidence$2: SizeOf[A])VarSizeOf[T[A]]
and method fixedSizeOf of type [T[_], A](implicit evidence$1: scalaz.Length[T], implicit evidence$2: FixedSizeOf[A])VarSizeOf[T[A]]
match expected type SizeOf[List[Int]]
implicitly[SizeOf[List[Int]]].sizeOf(List(1,2,3))
Lo que me gustaría es poder confiar en elFoldable
tipo de clase solo cuando elLength
+FixedSizeOf
La combinación no se aplica.
Para ello, puedo cambiar la definición defoldSizeOf()
aceptarVarSizeOf
elementos:
implicit def foldSizeOfVar[T[_] : Foldable, A : VarSizeOf] = // ...
Y ahora tenemos que rellenar la parte problemática que cubre.Foldable
contenedores conFixedSizeOf
elementos ynoLength
definido. No estoy seguro de cómo abordar esto, pero el pseudocódigo se vería algo así como:
implicit def foldSizeOfFixed[T[_] : Foldable : Not[Length], A : FixedSizeOf] = // ...
Los 'Not[Length]
', obviamente, siendo la parte hecha aquí.
Soluciones parciales que conozco
1) Defina una clase para implicaciones de baja prioridad y extiéndala, como se ve en 'object Predef extends LowPriorityImplicits
'. El último implícito (foldSizeOfFixed()
) se puede definir en la clase padre y se anulará por alternativa a la clase descendiente.
No estoy interesado en esta opción porque me gustaría poder eventualmente admitir el uso recursivo deSizeOf
, y esto evitará que lo implícito en la clase base de prioridad baja confíe en los de la subclase (¿es correcto mi entendimiento aquí? EDIT: ¡equivocado! ¡la búsqueda implícita funciona desde el contexto de la subclase, esta es una solución viable!)
2) Un enfoque más áspero se basa enOption[TypeClass]
(p.ej.,:Option[Length[List]]
. Algunos de esos y yo solo puedo escribir un gran ol implícito que escogeFoldable
ySizeOf
como obligatorio yLength
yFixedSizeOf
como opcional, y depende de este último si están disponibles. (fuente:aquí)
Los dos problemas aquí son la falta de modularidad y el retroceso a las excepciones de tiempo de ejecución cuando no se pueden localizar instancias de clase de tipo relevantes (este ejemplo probablemente puede funcionar para esta solución, pero eso no siempre es posible)
EDITAR: Esto es lo mejor que pude obtener con implícitos opcionales. Aún no está allí
implicit def optionalTypeClass[TC](implicit tc: TC = null) = Option(tc)
type OptionalLength[T[_]] = Option[Length[T]]
type OptionalFixedSizeOf[T[_]] = Option[FixedSizeOf[T]]
implicit def sizeOfContainer[
T[_] : Foldable : OptionalLength,
A : SizeOf : OptionalFixedSizeOf]: SizeOf[T[A]] = new SizeOf[T[A]] {
def sizeOf(as: T[A]) = {
// optionally calculate using Length + FixedSizeOf is possible
val fixedLength = for {
lengthOf <- implicitly[OptionalLength[T]]
sizeOf <- implicitly[OptionalFixedSizeOf[A]]
} yield lengthOf.length(as) * sizeOf.fixedSize
// otherwise fall back to Foldable
fixedLength.getOrElse {
val foldable = implicitly[Foldable[T]]
val sizeOf = implicitly[SizeOf[A]]
foldable.foldMap(as)(a => sizeOf.sizeOf(a))
}
}
}
Excepto que esto choca confixedSizeOf()
de antes, que sigue siendo necesario.
Gracias por cualquier ayuda o perspectiva :-)