Buen ejemplo de parámetro implícito en Scala?


Hasta ahora los parámetros implícitos en Scala no se ven bien para mí is está demasiado cerca de las variables globales, sin embargo, como Scala parece un lenguaje bastante estricto, empiezo a dudar en mi propia opinión :-).

Pregunta: podría mostrar un buen ejemplo de la vida real (o cercano) cuando los parámetros implícitos realmente funcionan. IOW: algo más serio que showPrompt, que justificaría tal diseño de lenguaje.

O al contrario could ¿podría mostrar un diseño de lenguaje confiable (puede ser imaginario) que haría implícito no necesario. Creo que incluso ningún mecanismo es mejor que implicits porque el código es más claro y no hay conjeturas.

Tenga en cuenta, estoy preguntando por parámetros, no funciones implícitas (conversiones)!

Actualizaciones

Variables globales

Gracias por todas las grandes respuestas. Tal vez aclare mi objeción de "variables globales". Considere tal función:

max(x : Int,y : Int) : Int

Lo llamas

max(5,6);

Usted podría (!) do it like esto:

max(x:5,y:6);

Pero en mis ojos implicits funciona así:

x = 5;
y = 6;
max()

No es muy diferente de dicha construcción (similar a PHP)

max() : Int
{
  global x : Int;
  global y : Int;
  ...
}

Respuesta de Derek

Este es un gran ejemplo, sin embargo, si puede pensar en un uso flexible de enviar mensajes que no usan implicit, por favor publique un contraejemplo. Tengo mucha curiosidad por la pureza en el diseño del lenguaje ;-).

Author: greenoldman, 2012-03-02

8 answers

En cierto sentido, sí, los implicitos representan el estado global. Sin embargo, no son mutables, que es el verdadero problema con las variables globales you no ves a la gente quejándose de las constantes globales, ¿verdad? De hecho, los estándares de codificación generalmente dictan que transformes cualquier constante en tu código en constantes o enums, que generalmente son globales.

Tenga en cuenta también que los implicitos son no en un espacio de nombres plano, lo que también es un problema común con las globales. Están explícitamente vinculados a tipos y, por lo tanto, a la jerarquía de paquetes de esos tipos.

Por lo tanto, tome sus globales, hágalos inmutables e inicializados en el sitio de declaración, y colóquelos en espacios de nombres. ¿Todavía parecen globales? ¿Todavía parecen problemáticos?

Pero no nos detengamos ahí. Los implicitos están vinculados a los tipos, y son tan "globales" como los tipos. ¿Te molesta el hecho de que los tipos son globales?

En cuanto a los casos de uso, son muchos, pero podemos hacer un breve revisión basada en su historia. Originalmente, afaik, Scala no tenía implicitos. Lo que Scala tenía eran tipos de vista, una característica que muchos otros idiomas tenían. Todavía podemos ver que hoy en día cada vez que escribes algo como T <% Ordered[T], lo que significa que el tipo T se puede ver como un tipo Ordered[T]. Los tipos de vista son una forma de hacer que los casts automáticos estén disponibles en los parámetros de tipo (genéricos).

Scala entonces generalizó esa característica con implicitos. Los moldes automáticos ya no existen, y, en su lugar, conversiones implícitas which que son solo valores Function1 y, por lo tanto, se pueden pasar como parámetros. A partir de entonces, T <% Ordered[T] significaba que un valor para una conversión implícita se pasaría como parámetro. Dado que el cast es automático, no se requiere que el llamante de la función pase explícitamente el parámetro so por lo que esos parámetros se convirtieron en parámetros implícitos.

Tenga en cuenta que hay dos conceptos conversions conversiones implícitas y parámetros implícitos that que están muy cerca, pero no se superponen completamente.

De todos modos, los tipos de vista se convirtieron en azúcar sintáctica para las conversiones implícitas que se pasan implícitamente. Se reescribirían así:

def max[T <% Ordered[T]](a: T, b: T): T = if (a < b) b else a
def max[T](a: T, b: T)(implicit $ev1: Function1[T, Ordered[T]]): T = if ($ev1(a) < b) b else a

Los parámetros implícitos son simplemente una generalización de ese patrón, lo que permite pasar cualquier tipo de de parámetros implícitos, en lugar de solo Function1. El uso real para ellos luego siguió, y el azúcar sintáctico para esos usos vino después.

Uno de ellos es Límites de contexto, se usa para implementar la clase de tipo pattern (pattern porque no es una característica incorporada, solo una forma de usar el lenguaje que proporciona una funcionalidad similar a la clase de tipo de Haskell). Un contexto enlazado se utiliza para proporcionar un adaptador que implementa la funcionalidad que es inherente a una clase, pero no declarada por ella. Ofrece los beneficios de la herencia y las interfaces sin sus inconvenientes. Por ejemplo:

def max[T](a: T, b: T)(implicit $ev1: Ordering[T]): T = if ($ev1.lt(a, b)) b else a
// latter followed by the syntactic sugar
def max[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a

Probablemente ya hayas usado eso there hay un uso común caso que la gente no suele notar. Es esto:

new Array[Int](size)

Que utiliza un contexto enlazado de manifiestos de clase, para habilitar dicha inicialización de matriz. Podemos ver que con este ejemplo:

def f[T](size: Int) = new Array[T](size) // won't compile!

Puedes escribirlo así:

def f[T: ClassManifest](size: Int) = new Array[T](size)

En la biblioteca estándar, los límites de contexto más utilizados son:

Manifest      // Provides reflection on a type
ClassManifest // Provides reflection on a type after erasure
Ordering      // Total ordering of elements
Numeric       // Basic arithmetic of elements
CanBuildFrom  // Collection creation

Los tres últimos se utilizan principalmente con colecciones, con métodos como max, sum y map. Una biblioteca que hace un uso extensivo de los límites de contexto es Scalaz.

Otro uso común es disminuir la placa de caldera en operaciones que deben compartir un parámetro común. Por ejemplo, transacciones:

def withTransaction(f: Transaction => Unit) = {
  val txn = new Transaction

  try { f(txn); txn.commit() }
  catch { case ex => txn.rollback(); throw ex }
}

withTransaction { txn =>
  op1(data)(txn)
  op2(data)(txn)
  op3(data)(txn)
}

Que luego se simplifica así:

withTransaction { implicit txn =>
  op1(data)
  op2(data)
  op3(data)
}

Este patrón se usa con la memoria transaccional, y creo (pero no estoy seguro) que la biblioteca de E/S de Scala también lo usa.

El tercer uso común que se me ocurre es hacer pruebas sobre los tipos que se están pasando, lo que hace posible detectar al compilar tiempo cosas que, de lo contrario, resultarían en excepciones en tiempo de ejecución. Por ejemplo, vea esta definición en Option:

def flatten[B](implicit ev: A <:< Option[B]): Option[B]

Eso hace esto posible:

scala> Option(Option(2)).flatten // compiles
res0: Option[Int] = Some(2)

scala> Option(2).flatten // does not compile!
<console>:8: error: Cannot prove that Int <:< Option[B].
              Option(2).flatten // does not compile!
                        ^

Una biblioteca que hace un uso extensivo de esa característica no tiene forma.

No creo que el ejemplo de la biblioteca Akka encaje en ninguna de estas cuatro categorías, pero ese es el punto de las características genéricas: la gente puede usarla de todo tipo de maneras, en lugar de las prescritas por el diseñador del lenguaje.

Si te gusta que te receten (como, por ejemplo, Python), entonces Scala no es para ti.

 93
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-03 21:55:31

Claro. Akka tiene un gran ejemplo de ello con respecto a sus Actores. Cuando estás dentro del método receive de un Actor, es posible que quieras enviar un mensaje a otro Actor. Al hacer esto, Akka agrupará (por defecto) el Actor actual como el sender del mensaje, así:

trait ScalaActorRef { this: ActorRef =>
  ...

  def !(message: Any)(implicit sender: ActorRef = null): Unit

  ...
}

El sender está implícito. En el Actor hay una definición que se parece a:

trait Actor {
  ...

  implicit val self = context.self

  ...
}

Esto crea el valor implícito dentro del alcance de su propio código, y le permite hacer cosas fáciles así:

someOtherActor ! SomeMessage

Ahora, puedes hacer esto también, si quieres:

someOtherActor.!(SomeMessage)(self)

O

someOtherActor.!(SomeMessage)(null)

O

someOtherActor.!(SomeMessage)(anotherActorAltogether)

Pero normalmente no lo hace. Solo mantiene el uso natural que es posible gracias a la definición de valor implícita en el rasgo Actor. Hay alrededor de un millón de otros ejemplos. Las clases de colección son enormes. Intente vagar por cualquier biblioteca Scala no trivial y encontrará un camión cargado.

 21
Author: Derek Wyatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-02 09:52:54

Un ejemplo serían las operaciones de comparación en Traversable[A]. Por ejemplo, max o sort:

def max[B >: A](implicit cmp: Ordering[B]) : A

Estos solo se pueden definir con sensatez cuando hay una operación < en A. Por lo tanto, sin implicits tendríamos que suministrar el contexto Ordering[B] cada vez que nos gustaría utilizar esta función. (O abandone la comprobación estática de tipo dentro de max y corra el riesgo de un error de conversión en tiempo de ejecución.)

Sin embargo, si una comparación implícita tipo clase está en el alcance, por ejemplo, algunos Ordering[Int], podemos usarlo de inmediato o simplemente cambie el método de comparación proporcionando algún otro valor para el parámetro implícito.

Por supuesto, los implicitos pueden ser sombreados y, por lo tanto, puede haber situaciones en las que lo implícito real que está en el alcance no es lo suficientemente claro. Para usos simples de max o sort podría ser suficiente tener un orden fijo trait en Int y usar alguna sintaxis para verificar si este rasgo está disponible. Pero esto significaría que no podría haber rasgos adicionales y cada pieza de código tendría que usar los rasgos que se definieron originalmente.

Además:
Respuesta a la variable global comparación.

Creo que tienes razón en que en un código recortado como {[19]]}

implicit val num = 2
implicit val item = "Orange"
def shopping(implicit num: Int, item: String) = {
  "I’m buying "+num+" "+item+(if(num==1) "." else "s.")
}

scala> shopping
res: java.lang.String = I’m buying 2 Oranges.

Puede oler a variables globales podridas y malvadas. El punto crucial, sin embargo, es que solo puede haber una variable implícita por tipo en el alcance. Tu ejemplo con dos Ints no va a funcionar.

También, esto significa que prácticamente, las variables implícitas se emplean solo cuando hay una instancia primaria no necesariamente única pero distinta para un tipo. La referencia self de un actor es un buen ejemplo para tal cosa. El ejemplo de clase de tipo es otro ejemplo. Puede haber docenas de comparaciones algebraicas para cualquier tipo, pero hay una que es especial. (En otro nivel, el número de línea real en el código mismo también podría ser una buena variable implícita siempre y cuando use una variable muy distintiva tipo.)

Normalmente no se usan implicit s para los tipos cotidianos. Y con tipos especializados (como Ordering[Int]) no hay demasiado riesgo en seguirlos.

 9
Author: Debilski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-02 12:18:45

Otro buen uso general de los parámetros implícitos es hacer que el tipo de retorno de un método dependa del tipo de algunos de los parámetros que se le pasan. Un buen ejemplo, mencionado por Jens, es el framework de colecciones, y métodos como map, cuya firma completa generalmente es:

def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[GenSeq[A], B, That]): That

Tenga en cuenta que el tipo de retorno That está determinado por el mejor ajuste CanBuildFrom que el compilador puede encontrar.

Para otro ejemplo de esto, ver esa respuesta. Allí, el tipo de retorno de la el método Arithmetic.apply se determina de acuerdo con un cierto tipo de parámetro implícito (BiConverter).

 4
Author: Jean-Philippe Pellet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:31

Es fácil, solo recuerda:

  • para declarar la variable a pasar como implícita también
  • para declarar todos los parámetros implícitos después de los parámetros no implícitos en un ()

Por ejemplo

def myFunction(): Int = {
  implicit val y: Int = 33
  implicit val z: Double = 3.3

  functionWithImplicit("foo") // calls functionWithImplicit("foo")(y, z)
}

def functionWithImplicit(foo: String)(implicit x: Int, d: Double) = // blar blar
 4
Author: samthebest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-21 17:35:38

Basado en mi experiencia no hay un buen ejemplo real para el uso de parámetros implicita o implicita conversión.

El pequeño beneficio de usar implicits (sin necesidad de escribir explícitamente un parámetro o un tipo) es redundante en comparación con los problemas que crean.

Soy desarrollador desde hace 15 años, y he estado trabajando con scala durante los últimos 1.5 años.

He visto muchas veces errores que fueron causados por el desarrollador no consciente del hecho de que implicits se utilizan, y que una función específica realmente devuelve un tipo diferente que el especificado. Debido a la conversión implícita.

También escuché declaraciones diciendo que si no te gustan los implicitos, no los uses. Esto no es práctico en el mundo real ya que muchas veces se usan bibliotecas externas, y muchas de ellas están usando implicitos, por lo que su código usa implicitos, y puede que no sea consciente de eso. Puedes escribir un código que tenga:

import org.some.common.library.{TypeA, TypeB}

O:

import org.some.common.library._

Ambos códigos compilarán y ejecutar. Pero no siempre producirán los mismos resultados ya que la segunda versión importada implica una conversión que hará que el código se comporte de manera diferente.

El 'bug' que es causado por esto puede ocurrir mucho tiempo después de que el código fue escrito, en caso de que algunos valores que son afectados por esta conversión no fueron utilizados originalmente.

Una vez que encuentre el error, no es una tarea fácil encontrar la causa. Tienes que hacer una investigación profunda.

Aunque te sientas como un experto en scala una vez que haya encontrado el error y lo haya solucionado cambiando una declaración de importación, realmente perdió mucho tiempo precioso.

Otras razones por las que generalmente estoy en contra de los implicitos son:

  • Hacen que el código sea difícil de entender (hay menos código, pero no sabes lo que está haciendo)
  • Tiempo de compilación. el código scala compila mucho más lento cuando se usan implicitos.
  • En la práctica, cambia el lenguaje de tipo estático a dinámico escribir. Es cierto que una vez que siga pautas de codificación muy estrictas puede evitar este tipo de situaciones, pero en el mundo real, no siempre es el caso. Incluso usando el IDE 'remove unused imports', puede causar que su código aún compile y se ejecute, pero no lo mismo que antes de eliminar las importaciones 'unused'.

No hay ninguna opción para compilar scala sin implicitos (si hay, por favor corríjame), y si hubiera una opción, ninguna de las bibliotecas comunes de scala de la comunidad tendría que compilar.

Por todas las razones anteriores, creo que los implicitos son una de las peores prácticas que utiliza el lenguaje scala.

Scala tiene muchas grandes características, y muchas no tan grandes.

Al elegir un lenguaje para un nuevo proyecto, los implicitos son una de las razones en contra de scala, no a favor de ella. En mi opinión.

 4
Author: noam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-03 10:10:03

Los parámetros implícitos se usan mucho en la API de colección. Muchas funciones obtienen un CanBuildFrom implícito, lo que garantiza que obtenga la mejor implementación de la colección de resultados.

Sin implicitos, o bien pasarías tal cosa todo el tiempo, lo que haría que el uso normal fuera engorroso. O use colecciones menos especializadas, lo que sería molesto porque significaría que pierde rendimiento/potencia.

 3
Author: Jens Schauder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-02 09:49:28

Estoy comentando este post un poco tarde, pero he empezado a aprender scala últimamente. Daniel y otros han dado buenos antecedentes sobre la palabra clave implícita. Me daría dos centavos en variable implícita desde la perspectiva del uso práctico.

Scala es más adecuado si se usa para escribir códigos de Apache Spark. En Spark, tenemos el contexto de spark y muy probablemente la clase de configuración que puede obtener las claves/valores de configuración de un archivo de configuración.

Ahora, Si tengo un clase abstracta y si declaro un objeto de configuración y contexto spark de la siguiente manera: -

abstract class myImplicitClass {

implicit val config = new myConfigClass()

val conf = new SparkConf().setMaster().setAppName()
implicit val sc = new SparkContext(conf)

def overrideThisMethod(implicit sc: SparkContext, config: Config) : Unit
}

class MyClass extends myImplicitClass {

override def overrideThisMethod(implicit sc: SparkContext, config: Config){

/*I can provide here n number of methods where I can pass the sc and config 
objects, what are implicit*/
def firstFn(firstParam: Int) (implicit sc: SparkContext, config: Config){ 
    /*I can use "sc" and "config" as I wish: making rdd or getting data from cassandra, for e.g.*/
    val myRdd = sc.parallelize(List("abc","123"))
}
def secondFn(firstParam: Int) (implicit sc: SparkContext, config: Config){
 /*following are the ways we can use "sc" and "config" */

        val keyspace = config.getString("keyspace")
        val tableName = config.getString("table")
        val hostName = config.getString("host")
        val userName = config.getString("username")
        val pswd = config.getString("password")

    implicit val cassandraConnectorObj = CassandraConnector(....)
    val cassandraRdd = sc.cassandraTable(keyspace, tableName)
}

}
}

Como podemos ver en el código anterior, tengo dos objetos implícitos en mi clase abstracta, y he pasado esas dos variables implícitas como parámetros implícitos de función/método/definición. Creo que este es el mejor caso de uso que podemos representar en términos de uso de variables implícitas.

 0
Author: anshuman sharma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-20 10:32:47