Digamos que eu tenho as seguintes duas case class
es:
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
e a seguinte instância da Person
classe:
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
Agora se eu quiser atualização zipCode
de raj
então vou ter que fazer:
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
Com mais níveis de aninhamento, isso fica ainda mais feio. Existe uma maneira mais limpa (algo como a de Clojure update-in
) de atualizar essas estruturas aninhadas?
scala
case-class
zipper
desaparecido
fonte
fonte
Respostas:
Zíperes
O Zipper de Huet fornece travessia conveniente e 'mutação' de uma estrutura de dados imutável. O Scalaz fornece zíperes para
Stream
( scalaz.Zipper ) eTree
( scalaz.TreeLoc ). Acontece que a estrutura do zíper é derivada automaticamente da estrutura de dados original, de maneira que se assemelha à diferenciação simbólica de uma expressão algébrica.Mas como isso ajuda você com suas aulas de caso Scala? Bem, Lukas Rytz recentemente prototipou uma extensão do scalac que criaria automaticamente zíperes para classes de casos anotadas. Vou reproduzir o exemplo dele aqui:
Portanto, a comunidade precisa convencer a equipe Scala de que esse esforço deve ser continuado e integrado ao compilador.
Aliás, Lukas publicou recentemente uma versão do Pacman, usuário programável através de uma DSL. Parece que ele não usou o compilador modificado, pois não consigo ver nenhuma
@zip
anotação.Reescrita em Árvore
Em outras circunstâncias, convém aplicar alguma transformação em toda a estrutura de dados, de acordo com alguma estratégia (de cima para baixo, de baixo para cima) e com base em regras que correspondam ao valor em algum momento da estrutura. O exemplo clássico é transformar um AST para um idioma, talvez para avaliar, simplificar ou coletar informações. O Kiama suporta a reescrita , veja os exemplos em RewriterTests e assista a este vídeo . Aqui está um trecho para estimular seu apetite:
Observe que o Kiama sai do sistema de tipos para conseguir isso.
fonte
Engraçado que ninguém adicionou lentes, pois elas foram feitas para esse tipo de coisa. Então, aqui está um documento de plano de fundo da CS, aqui está um blog que aborda brevemente o uso de lentes no Scala, aqui está uma implementação de lentes para o Scalaz e aqui está um código usando-o, que se parece surpreendentemente com a sua pergunta. E, para reduzir a placa da caldeira, aqui está um plugin que gera lentes Scalaz para classes de casos.
Para pontos de bônus, aqui está outra questão do SO que toca nas lentes e um artigo de Tony Morris.
O grande problema das lentes é que elas são compostáveis. Portanto, eles são um pouco pesados no começo, mas continuam ganhando terreno quanto mais você os usa. Além disso, eles são ótimos em termos de testabilidade, já que você só precisa testar lentes individuais e pode dar como certa sua composição.
Portanto, com base em uma implementação fornecida no final desta resposta, veja como você faria isso com lentes. Primeiro, declare as lentes para alterar um CEP em um endereço e um endereço em uma pessoa:
Agora, componha-as para obter uma lente que altera o CEP de uma pessoa:
Por fim, use essa lente para alterar o raj:
Ou, usando um pouco de açúcar sintático:
Ou até:
Aqui está a implementação simples, tirada do Scalaz, usada neste exemplo:
fonte
personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
é o mesmo quepersonZipCodeLens mod (raj, _ + 1)
mod
não é um primitivo para as lentes.Ferramentas úteis para usar lentes:
Só quero acrescentar que os projetos Macrocosm e Rillit , baseados nas macros Scala 2.10, oferecem criação dinâmica de lentes.
Usando o Rillit:
Usando o macrocosmo:
fonte
Estive procurando ao redor da biblioteca Scala que tem a melhor sintaxe e a melhor funcionalidade, e uma biblioteca não mencionada aqui é o monóculo, o que para mim tem sido realmente bom. Um exemplo a seguir:
São muito legais e existem várias maneiras de combinar as lentes. O Scalaz, por exemplo, exige muito clichê e isso compila rapidamente e funciona muito bem.
Para usá-los em seu projeto, adicione-o às suas dependências:
fonte
Shapeless faz o truque:
com:
Observe que, embora algumas outras respostas aqui permitam compor lentes para aprofundar uma determinada estrutura, essas lentes sem forma (e outras bibliotecas / macros) permitem combinar duas lentes não relacionadas, de modo que você pode criar lentes que definem um número arbitrário de parâmetros em posições arbitrárias na sua estrutura. Para estruturas de dados complexas, essa composição adicional é muito útil.
fonte
Lens
código na resposta de Daniel C. Sobral e, portanto, evitei adicionar uma dependência externa.Devido à sua natureza compostável, as lentes oferecem uma solução muito agradável para o problema de estruturas fortemente aninhadas. No entanto, com um baixo nível de aninhamento, às vezes sinto que as lentes são um pouco demais e não quero introduzir a abordagem de lentes inteiras se houver apenas alguns lugares com atualizações aninhadas. Por uma questão de integridade, aqui está uma solução muito simples / pragmática para este caso:
O que faço é simplesmente escrever algumas
modify...
funções auxiliares na estrutura de nível superior, que lidam com a cópia aninhada feia. Por exemplo:Meu principal objetivo (simplificar a atualização no lado do cliente) é alcançado:
Criar o conjunto completo de auxiliares de modificação é obviamente irritante. Mas para coisas internas, geralmente é bom apenas criá-las na primeira vez que você tenta modificar um determinado campo aninhado.
fonte
Talvez o QuickLens corresponda melhor à sua pergunta. O QuickLens usa macro para converter uma expressão amigável do IDE em algo próximo à instrução de cópia original.
Dadas as duas classes de casos de exemplo:
e a instância da classe Person:
você pode atualizar o zipCode do raj com:
fonte