¿Cuál es la motivación para evaluar la asignación de Scala a Unit 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 algo interesante que dejar fuera de un lenguaje funcional. Me pregunto por qué se hizo así.
scala
functional-programming
io
assignment-operator
Graham Lea
fuente
fuente
Respuestas:
Abogué por que las asignaciones devuelvan el valor asignado en lugar de la unidad. Martin y yo íbamos y veníamos sobre él, pero su argumento era que poner un valor en la pila solo para sacarlo el 95% del tiempo era una pérdida de códigos de bytes y tenía un impacto negativo en el rendimiento.
fuente
void
. En Scalafoo_=(v: Foo)
debería volverFoo
si lo hace la asignación.void
(Unit
), las asignacionesx = value
se traducen al equivalente dex.set(value);x.get(value)
; el compilador elimina en las fases de optimización lasget
-calls si el valor no se usó. Podría ser un cambio bienvenido en una nueva versión importante de Scala (debido a la incompatibilidad con versiones anteriores) y menos irritaciones para los usuarios. ¿Qué piensas?No tengo acceso a información privilegiada sobre las razones reales, pero mi sospecha es muy simple. Scala hace que los bucles de efectos secundarios sean incómodos de usar, por lo que los programadores naturalmente preferirán las comprensiones.
Lo hace de muchas formas. Por ejemplo, no tiene un
for
bucle en el que declare y mute una variable. No puede (fácilmente) mutar un estado en unwhile
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. Las variables declaradas dentro de unwhile
bloque no son visibles desde lawhile
condición de prueba, lo que las hacedo { ... } while (...)
mucho menos útiles. Y así.Solución alterna:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
Por lo que valga.
Como explicación alternativa, quizás Martin Odersky tuvo que enfrentarse a algunos errores muy feos derivados de tal uso y decidió prohibirlo en su lenguaje.
EDITAR
David Pollack ha respondido con algunos hechos reales, que están claramente respaldados por el hecho de que el propio Martin Odersky comentó su respuesta, dando crédito al argumento de cuestiones relacionadas con el desempeño presentado por Pollack.
fuente
for
versión de bucle sería: lofor (bytesRead <- in.read(buffer) if (bytesRead) != -1
cual es genial, excepto que no funcionará porque no hayforeach
y estáwithFilter
disponible.Esto sucedió como parte de Scala que tenía un sistema de tipos más "formalmente correcto". Hablando formalmente, la asignación es una declaración puramente de efectos secundarios 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 }
El
state_=
método regresaUnit
(como se esperaría de un establecedor) precisamente porque la asignación regresaUnit
.Estoy de acuerdo en que para 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 consistencia general del sistema de tipos.
fuente
¿Quizás esto se deba al principio de separación comando-consulta ?
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 métodos de objeto que tienen o no efectos secundarios (es decir, que alteran el objeto). La aplicación de CQS a las asignaciones de variables va más allá de lo habitual, pero se aplica la misma idea.
Un corto ilustración de por qué CQS es útil: Considere un lenguaje F / OO híbrido hipotético con una
List
clase que tiene métodosSort
,Append
,First
, yLength
. En imperativo estilo OO, 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, es más probable que uno escriba 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 estar intentando 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.
El uso de SCC, sin embargo, nos gustaría insistir en que si
Append
ySort
modificar la lista, deben devolver el tipo de unidad, lo que nos impide la creación de errores mediante el uso de la segunda forma cuando no debería. Por lo tanto, la presencia de efectos secundarios también se vuelve implícita en la firma del método.fuente
Supongo que esto es para mantener el programa / lenguaje libre de efectos secundarios.
Lo que describe es el uso intencional de un efecto secundario que en el caso general se considera algo malo.
fuente
val a = b = 1
(imaginar "mágica"val
frenteb
) vsval a = 1; val b = 1;
.No es el mejor estilo utilizar una asignación como expresión booleana. Realiza dos cosas al mismo tiempo, lo que a menudo conduce a errores. Y el uso accidental de "=" en lugar de "==" se evita con la restricción de Scalas.
fuente
Por cierto: encuentro estúpido el truco inicial inicial, 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 bytesRead está en el ámbito al que pertenece, y no estoy jugando con trucos divertidos de asignación ...
fuente
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) } }
Luego, bajo la restricción que tendrá que usar
ref.value
para acceder a la referencia posteriormente, puede escribir suwhile
predicado comoval bytesRead = Ref(0) // maybe there is a way to get rid of this line while ((bytesRead := in.read(buffer)) (_ != -1)) { // ... println(bytesRead.value) }
y puede realizar la comprobación
bytesRead
de una manera más implícita sin tener que escribirlo.fuente