Qual é a motivação para a avaliação da atribuição do Scala à Unidade em vez do valor atribuído?
Um padrão comum na programação de E / S é fazer coisas assim:
while ((bytesRead = in.read(buffer)) != -1) { ...
Mas isso não é possível no Scala porque ...
bytesRead = in.read(buffer)
.. retorna Unit, não o novo valor de bytesRead.
Parece algo interessante deixar de fora de uma linguagem funcional. Estou me perguntando por que isso foi feito?
scala
functional-programming
io
assignment-operator
Graham Lea
fonte
fonte
Respostas:
Defendi que as atribuições devolveriam o valor atribuído em vez da unidade. Martin e eu conversamos sobre isso, mas seu argumento era que colocar um valor na pilha apenas para retirá-lo 95% do tempo era uma perda de códigos de byte e tinha um impacto negativo no desempenho.
fonte
void
. Em Scalafoo_=(v: Foo)
deve retornarFoo
se a atribuição retornar .void
(Unit
), atribuiçõesx = value
são traduzidas em equivalentes dex.set(value);x.get(value)
; o compilador elimina nas fases de otimização osget
-calls se o valor não foi usado. Pode ser uma mudança bem-vinda em uma nova versão principal do Scala (por causa da incompatibilidade com versões anteriores) e menos irritações para os usuários. O que você acha?Não tenho acesso a informações privilegiadas sobre os reais motivos, mas minha suspeita é muito simples. O Scala torna os loops de efeito colateral difíceis de usar, de modo que os programadores irão naturalmente preferir as compreensões para.
Ele faz isso de várias maneiras. Por exemplo, você não tem um
for
loop onde declara e altera uma variável. Você não pode (facilmente) transformar o estado em umwhile
loop ao mesmo tempo em que testa a condição, o que significa que geralmente é necessário repetir a mutação imediatamente antes e no final dela. As variáveis declaradas dentro de umwhile
bloco não são visíveis nawhile
condição de teste, o que tornado { ... } while (...)
muito menos útil. E assim por diante.Gambiarra:
while ({bytesRead = in.read(buffer); bytesRead != -1}) { ...
Por qualquer coisa que valha a pena.
Como uma explicação alternativa, talvez Martin Odersky teve que enfrentar alguns insetos muito feios derivados de tal uso e decidiu bani-lo de sua linguagem.
EDITAR
David Pollack tem respondido com alguns fatos reais, que são claramente endossado pelo fato de que Martin Odersky se comentou sua resposta, dando credibilidade ao argumento problemas relacionados ao desempenho apresentadas por Pollack.
fonte
for
versão em loop seria: ofor (bytesRead <- in.read(buffer) if (bytesRead) != -1
que é ótimo, exceto que não funcionará porque não háforeach
e estáwithFilter
disponível!Isso aconteceu como parte do Scala ter um sistema de tipos mais "formalmente correto". Falando formalmente, a atribuição é uma declaração puramente de efeito colateral e, portanto, deve retornar
Unit
. Isso tem algumas consequências interessantes; por exemplo:class MyBean { private var internalState: String = _ def state = internalState def state_=(state: String) = internalState = state }
O
state_=
método retornaUnit
(como seria esperado para um setter) precisamente porque a atribuição retornaUnit
.Concordo que, para padrões de estilo C, como copiar um fluxo ou similar, essa decisão de design específica pode ser um pouco problemática. No entanto, é relativamente não problemático em geral e realmente contribui para a consistência geral do sistema de tipos.
fonte
Talvez isso se deva ao princípio de separação comando-consulta ?
O CQS tende a ser popular na interseção de estilos de programação OO e funcionais, pois cria uma distinção óbvia entre métodos de objeto que têm ou não efeitos colaterais (ou seja, que alteram o objeto). Aplicar o CQS a atribuições de variáveis está levando mais longe do que o normal, mas a mesma ideia se aplica.
Uma pequena ilustração de por CQS é útil: Considere uma linguagem F / OO híbrido hipotética com uma
List
classe que tem métodosSort
,Append
,First
, eLength
. No estilo OO imperativo, pode-se querer escrever uma função 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()
Considerando que em um estilo mais funcional, seria mais provável escrever algo assim:
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()
Eles parecem estar tentando fazer a mesma coisa, mas obviamente um dos dois está incorreto e, sem saber mais sobre o comportamento dos métodos, não podemos dizer qual deles.
Usando o CQS, entretanto, insistiríamos que se
Append
eSort
alterar a lista, eles devem retornar o tipo de unidade, evitando assim a criação de bugs usando a segunda forma quando não deveríamos. A presença de efeitos colaterais, portanto, também se torna implícita na assinatura do método.fonte
Eu acho que isso é para manter o programa / a linguagem livre de efeitos colaterais.
O que você descreve é o uso intencional de um efeito colateral que, em geral, é considerado uma coisa ruim.
fonte
val a = b = 1
(imagine "mágico"val
na frente deb
) vsval a = 1; val b = 1;
..Não é o melhor estilo usar uma atribuição como uma expressão booleana. Você executa duas coisas ao mesmo tempo, o que freqüentemente leva a erros. E o uso acidental de "=" em vez de "==" é evitado com a restrição Scalas.
fonte
A propósito: acho o while-trick inicial estúpido, mesmo em Java. Por que não algo assim?
for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) { //do something }
Concedido, a atribuição aparece duas vezes, mas pelo menos bytesRead está no escopo ao qual pertence, e não estou brincando com truques de atribuição engraçados ...
fonte
Você pode ter uma solução alternativa para isso, desde que tenha um tipo de referência para indireção. Em uma implementação ingênua, você pode usar o seguinte para tipos arbitrários.
case class Ref[T](var value: T) { def := (newval: => T)(pred: T => Boolean): Boolean = { this.value = newval pred(this.value) } }
Então, sob a restrição que você terá que usar
ref.value
para acessar a referência posteriormente, você pode escrever seuwhile
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) }
e você pode fazer a verificação de
bytesRead
uma maneira mais implícita, sem precisar digitá-la.fonte