Определите data Controlled :: Type -> Bool -> Type
так, чтобы Controlled t False
был одноэлементным типом (например, ()
, без информации), а Controlled t True
был копией t
:
data Controlled :: Type -> Bool -> Type where
Exists :: a -> Controlled a True
NonExist :: Controlled a False
Затем определите " полный "тип, представляющий все, что у вас есть, с Controlled
слотами:
data AssignedEstimatedTask' :: Bool -> Bool -> Type where
AssignedEstimatedTask ::
{ getTask :: Task
, taskUser :: Controlled User a
, taskEstimate :: Controlled Estimate b } ->
AssignedEstimatedTask' a b
Введите кучу синонимов:
-- naming the types is exponential!
type JustTask = AssignedEstimatedTask' False False
type AssignedTask = AssignedEstimatedTask' True False
type EstimatedTask = AssignedEstimatedTask' False True
type AssignedEstimatedTask = AssignedEstimatedTask' True True
justTask :: Task -> JustTask
justTask t = AssignedEstimatedTask t NonExist NonExist
И теперь вы можете добавлять элементы в задачу, с использованием обновлений записей, которые отслеживаются во время компиляции:
jTask :: JustTask
aTask :: AssignedTask
eTask :: EstimatedTask
aeTask, eaTask :: AssignedEstimatedTask
-- ofc, all of ^ could have been inferred
jTask = justTask someTask
aTask = jTask { taskUser = Exists someUser }
eTask = jTask { taskEstimate = Exists someEstimate }
aeTask = aTask { taskEstimate = Exists someEstimate }
eaTask = eTask { taskUser = Exists someUser }
-- aeTask == eaTask
Рабочий пример
Если вы действительно хотите извлечь обновления записей как функции, они выглядят так это
assignTask :: User -> AssignedEstimatedTask' a e -> AssignedEstimatedTask' True e
assignTask u t = t { taskUser = Exists u }
estimateTask :: Estimate -> AssignedEstimatedTask' a e -> AssignedEstimatedTask' a True
estimateTask e t = t { taskEstimate = Exists e }