2009-07-22 19 views
27

Acabo de empezar a jugar con Scala, y acabo de enterarme de cómo se pueden hacer los métodos asociativo de la derecha (en oposición a la asociación-izquierda más tradicional común en los lenguajes imperativos orientados a objetos).¿De qué sirven los métodos asociativos de derechos en Scala?

Al principio, cuando vi código de ejemplo para cons una lista en la Scala, que se había dado cuenta de que todos los ejemplos siempre tenía la lista en el lado derecho:

println(1 :: List(2, 3, 4)) 
newList = 42 :: originalList 

Sin embargo, incluso después de ver esto una y otra vez, no lo pensé dos veces, porque no sabía (en ese momento) que :: es un método en List. Simplemente asumí que era un operador (nuevamente, en el sentido de operador en Java) y que la asociatividad no importaba. El hecho de que el List apareciera siempre en el lado derecho en el código de ejemplo simplemente parecía una coincidencia (pensé que tal vez era solo el "estilo preferido").

Ahora sé mejor: tiene que escribirse de esa manera porque :: es asociativo de la derecha.

Mi pregunta es, ¿qué sentido tiene poder definir los métodos asociativos correctos?

¿Es puramente por razones estéticas, o puede la asociatividad de la derecha realmente tener algún tipo de beneficio sobre la asociatividad izquierda en ciertas situaciones?

Desde mi (novato) de punto de vista, no veo realmente cómo

1 :: myList 

es mejor que

myList :: 1 

pero eso es obviamente un ejemplo tan trivial que dudo es una comparación justa.

+0

Acaba de incluir sus comentarios en mi respuesta, con algunos ejemplos para ilustrarlos. – VonC

+0

Por lo que puedo decir, es para hacer que los usuarios de Lisp se sientan como en casa, simplemente para permitir la :: lista anexar la operación. – skaffman

+3

@skaffman no, no lo es. lee la respuesta. –

Respuesta

34

La respuesta corta es que la asociatividad correcta puede mejorar la legibilidad al hacer que el programador tipo sea consistente con lo que realmente hace el programa.
Por lo tanto, si escribe '1 :: 2 :: 3', obtiene una Lista (1, 2, 3), en lugar de volver a obtener una Lista en un orden completamente diferente.
Eso sería porque '1 :: 2 :: 3 :: Nil' es en realidad

List[Int].3.prepend(2).prepend(1) 

scala> 1 :: 2 :: 3:: Nil 
res0: List[Int] = List(1, 2, 3) 

que es tanto:

  • más legible
  • más eficiente (O (1) para prepend, frente a O (n) para un hipotético append método)

(Recordatorio, extracto del libro Programming in Scala)
Si se utiliza un método en notación de operador, como a * b, el método se invoca en el operando izquierdo, como en a.*(b), a menos que el nombre del método termine en dos puntos.
Si el nombre del método termina en dos puntos, el método se invoca en el operando derecho.
Por lo tanto, en 1 :: twoThree, se invoca el método :: en twoThree, pasando en 1, así: twoThree.::(1).

Para Lista, que desempeña el papel de una operación de anexión (la lista parece ser añadido después de que el '1' para formar '1 2 3', cuando en realidad es 1, que es antepuesto a la lista).
Lista de clase no ofrece una verdadera operación de anexión, ya que el tiempo que se necesita para añadir a una lista crece linealmente con el tamaño de la lista, mientras que anteponiendo con :: toma tiempo constante.
myList :: 1 trataría de anteponer el contenido completo de miLista a '1', lo que sería más largo que anteponiendo 1 a Mi lista (como en '1 :: myList')

Nota: No importa lo que la asociatividad un operador tiene, sin embargo, su los operandos son siempre evaluados de izquierda a derecha.
Así que si B es una expresión que no es sólo una simple referencia a un valor inmutable, a continuación, una ::: B se trata con mayor precisión como el bloque siguiente:

{ val x = a; b.:::(x) } 

En este bloque de una imagen fija se evalúa antes b, y luego el resultado de esta evaluación se pasa como un operando al método b :::.


¿por qué hacer la distinción entre los métodos de izquierda y derecha asociativos-asociativos en absoluto?

Que permite mantener la apariencia de una operación asociativa izquierda usual ('1 :: myList') mientras se aplica la operación en la expresión correcta porque;

  • es más eficiente.
  • pero es más fácil de leer con una orden asociativo inversa ('1 :: myList 'vs' myList.prepend(1)')

Así como usted dice, 'azúcar sintáctico', por lo que yo sé.
Tenga en cuenta, en el caso de foldLeft, por ejemplo, que podría tener gone a little to far (con la '/:' equivalente operador asociativo por la derecha)


Para incluir algunos de sus comentarios, ligeramente reformulada:

si considera una función 'agregar', asociativa a la izquierda, entonces debería escribir 'oneTwo append 3 append 4 append 5'.
Sin embargo, si se tratara de anexar 3, 4, y 5 a oneTwo (que asumiría por la forma en que está escrito), sería O (N).
mismo con '::' si era para "añadir". Pero no lo es. En realidad, es para "anteponer"

Eso significa 'a :: b :: Nil' es para 'List[].b.prepend(a)'

Si '::' fueron precedidos y sin embargo seguir siendo asociativos por la izquierda, a continuación, la lista resultante estaría en el orden equivocado .
Se esperaría que devuelva List (1, 2, 3, 4, 5), pero terminaría devolviendo List (5, 4, 3, 1, 2), lo que podría ser inesperado para el programador.
Eso se debe a que, lo que ha hecho habría sido, en el orden asociativo por la izquierda:

(1,2).prepend(3).prepend(4).prepend(5) : (5,4,3,1,2) 

Así, la asociatividad por la derecha hace que la coincidencia de código con el orden real del valor de retorno.

+0

Sí, sé que los métodos que terminan en dos puntos (:) se aplican al lado derecho, y he visto la explicación de que anteponer a List es O (1) en oposición a O (N) para anexar. Mi pregunta es más ¿por qué hacer la distinción entre los métodos asociativos izquierdos y los asociativos correctos en absoluto? Estoy tratando de descubrir la razón para diseñar soporte para los métodos asociativos correctos en el lenguaje. Supongo que es puramente por azúcar sintáctico, pero me pregunto si se pensó más en esta opción de diseño que en la mera apariencia del código. –

+1

OK. Creo que entiendo a lo que te refieres.Si '::' fuera asociativo a la izquierda, entonces escribiría 'oneTwo :: 3 :: 4 :: 5'. Sin embargo, si fuera a agregar 3, 4 y 5 a oneTwo (lo cual asumiría por cierto está escrito), sería O (N). Sin embargo, si '::' fuera anterior pero permanezca asociado a la izquierda, entonces la lista resultante estaría en el orden incorrecto. Es de esperar que devuelva List (1, 2 , 3, 4, 5), pero terminaría devolviendo List (5, 4, 3, 1, 2), lo que podría ser inesperado para el programador. Por lo tanto, la asociatividad correcta hace que el código coincida con el orden real del valor de retorno –

+0

Hombre, es realmente difícil explicar lo que quiero decir en estos pequeños cuadros de comentarios;) La respuesta corta es que parece correcto: la asociatividad puede mejorar la legibilidad haciendo que el programador sea consistente con lo que el programa realmente hace. Entonces, si escribe '1 :: 2 :: 3', obtiene una Lista (1, 2, 3), en lugar de volver a obtener una Lista en un orden completamente diferente. –

2

¿Cuál es el punto de poder definir los métodos asociativos correctos?

creo que el punto de método asociativo por la derecha es dar a alguien la oportunidad de ampliar el lenguaje, que es el punto de primordial operador en general.

Sobrecarga del operador es una cosa útil, por lo que Scala dijo: ¿Por qué no abrirlo a cualquier combinación de símbolos? Más bien, ¿por qué hacer una distinción entre los operadores y los métodos? Ahora, ¿cómo interactúa un implementador de biblioteca con tipos incorporados como Int? En C++, ella habría usado la función friend en el alcance global. ¿Qué pasa si queremos todos los tipos para implementar el operador ::?

La asociatividad correcta proporciona una forma limpia de agregar el operador :: a todos los tipos. Por supuesto, técnicamente el operador :: es un método de tipo Lista. Pero también es un operador simulado para Int incorporado y todos de los otros tipos, al menos si puede ignorar el :: Nil al final.

Creo que refleja la filosofía de Scala de implementar tantas cosas en la biblioteca y hacer que el lenguaje sea flexible para admitirlas. Esto permite una oportunidad para que alguien venga con Superlista, que podría denominarse como:

1 :: 2 :: SuperNil 

Es algo desafortunado que la asociatividad por la derecha está programado en forma fija sólo en los dos puntos al final, pero supongo que hace es bastante fácil de recordar.

3

La asociatividad a la derecha y la asociatividad a la izquierda desempeñan un papel importante cuando se realizan operaciones de plegado de listas. Por ejemplo:

def concat[T](xs: List[T], ys: List[T]): List[T] = (xs foldRight ys)(_::_) 

Esto funciona perfectamente bien. Pero no se puede realizar la misma operación utilizando foldLeft

def concat[T](xs: List[T], ys: List[T]): List[T] = (xs foldLeft ys)(_::_) 

, porque :: es asociativo por la derecha.

Cuestiones relacionadas