Сокращенный способ назначения одного поля в записи при копировании остальных полей?

119

Допустим, у меня есть следующая запись ADT:

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

Мне нужна функция, которая принимает запись и возвращает запись (того же типа), где все поля, кроме одного, имеют значения, идентичные значению, переданному в качестве аргумента, например:

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

Вышеуказанное работает, но для записи с большим количеством полей (скажем 10) создание такой функции повлечет за собой много набора текста, который, как мне кажется, совершенно не нужен.

Есть ли менее утомительные способы сделать то же самое?

jaymmer - восстановить Монику
источник
3
Синтаксис записи для обновления существует, но быстро становится громоздким. Вместо этого взгляните на линзы .
Cat Plus Plus

Ответы:

155

Да, есть хороший способ обновить поля записи. В GHCi вы можете сделать -

> 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" }
Крис Тейлор
источник
9
RecordWildCardsРасширение может быть хорошо , как хорошо, чтобы «Распаковать» полей в области. Для обновлений это не так хорошо:incrementA x@Foo{..} = x { a = succ a }
Джон Парди,
2
Кстати, во Frege (Haskell для JVM) вы бы определили функцию как updateFoo x = x.{ c = "Goodbye" }(обратите внимание на .оператор).
0dB
Кстати,
красивое
Спасибо. К сожалению, я давно не писал ни одного Haskell!
Крис Тейлор
37

Это хорошая работа для линз :

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

test = Foo 1 2 "Hello"

Затем:

setL c "Goodbye" test

обновит поле 'c' из 'test' до вашей строки.

Дон Стюарт
источник
5
А пакеты, похожие на линзы, часто определяют операторов в дополнение к функциям для получения и настройки полей. Например, test $ c .~ "Goodbye"как lensбы это сделать iirc. Я не говорю, что это интуитивно понятно, но если вы знаете операторы, я думаю, это будет так же легко, как $.
Thomas M. DuBuisson
3
Вы знаете, куда делся setL ? Я импортирую Control.Lens , но ghc сообщает, что setL не определен.
dbanas
1
используйте set вместо setL
Subhod I 08
16

Вам не нужно определять дополнительные функции или использовать линзы. В стандартном Haskell уже есть все, что вам нужно. Возьмем пример Дона Стюарта:

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

test = Foo 1 2 "Hello"

Затем вы можете просто сказать, test { c = "Goodbye" }чтобы получить обновленную запись.

Вольфганг Ельч
источник