Pregunta ¿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 cosas como esta:

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

Pero esto no es posible en Scala porque ...

bytesRead = in.read(buffer)

.. devuelve Unit, no el nuevo valor de bytesRead.

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


76
2018-01-04 10:37


origen


Respuestas:


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.


75
2018-01-04 16:18



No estoy al tanto de la información interna 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.

Lo hace de muchas maneras. Por ejemplo, no tienes un for bucle donde declaras y muta una variable. No puedes (fácilmente) mutar el estado en un while bucle 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. Variables declaradas dentro de una while bloque no son visibles desde el while condición de prueba, lo que hace do { ... } while (...) mucho menos útil. Y así.

Solución:

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

Por lo que sea que valga

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

EDITAR

David Pollack tiene contestada con algunos hechos reales, que están claramente respaldados por el hecho de que Martin Odersky él mismo comentó su respuesta, dando crédito al argumento de los problemas relacionados con el desempeño presentado por Pollack.


17
2018-01-04 11:57



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

class MyBean {
  private var internalState: String = _

  def state = internalState

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

los state_= método devuelve Unit (como se esperaría de un colocador) precisamente porque la asignación regresa Unit.

Estoy de acuerdo en que para los patrones de estilo C, como copiar una secuencia o similar, esta decisión de diseño 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.


9
2018-01-04 15:14



Tal vez esto se deba a la separación de consulta de comandos ¿principio?

CQS tiende a ser popular en la intersección de OO y los 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.

Una breve ilustración de por qué CQS es útil: Considere un hipotético lenguaje híbrido F / OO con un List clase que tiene métodos Sort, Append, Firsty Length. En el estilo OO imperativo, uno podría querer 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 un estilo más funcional, sería más probable 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()

Estos parecen ser molesto 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 que si Append y Sort Al modificar la lista, deben devolver el tipo de unidad, lo que nos impide 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.


5
2018-01-04 16:38



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

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


4
2018-01-04 10:42



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.


4
2018-01-04 11:08



Por cierto: creo que el truco de while inicial es 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 tarea aparece dos veces, pero al menos bytesRead está en el alcance al que pertenece, y no estoy jugando con trucos divertidos de asignación ...


2
2018-01-04 21:55



Puede tener una solución para esto siempre que tenga 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)
  }
}

Entonces, bajo la restricción que tendrás que usar ref.value para acceder a la referencia después, puede escribir su while predicado como

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 puedes hacer la comprobación contra bytesRead de una manera más implícita sin tener que escribirlo.


0
2018-06-18 23:03