Слово в защиту RecordWildCards
date = fromGregorian 2021 apr 06
category = "Расширения"
tags = ["расширения", "стиль", "практики", "читаемость"]
Многие практикующие хаскелиты ругают расширение RecordWildCards
за ухудшение читаемости. Действительно, если использовать его где захочется:
{- здесь и далее использованы случайные слова, не ищите скрытый смысл -}
import Stove.Egg -- (1)
import Dot.Dig.Trail -- (2)
shoulder
Pleasure{..} -- (3)
Type{..} -- (4)
= do
Enemy{..} <- -- (5)
street *> dance
allow $ having . lonely
where
Train{..} = -- (6)
well #. cream
то непонятно, откуда взялась та или иная переменная. (Потенциальные места отмечены номерами.)
Однако, RecordWildCards
в некоторых случаях может улучшить читаемость кода!
1. Тривиальная упаковка структуры
Когда все поля заполняются в do-блоке в непосредственной близости.
parseOptions = do
inputs <- many parseInput
output <- optional parseOutput
health <- optHealth
roar <- flagRoar
pure Options{..}
Особенно полезно в комбинации с ApplicativeDo
.
2. Тривиальная распаковка структуры
Когда в теле функции используются только поля одной структуры плюс не очень толстая обвязка общего назначения.
toJSON Mark{..} =
object
[ "id" .= markId
, "type" .= type_
, "stone" .= stone
, "frame" .= base64encode frame
]
2a. Чтение поля
Частный случай — ещё более тривиальная распаковка, когда нет вообще ничего, кроме извлечения полей.
Иногда хочется прочитать одно поле, но от автоматических селекторов уже отказались в пользу DuplicateRecordFields
.
data Path = Path{hole :: Hole}
data Wall = Wall{hole :: Hole}
sortPathsOnHole = sortOn \Path{..} -> hole
sortWallsOnHole = sortOn \Wall{..} -> hole
Есть ещё вариант с линзами, но у линз своя цена.
3. Преобразование структур с частично совпадающими полями
Например, миграция в базе данных.
data AuthorV1 = AuthorV1
{ shorter :: Shorter
, largest :: Largest
, center :: Center
, event :: EventV1
}
data AuthorV2 = AuthorV2
{ shorter :: Shorter
, largest :: Largest
, center :: Center
, event :: EventV2
, worried :: Worried
}
upgradeAuthorV1ToV2 AuthorV1{..} =
AuthorV2
{ event = upgradeEventV1ToV2 event
, worried = defaultWorried
, ..
}
3a. Обход ворнинга -Wincomplete-record-updates
Очень полезный ворнинг, который я всегда включаю, потому что он находит вот такие ошибки при добавлении новых слагаемых в тип-сумму:
data Image
= ColorImage{width, height, colorDepth :: Word}
-- ^ сначала было только одно слагаемое
| MonoImage{width, height :: Word}
-- ^ это добавили позже
setColorDepth1 d image = image{colorDepth = d}
-- ^^^^^^^^^^^^^^^^^^^^^
-- Pattern match(es) are non-exhaustive
Внезапно при включении этого ворнинга GHC начинает ругаться и на совершенно корректный код:
setColorDepth2 d = \case
image@ColorImage{} -> image{colorDepth = d}
-- ^^^^^^^^^^^^^^^^^^^^^
-- Pattern match(es) are non-exhaustive
image -> image
И с точки зрения системы типов компилятор совершенно прав. Сопоставление с ColorImage
не сужает тип переменной. Это не баг. Но как-то улучшить обнаружение проблем в этом месте, наверно, всё-таки можно.
Обойти можно так:
Ну и линзами, конечно.
Возможно, более правильный вариант — вообще разделить суммы и произведения:
data Image = ImageColor ColorImage | ImageMono MonoImage
data ColorImage = ColorImage{width, height, colorDepth :: Word}
data MonoImage = MonoImage{width, height :: Word}
setColorDepth4 d image = image{colorDepth = d}
-- выводится тип ColorImage, неопределённости нет, ошибки нет
Недостатки
Включение RecordWildCards
приоткрывает дорогу и плохому коду, если не следить за новичками пристально на ревью. Эту задачу можно было бы переложить на линтеры, но я пока не встречал таких, что могли бы ограничить использование этого расширения только в описанных случаях. Найдёте — сообщите мне.
Ссылки
- Менее предвзятый пост Дмитрия Кованикова о возможностях
RecordWildCards
.