2010-01-04 5 views
67

¿Cuál es la motivación para asignar la asignación de Scala a la unidad en lugar del valor asignado?¿Cuál es la motivación para asignar la asignación de Scala a la Unidad en lugar del valor asignado?

Un patrón común en la programación de E/S es hacer las cosas de esta manera:

while ((bytesRead = in.read(buffer)) != -1) { ... 

Pero esto no es posible en Scala porque ...

bytesRead = in.read(buffer) 

.. pone la unidad, no el nuevo valor de bytesRead.

Parece una cosa interesante de dejar fuera de un lenguaje funcional. Me pregunto por qué se hizo así?

+0

David Pollack ha publicado información de primera mano, bastante respaldada por el comentario que el propio Martin Odersky dejó en su respuesta. Creo que uno puede aceptar con seguridad la respuesta de Pollack. –

Respuesta

66

Abogué por que las asignaciones devuelvan el valor asignado en lugar de la unidad. Martin y yo fuimos de un lado a otro, pero su argumento era que poner un valor en la pila solo para abrirlo el 95% del tiempo era un desperdicio de códigos de bytes y tenía un impacto negativo en el rendimiento.

+4

¿Hay alguna razón por la cual Scala el compilador no pudo ver si el valor de la asignación realmente se usa, y generar un bytecode eficiente en consecuencia? –

+39

No es tan fácil en presencia de instaladores: cada incubadora tiene que devolver un resultado, lo cual es difícil de escribir. Luego el compilador tiene que optimizarlo, lo que es difícil de hacer en todas las llamadas. –

+0

Tu argumento tiene sentido, pero java y C# están en contra de eso. Supongo que estás haciendo algo raro con el código de bytes generado, ¿cómo sería una asignación en Scala compilada en el archivo de clase y el decompilado en Java? –

5

Supongo que esto es para mantener el programa/el idioma libre de efectos secundarios.

Lo que usted describe es el uso intencional de un efecto secundario que en el caso general se considera algo malo.

+0

Heh. Scala libre de efectos secundarios? :) Además, imagine un caso como 'val a = b = 1' (imagine" mágico "' val' delante de 'b') contra' val a = 1; val b = 1; '. –

4

No es el mejor estilo para usar una asignación como una expresión booleana. Usted realiza dos cosas al mismo tiempo que a menudo conduce a errores. Y se evita el uso de "=" en lugar de "==" con la restricción de Scalas.

+2

¡Creo que es una razón de porquería! A medida que el OP publicado, el código aún se compila y ejecuta: simplemente no hace lo que razonablemente se puede esperar. ¡Es uno más, no menos! –

+1

Si escribe algo como if (a = b) no se compilará. Por lo tanto, al menos este error puede evitarse. – deamon

+1

El OP no utilizó '=' en lugar de '==', usó ambos. Espera que la asignación devuelva un valor que luego se puede utilizar, por ejemplo, para comparar con otro valor (-1 en el ejemplo) – IttayD

16

No tengo información privilegiada sobre las razones reales, pero mi sospecha es muy simple. Scala hace que los bucles de efectos laterales sean incómodos de usar, por lo que los programadores naturalmente preferirán las comprensiones.

Hace esto de muchas maneras. Por ejemplo, no tiene un bucle for donde declara y muta una variable. No puede (fácilmente) mutar el estado en un bucle while al mismo tiempo que prueba la condición, lo que significa que a menudo tiene que repetir la mutación justo antes y al final de la misma. Las variables declaradas dentro de un bloque while no son visibles desde la condición de prueba while, lo que hace que do { ... } while (...) sea mucho menos útil. Y así.

Solución:

while ({bytesRead = in.read(buffer); bytesRead != -1}) { ... 

Por lo que vale la pena.

Como explicación alternativa, tal vez Martin Odersky tuvo que enfrentarse a algunos errores muy feos derivados de tal uso, y decidió prohibirlo en su idioma.

EDITAR

David Pollack tiene answered con algunos hechos reales, que están claramente respaldados por el hecho de que Martin Odersky mismo comentó su respuesta, dando credibilidad al argumento de los problemas relacionados con el rendimiento presentada por Pollack.

+3

Entonces, presumiblemente, la versión del bucle 'for' sería:' for (bytesRead <- in.read (buffer) if (bytesRead)! = -1' que es genial, excepto que no funcionará porque no hay 'foreach' y 'withFilter' disponible! –

8

Esto sucedió como parte de que Scala tenía un sistema de tipo más "formalmente correcto". Formalmente hablando, la asignación es una declaración puramente colateral y por lo tanto debe devolver Unit.Esto tiene algunas buenas consecuencias; por ejemplo:

class MyBean { 
    private var internalState: String = _ 

    def state = internalState 

    def state_=(state: String) = internalState = state 
} 

El método state_= devuelve Unit (como sería de esperar para un setter) precisamente porque asignación devuelve Unit.

Acepto que para los patrones de estilo C, como copiar una secuencia o similar, esta decisión de diseño en particular puede ser un poco problemática. Sin embargo, en realidad es relativamente poco problemático en general y realmente contribuye a la coherencia general del sistema de tipos.

+0

Gracias, Daniel. Creo que preferiría que la consistencia fuera que tanto las asignaciones como los usuarios devolvieran el valor (no hay ninguna razón para que no puedan hacerlo). Sospecho que no estoy asimilando los matices de los conceptos. como una "afirmación puramente colateral" por el momento. –

+2

@Graham: Pero entonces, tendrías que seguir la consistencia y asegurarte en todos tus setters por más complejos que sean, que devuelvan el valor que establecieron. complicado en algunos casos y en otros casos simplemente equivocado, creo. (¿Qué devolvería en caso de error? null? - en lugar de no. ¿Ninguno? - entonces su tipo será Opción [T].) Creo que es difícil ser consistente con eso. – Debilski

5

Quizás esto se deba al principio command-query separation?

CQS tiende a ser popular en la intersección de OO y estilos de programación funcional, ya que crea una distinción obvia entre los métodos de objeto que tienen o no tienen efectos secundarios (es decir, que alteran el objeto). Aplicar CQS a asignaciones variables es llevarlo más allá de lo habitual, pero se aplica la misma idea.

breve ilustración de por qué CQS es útil: Considere un lenguaje híbrido hipotético F/OO con una clase List que tiene métodos Sort, Append, First, y Length. En el estilo OO imprescindible, uno podría desear escribir una función como esta:

func foo(x): 
    var list = new List(4, -2, 3, 1) 
    list.Append(x) 
    list.Sort() 
    # list now holds a sorted, five-element list 
    var smallest = list.First() 
    return smallest + list.Length() 

Mientras que en el estilo más funcional, se haría más probable que escribir algo como esto:

func bar(x): 
    var list = new List(4, -2, 3, 1) 
    var smallest = list.Append(x).Sort().First() 
    # list still holds an unsorted, four-element list 
    return smallest + list.Length() 

Estas parecen ser tratando para hacer lo mismo, pero obviamente uno de los dos es incorrecto, y sin saber más sobre el comportamiento de los métodos, no podemos decir cuál.

Usando CQS, sin embargo, insistiríamos en que si Append y Sort alteran la lista, deben devolver el tipo de unidad, impidiéndonos crear errores utilizando el segundo formulario cuando no deberíamos. La presencia de efectos secundarios, por lo tanto, también queda implícita en la firma del método.

2

Por cierto: el truco while-thick inicial me parece estúpido, incluso en Java. ¿Por qué no algo así?

for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) { 
    //do something 
} 

Por supuesto, la asignación aparece dos veces, pero al menos es BytesRead en el ámbito al que pertenece, y no estoy jugando con trucos divertidos de asignación ...

+0

Que si bien el truco es bastante común, por lo general aparece en cada aplicación que lee a través de un búfer. Y siempre se ve como la versión de OP. – TWiStErRob

1

Usted puede tener una solución para este siempre que tengas un tipo de referencia para la indirección. En una implementación ingenua, puede usar lo siguiente para tipos arbitrarios.

case class Ref[T](var value: T) { 
    def := (newval: => T)(pred: T => Boolean): Boolean = { 
    this.value = newval 
    pred(this.value) 
    } 
} 

Luego, bajo la restricción de que vas a tener que usar ref.value acceder a la referencia después, se puede escribir el predicado como while

val bytesRead = Ref(0) // maybe there is a way to get rid of this line 

while ((bytesRead := in.read(buffer)) (_ != -1)) { // ... 
    println(bytesRead.value) 
} 

y se puede hacer la comprobación contra bytesRead en una de manera más implícita sin tener que escribirlo.

Cuestiones relacionadas