2010-06-18 29 views
8

Tengo un rasgo en Scala que tiene un único método. Llámelo Computable y el único método es calcular (entrada: Int): Int. No puedo determinar si deboRasgo, FunciónN o Rasgo-heredando-FunciónN en Scala?

  • Déjalo como un rasgo independiente con un único método.
  • Heredar de (Int => Int) y renombrar "calcular" para "aplicar".
  • Deshazte de Computable y usa (Int => Int).

Un factor a favor de que sea un rasgo es que podría agregar algunos métodos adicionales. Pero, por supuesto, si todos fueran implementados en términos del método de cómputo, entonces podría simplemente dividirlos en un objeto separado.

Un factor a favor de simplemente usar el tipo de función es la simplicidad y el hecho de que la sintaxis para una función anónima es más concisa que la de una instancia Computable anónima. Pero entonces no tengo manera de distinguir objetos que en realidad son instancias computables de otras funciones que mapean Int a Int, pero no están destinadas a ser utilizadas en el mismo contexto que Computable.

¿Cómo se acercan otras personas a este tipo de problema? No hay respuestas correctas o incorrectas aquí; Solo estoy buscando consejos.

Respuesta

3

Creación de un rasgo que se extiende desde un tipo de función puede ser útil por un par de razones.

  1. Su objeto función hace algo especial y no evidente (y difícil de escribir), y se puede parametrizar ligeras variaciones de un constructor. Por ejemplo, supongamos que está escribiendo un rasgo para realizar una consulta XPath en un árbol XML. La función aplicar escondería varios tipos de trabajo en la construcción del mecanismo de consulta XPath, pero aún vale la pena implementar la interfaz Function1 para que pueda consultar desde un conjunto completo de nodos usando map o flatMap.

  2. Como una extensión del n. ° 1, quiere hacer algún procesamiento en tiempo de construcción (por ejemplo, analizar la expresión XPath y compilarla para ejecutarla rápidamente), puede hacerlo una vez, en el constructor del objeto (mientras si sólo al curry Function s sin subclases, la compilación sólo podía suceder en tiempo de ejecución, por lo que se repetiría para cada consulta.)

  3. se debe pasar una función de cifrado (un tipo de Function1[String,String]) como implícita, pero no todos Function1[String,String] s realizan cifrado. Al derivar de Function1[String,String] y nombrar la subclase/rasgo EncryptionFunction, puede asegurarse de que solo las funciones de la subclase correcta se aprobarán implícitamente. (Esto no es así al declarar Type EncryptionFunction = String => String.)

Espero que haya sido claro.

1

Parece que es posible que desee utilizar un structural type. También se les llama interfaces implícitas.

Podría entonces refactorizar los métodos que actualmente aceptan un Computable para aceptar cualquier cosa que tenga un método compute(input: Int).

+0

Bien, entonces eso haría que cualquier objeto que tenga un método de cálculo pueda utilizarse como Computable. Pero lo que estaba más interesado en saber es, ¿cuáles son algunas consideraciones a favor o en contra de hacer de Computable un tipo de función? Un problema que he notado es que si Computable es un tipo de función, entonces las clases que lo implementan no pueden ser de otro tipo. Si tuviera otra interfaz, Displayable, heredada de (String => String), una clase no podría ser Computable ni Displayable porque heredaría Function1 de ambos rasgos pero con diferentes parámetros de tipo. –

+1

Correcto, ese es un argumento en contra de extender (o usar directamente) Int => Int. Si espera que ese sea el caso de uso típico, entonces un nombre personalizado podría ser mejor. –

1

Una opción es definir un tipo (aún así puede llamarlo Computable), que en este momento es Int => Int. Úselo cuando necesite las cosas computables. Obtendrá todos los beneficios de heredar de Function1. Entonces, si se da cuenta de que necesita algunos métodos más, puede cambiar el tipo a otro rasgo.

Al principio:

type Computable = Int => Int 

Más adelante:

type Computable = ComputableTrait // with its own methods. 

Una desventaja de esto es que el tipo que ha definido no es realmente un nuevo tipo, más de un tipo de alias. Entonces, hasta que lo cambie a un rasgo, el compilador aún aceptará otras funciones Int => Int. Al menos, usted (el desarrollador) puede diferenciar. Cuando cambia a un rasgo (y la diferencia se vuelve importante) el compilador descubrirá cuándo necesita un Computable pero tiene un Int => Int.

Si desea que el compilador rechace otras Int => Int -s desde el primer día, entonces le recomendaría que use un rasgo, pero extienda Int => Int. Cuando necesite llamarlo, aún tendrá la sintaxis más conveniente.

Otra opción podría ser tener un rasgo y un objeto complementario con un método de aplicación que acepte Int => Int y cree un Computable de eso. Entonces, la creación de Computables nuevos sería casi tan simple como escribir funciones simples anónimas, pero aún tendría la verificación de tipo (que se perdería con la conversión implícita). Además, podría mezclar el rasgo sin problemas (pero luego la aplicación del objeto complementario no se puede usar tal como está).

8

Si lo convierten en un rasgo y todavía quiere ser capaz de utilizar la sintaxis de la función de peso ligero, también se puede añadir, además, una conversión implícita en los lugares donde los desee:

scala> trait Computable extends (Int => Int) 
defined trait Computable 

scala> def computes(c: Computable) = c(5) 
computes: (c: Computable)Int 

scala> implicit def toComputable(f: Int => Int) = new Computable { def apply(i: Int) = f(i) } 
toComputable: (f: (Int) => Int)java.lang.Object with Computable 

scala> computes((i: Int) => i * 2) 
res0: Int = 10 
+2

Estoy usando un enfoque muy similar. Tengo los rasgos 'Extensión de curva (Doble => Doble)' y 'Extensión de superficie ((Doble, Doble) => Doble)', con implicaciones para levantar las funciones normales de scala. Si define la conversión en el objeto complementario de su rasgo 'Computable', se encuentra automáticamente en el ámbito implícito al buscar una vista de cualquier tipo' T' a 'Computable'. – retronym

+0

Esto es de hecho lo que hice. Pero me pregunto qué es lo que el rasgo Computable realmente "me compra". Si siempre lo uso como (Int => Int) entonces ¿para qué molestarse? Por lo que puedo decir, solo tiene un beneficio documental. –

+1

Si solo es para documentación, también podría definir un alias tipo: 'escriba Computable = Int => Int' .. –

Cuestiones relacionadas