Haskell: "сколько" типа должны получать функции? и избегая полной "реконструкции",

У меня есть эти типы данных:

data PointPlus = PointPlus
    { coords :: Point
    , velocity :: Vector
    } deriving (Eq)

data BodyGeo = BodyGeo
    { pointPlus :: PointPlus
    , size :: Point
    } deriving (Eq)

data Body = Body
    { geo :: BodyGeo
    , pict :: Color
    } deriving (Eq)

Это базовый тип данных для персонажей, врагов, объектов и т.д. в моей игре (ну, я просто имею два прямоугольника в качестве игрока и земли прямо сейчас: p).

Когда клавиша, символы перемещаются вправо, влево или прыгают, меняя ее velocity. Перемещение выполняется добавлением velocity в coords. В настоящее время он написан следующим образом:

move (PointPlus (x, y) (xi, yi)) = PointPlus (x + xi, y + yi) (xi, yi)

Я просто беру PointPlus часть моего Body, а не всего Body, иначе это будет:

move (Body (BodyGeo (PointPlus (x, y) (xi, yi)) wh) col) = (Body (BodyGeo (PointPlus (x + xi, y + yi) (xi, yi)) wh) col)

Лучше ли первая версия move? В любом случае, если move изменяет только PointPlus, должна существовать другая функция, которая вызывает его внутри нового Body. Я объясню: есть функция update, которая вызывается для обновления состояния игры; он передает текущее состояние игры, единственное Body на данный момент и возвращает обновленный Body.

update (Body (BodyGeo (PointPlus xy (xi, yi)) wh) pict) = (Body (BodyGeo (move (PointPlus xy (xi, yi))) wh) pict)

Это щекочет меня. Все сохраняется в пределах Body, кроме PointPlus. Есть ли способ избежать этой полной "реконструкции" вручную? Как в:

update body = backInBody $ move $ pointPlus body

Без определения backInBody, конечно.

Ответ 1

Вы ищете "линзы". Существует несколько различных пакетов для линз; здесь - хорошее резюме из них.

Я понимаю, что объектив с типом данных a для некоторого поля b предоставляет две операции: способ получить значение b и способ получить новый a с другим значением of b. Таким образом, вы просто используете объектив для работы с глубоко вложенным PointPlus.

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

Я думаю, что они заслуживают внимания в вашем проекте, особенно потому, что вы, вероятно, столкнетесь с аналогичными проблемами с вложением в других местах благодаря структуре ваших типов данных.