This sounds like a perfect opportunity to use phantom types. I'll be using it's DataKind-enhanced variant for added beauty.
-- An Item that can be of two types, one whose value can be changed, one whose value are frozen once created
data Item = FreeToChange {freeToChangeCount:: Int}
| CannotChange {frozenCount:: Int}
I assume all items are equal apart from their changeability. Not that it's necessary, but it makes the demonstration simpler. If changeable and unchangeable items have differing structure you may need additional tools like smart constructors. Accordingly, my items have the type
data PlainItem = PlainItem { count :: Int }
Changeability will be added on top:
data Changeability = Changeable | Unchangeable data Item (c :: Changeability) = Item { plainItem :: PlainItem }
Why separate Item and PlainItem? One second,
please.
-- The item is part of a basket
data Basket = Basket { name:: String, item::Item }
data Basket c = Basket { name :: String, item :: Item c } -- No kind signature necessary. Thanks, solver.
Therefore, valid operation are:
1. I can create an Basket with either FreeToChange item or CannotChange item.
2. I can update the count for FreeToChange item in the Basket
changeItem :: (PlainItem -> PlainItem) -> Item 'Changeable -> Item 'Changeable
changeItem f (Item i) = Item (f i)
changeBasket :: (PlainItem -> PlainItem) -> Basket 'Changeable -> Basket 'Changeable
changeBasket f basket@Basket{..} = basket { item = changeItem f item }
freezeItem :: Item c -> Item 'Unchangeable
freezeItem (Item i) = Item i
freezeBasket :: Basket c -> Basket 'Unchangeable
freezeBasket basket@Basket{..} = basket { item = freezeItem item }
You later mention that you might want to map updates only over some of the baskets in a cart. That's not hard either. As an example, here's a way to implement a function that updates changeable baskets while ignoring unchangeable ones:
class MaybeUpdateBasket c where updateBasket :: (PlainItem -> PlainItem) -> Basket c -> Basket c instance MaybeUpdateBasket 'Changeable where updateBasket = changeBasket instance MaybeUpdateBasket 'Unchangeable where updateBasket _ = id
Just map it over your cart as always.
If you want more complicated things (eg. you want a cart to freeze once any bucket freezes) you just have to expand the ideas here. You may need MultiParamTypeClasses and FunctionalDependencies, but the basic ideas are the same.
Cheers.