Maneira abreviada de atribuir um único campo em um registro, enquanto copia o restante dos campos?

119

Digamos que eu tenha o seguinte registro ADT:

data Foo = Bar { a :: Integer, b :: String, c :: String }

Eu quero uma função que leva um registro e retorna um registro (do mesmo tipo) onde todos, exceto um dos campos têm valores idênticos ao passado como argumento, assim:

walkDuck x = Bar { a = a x, b = b x, c = lemonadeStand (a x) (b x) }

O procedimento acima funciona, mas para um registro com mais campos (digamos 10), a criação de tal função envolveria muita digitação que considero desnecessária.

Existem maneiras menos tediosas de fazer o mesmo?

Jaymmer - Reintegrar Monica
fonte
3
A sintaxe de registro para atualização existe, mas fica rapidamente complicada. Em vez disso, uma olhada nas lentes .
Cat Plus Plus

Respostas:

155

Sim, existe uma boa maneira de atualizar os campos de registro. No GHCi você pode fazer -

> data Foo = Foo { a :: Int, b :: Int, c :: String }  -- define a Foo
> let foo = Foo { a = 1, b = 2, c = "Hello" }         -- create a Foo
> let updateFoo x = x { c = "Goodbye" }               -- function to update Foos
> updateFoo foo                                       -- update the Foo
Foo {a = 1, b = 2, c = "Goodbye" }
Chris Taylor
fonte
9
A RecordWildCardsextensão também pode ser boa, para “desempacotar” campos em um escopo. Porém, para atualizações não é tão bom:incrementA x@Foo{..} = x { a = succ a }
Jon Purdy
2
BTW, em Frege (um Haskell para o JVM) você definiria a função como updateFoo x = x.{ c = "Goodbye" }(observe o .operador).
0dB de
A propósito, bom vídeo youtube.com/watch?v=YScIPA8RbVE
Damián Rafael Lattenero
Obrigado. Infelizmente, já faz muito tempo que não escrevo Haskell!
Chris Taylor
37

Este é um bom trabalho para lentes :

data Foo = Foo { a :: Int, b :: Int , c :: String }

test = Foo 1 2 "Hello"

Então:

setL c "Goodbye" test

atualizaria o campo 'c' de 'teste' para sua string.

Don Stewart
fonte
5
E pacotes semelhantes a lentes geralmente definem operadores, além de funções para obter e definir campos. Por exemplo, test $ c .~ "Goodbye"é como lensfaria isso iirc. Não estou dizendo que seja intuitivo, mas uma vez que você conhece os operadores, espero que seja tão fácil quanto $.
Thomas M. DuBuisson
3
Você sabe para onde foi setL ? Estou importando Control.Lens , mas ghc está relatando que setL é indefinido.
dbanas de
1
use set em vez de setL
Subhod I
16

Você não precisa definir funções auxiliares ou empregar lentes. O Haskell padrão já tem o que você precisa. Vejamos o exemplo de Don Stewart:

data Foo = Foo { a :: Int, b :: Int , c :: String }

test = Foo 1 2 "Hello"

Então você pode simplesmente dizer test { c = "Goodbye" }para obter um registro atualizado.

Wolfgang Jeltsch
fonte