example def conversion scala coding-style implicit

def - scala implicit conversion



La codificación con Scala implica en estilo (3)

¿Hay guías de estilo que describan cómo escribir código usando implícitos de Scala?

Los implícitos son realmente poderosos, y por lo tanto pueden ser fácilmente abusados. ¿Hay algunas pautas generales que dicen cuándo las implicaciones son apropiadas y cuándo usarlas oscurece el código?


Este es tan poco conocido que aún no se le ha dado un nombre (que yo sepa), pero ya está firmemente establecido como uno de mis favoritos personales.

Así que voy a arriesgarme aquí, y lo denominaré patrón de " proxeneta de mi clase de tipos ". Quizás a la comunidad se le ocurra algo mejor.

Este es un patrón de 3 partes, construido enteramente de implícitos. También se usa en la biblioteca estándar (desde 2.9). Explicado aquí a través de la clase de tipo Numeric muy reducida, que con suerte debería ser familiar.

Parte 1 - Crear una clase de tipo

trait Numeric[T] { def plus(x: T, y: T): T def minus(x: T, y: T): T def times(x: T, y: T): T //... } implicit object ShortIsNumeric extends Numeric[Short] { def plus(x: Short, y: Short): Short = (x + y).toShort def minus(x: Short, y: Short): Short = (x - y).toShort def times(x: Short, y: Short): Short = (x * y).toShort //... } //...

Parte 2: agregar una clase anidada que proporciona operaciones de infijo

trait Numeric[T] { // ... class Ops(lhs: T) { def +(rhs: T) = plus(lhs, rhs) def -(rhs: T) = minus(lhs, rhs) def *(rhs: T) = times(lhs, rhs) // ... } }

Parte 3 - Pimp miembros de la clase de tipo con las operaciones

implicit def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#Ops = new num.Ops(x)

Entonces utilízalo

def addAnyTwoNumbers[T: Numeric](x: T, y: T) = x + y

Código completo:

object PimpTypeClass { trait Numeric[T] { def plus(x: T, y: T): T def minus(x: T, y: T): T def times(x: T, y: T): T class Ops(lhs: T) { def +(rhs: T) = plus(lhs, rhs) def -(rhs: T) = minus(lhs, rhs) def *(rhs: T) = times(lhs, rhs) } } object Numeric { implicit object ShortIsNumeric extends Numeric[Short] { def plus(x: Short, y: Short): Short = (x + y).toShort def minus(x: Short, y: Short): Short = (x - y).toShort def times(x: Short, y: Short): Short = (x * y).toShort } implicit def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#Ops = new num.Ops(x) def addNumbers[T: Numeric](x: T, y: T) = x + y } } object PimpTest { import PimpTypeClass.Numeric._ def main(args: Array[String]) { val x: Short = 1 val y: Short = 2 println(addNumbers(x, y)) } }


No creo que haya encontrado nada, ¡así que creémoslo aquí! Algunas reglas de oro:

Conversiones implícitas

Cuando se convierte implícitamente de A a B donde no es el caso de que cada A pueda verse como una B , hágalo a través de una conversión de toX , o algo similar. Por ejemplo:

val d = "20110513".toDate //YES val d : Date = "20110513" //NO!

¡No te vuelvas loco! ¡Úsalo para la funcionalidad de biblioteca central muy común , en lugar de en cada clase para pinchar algo por el bien de eso!

val (duration, unit) = 5.seconds //YES val b = someRef.isContainedIn(aColl) //NO! aColl exists_? aPred //NO! - just use "exists"

Parámetros implícitos

Use estos para cualquiera de los dos:

  • proporcionar instancias de typeclass (como scalaz )
  • inyectar algo obvio (como proporcionar un Servicio de ExecutorService a alguna invocación de un trabajador)
  • como una versión de inyección de dependencia (por ejemplo, propagar la configuración de campos de tipo de servicio en instancias)

¡No lo uses por la pereza !


No creo que haya un estilo de comunidad todavía. He visto muchas convenciones. Describiré el mío y explicaré por qué lo uso.

Nombrar

Yo llamo una de mis conversiones implícitas

implicit def whatwehave_to_whatwegenerate implicit def whatwehave_whatitcando implicit def whatwecandowith_whatwehave

No espero que estos se utilicen explícitamente, así que tiendo a nombres bastante largos. Desafortunadamente, hay números en los nombres de las clases con suficiente frecuencia, por lo que la convención de lo que whatwehave2whatwegenerate lo que es whatwehave2whatwegenerate se vuelve confusa. Por ejemplo: tuple22myclass Tuple22 ¿es de Tuple22 que estás hablando de Tuple2 o Tuple22 ?

Si la conversión implícita se define fuera del argumento y del resultado de la conversión, siempre utilizo la notación x_to_y para mayor claridad. De lo contrario, veo el nombre más como un comentario. Así, por ejemplo, en

class FoldingPair[A,B](t2: (A,B)) { def fold[Z](f: (A,B) => Z) = f(t2._1, t2._2) } implicit def pair_is_foldable[A,B](t2: (A,B)) = new FoldingPair(t2)

Utilizo tanto el nombre de la clase como el implícito como una especie de comentario sobre el punto del código, es decir, para agregar un método de fold a los pares (es decir, Tuple2 ).

Uso

Pimp-My-Library

Utilizo las conversiones implícitas más para las construcciones de estilo pimp-my-library. Hago esto en todas partes donde se agrega la funcionalidad faltante o hace que el código resultante se vea más limpio.

val v = Vector(Vector("This","is","2D" ... val w = v.updated(2, v(2).updated(5, "Hi")) // Messy! val w = change(v)(2,5)("Hi") // Okay, better for a few uses val w = v change (2,5) -> "Hi" // Arguably clearer, and... val w = v change ((2,5) -> "Hi", (2,6) -> "!")) // extends naturally to this!

Ahora, hay una penalización de rendimiento para pagar las conversiones implícitas, por lo que no escribo código en puntos de acceso de esta manera. Pero de lo contrario, es muy probable que use un patrón de pimp-my-library en lugar de una definición una vez que vaya por encima de un puñado de usos en el código en cuestión.

Hay otra consideración, y es que las herramientas no son tan confiables como para mostrar de dónde provienen sus conversiones implícitas y de dónde provienen sus métodos. Por lo tanto, si estoy escribiendo un código que es difícil, y espero que cualquier persona que lo use o lo tenga que mantener, tendrá que estudiarlo para comprender qué se requiere y cómo funciona, I - y esto es casi al revés. Una filosofía típica de Java: es más probable que utilicen PML de esta manera para que los pasos sean más transparentes para un usuario capacitado. Los comentarios advertirán que el código debe ser entendido profundamente; Una vez que entiendes profundamente, estos cambios ayudan en lugar de herir. Si, por otro lado, el código está haciendo algo relativamente sencillo, es más probable que deje las definiciones en su lugar ya que los IDE me ayudarán a mí oa otros a acelerar la velocidad si necesitamos hacer un cambio.

Evitar conversiones explícitas.

Intento evitar conversiones explícitas. Ciertamente puedes escribir

implicit def string_to_int(s: String) = s.toInt

pero es terriblemente peligroso, incluso si parece que le está salpicando todas las cuerdas con .toInt.

La principal excepción que hago es para las clases de envoltura. Supongamos, por ejemplo, que desea que un método tome clases con un código hash precalculado. me gustaría

class Hashed[A](private[Hashed] val a: A) { override def equals(o: Any) = a == o override def toString = a.toString override val hashCode = a.## } object Hashed { implicit def anything_to_hashed[A](a: A) = new Hashed(a) implicit def hashed_to_anything[A](h: Hashed[A]) = h.a }

y recupere cualquier clase con la que comencé automáticamente o, en el peor de los casos, agregando una anotación de tipo (por ejemplo, x: String ). La razón es que esto hace que las clases de envoltorio sean mínimamente intrusivas. Realmente no quieres saber sobre el envoltorio; Solo necesitas la funcionalidad a veces. No se puede evitar por completo el envoltorio (por ejemplo, solo se pueden arreglar los iguales en una dirección y, a veces, es necesario volver al tipo original). Pero esto a menudo le permite escribir código con un mínimo esfuerzo, que a veces es lo que hay que hacer.

Parámetros implícitos

Los parámetros implícitos son alarmantemente promiscuos. Utilizo valores predeterminados siempre que puedo en su lugar. Pero a veces no puedes, especialmente con código genérico.

Si es posible, trato de hacer que el parámetro implícito sea algo que ningún otro método usaría jamás. Por ejemplo, la biblioteca de colecciones de Scala tiene una clase CanBuildFrom que es casi perfectamente inútil como algo más que un parámetro implícito a los métodos de colecciones. Así que hay muy poco peligro de interferencia involuntaria.

Si esto no es posible, por ejemplo, si un parámetro debe pasarse a varios métodos diferentes, pero al hacerlo realmente distrae la atención de lo que hace el código (por ejemplo, tratando de hacer el registro en el medio de la aritmética), entonces en lugar de hacer una clase común (por ejemplo, una String ) es el valor implícito, lo envuelvo en una clase de marcador (generalmente con una conversión implícita).