scala - classes - ¿Cómo declarar rasgos tomando los "parámetros de constructor" implícitos?
introduction to scala (5)
Estoy diseñando una jerarquía de clases, que consiste en una clase base junto con varios rasgos. La clase base proporciona implementaciones predeterminadas de varios métodos, y los rasgos anulan selectivamente ciertos métodos a través de abstract override
, para que actúen como rasgos apilables / mixins.
Desde una perspectiva de diseño, esto funciona bien, y se mapea al dominio para que pueda agregar una función de filtrado desde aquí (un rasgo) con un predicado desde aquí (otro rasgo), etc.
Sin embargo, ahora me gustaría que algunos de mis rasgos tomen parámetros implícitos. Estoy feliz de que esto todavía tenga sentido desde el punto de vista del diseño, y no resultaría confuso en la práctica. Sin embargo, no puedo convencer al compilador de que corra con él.
El núcleo del problema parece ser que no puedo proporcionar argumentos de constructor para un rasgo, de modo que puedan marcarse implícitamente. Al hacer referencia al parámetro implícito dentro de una implementación de método no se puede compilar con el mensaje esperado "no se pudo encontrar el valor implícito"; Traté de "propagar" lo implícito de la etapa de construcción (donde, en la práctica, siempre está en el alcance) para estar disponible dentro del método a través de
implicit val e = implicitly[ClassName]
pero (como sin duda muchos de ustedes esperan) esa definición falló con el mismo mensaje.
Parece que el problema aquí es que no puedo convencer al compilador de que marque la firma del rasgo con un indicador de implicit ClassName
y fuerce a los que llaman (es decir, los que mezclan el rasgo en un objeto) a proporcionar el implícito. Actualmente mis llamadores lo están haciendo, pero el compilador no está revisando en este nivel.
¿Hay alguna forma de marcar un rasgo que requiera ciertas implícitas disponibles en el momento de la construcción?
(Y si no, ¿esto simplemente no se implementa aún o hay una razón más profunda por la cual esto no es práctico?)
Como parece que esto no es posible, opté por la opción de declarar el val
implícito en el constructor de la clase base. Como se señaló en la pregunta, esto no es ideal, pero satisface al compilador y, pragmáticamente, no es una carga demasiado grande en mi caso particular.
Si alguien tiene una mejor solución, me encantaría escucharla y aceptarla.
En realidad, he querido esto bastante a menudo antes, pero solo se me ocurrió esta idea. Puedes traducir
trait T(implicit impl: ClassName) {
def foo = ... // using impl here
}
a [EDITADO: la versión original no proporcionaba acceso implícito para otros métodos]
trait T {
// no need to ever use it outside T
protected case class ClassNameW(implicit val wrapped: ClassName)
// normally defined by caller as val implWrap = ClassNameW
protected val implWrap: ClassNameW
// will have to repeat this when you extend T and need access to the implicit
import implWrap.wrapped
def foo = ... // using wrapped here
}
Esto no es posible
Pero puedes usar implicitly
y la inferencia de tipo de Scala para que esto sea lo menos doloroso posible.
trait MyTrait {
protected[this] implicit def e: ClassName
}
y entonces
class MyClass extends MyTrait {
protected[this] val e = implicitly // or def
}
Sucinta, y ni siquiera requiere escribir el tipo en la clase extendida.
Me encontré con este problema varias veces, y de hecho es un poco molesto, pero no demasiado. Los miembros y parámetros abstractos son generalmente dos formas alternativas de hacer lo mismo, con sus ventajas y desventajas; para los rasgos que tienen un miembro abstracto no es demasiado inconveniente, porque de todos modos se necesita otra clase para implementar el rasgo. *
Por lo tanto, simplemente debe tener una declaración de valor abstracta en el rasgo, de modo que las clases implementadoras tengan que proporcionar un implícito para usted. Vea el siguiente ejemplo, que compila correctamente y muestra dos formas de implementar el rasgo dado:
trait Base[T] {
val numT: Ordering[T]
}
/* Here we use a context bound, thus cannot specify the name of the implicit
* and must define the field explicitly.
*/
class Der1[T: Ordering] extends Base[T] {
val numT = implicitly[Ordering[T]]
//Type inference cannot figure out the type parameter of implicitly in the previous line
}
/* Here we specify an implicit parameter, but add val, so that it automatically
* implements the abstract value of the superclass.
*/
class Der2[T](implicit val numT: Ordering[T]) extends Base[T]
La idea básica que muestro también está presente en la respuesta de Knut Arne Vedaa, pero traté de hacer un ejemplo más convincente y conveniente, eliminando el uso de funciones innecesarias.
* Esta no es la razón por la cual el rasgo no puede aceptar parámetros, no lo sé. Solo estoy argumentando que la limitación es aceptable en este caso.
Podrías hacerlo así:
abstract class C
trait A { this: C =>
val i: Int
}
implicit val n = 3
val a = new C with A {
val i = implicitly[Int]
}
Pero no estoy seguro de si hay algún punto en ello; podría hacer referencia al valor implícito explícitamente.
Supongo que lo que quieres es deshacerte de la implementación de i
en la instanciación, pero como dices tú mismo, el núcleo del problema es que los rasgos no toman los parámetros del constructor, ya sea que estén implícitos o no, no importa. .
Una posible solución para este problema sería agregar una nueva característica a la sintaxis ya válida:
trait A {
implicit val i: Int
}
donde sería implementado por el compilador si un implícito estaba en el alcance.