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