[Haskell-cafe] Design Question - Functions taking 'subtype' like arguments but has to be restricted to one type

MarLinn monkleyon at googlemail.com
Sun Jul 31 18:41:53 UTC 2016


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.

The new /Basket/ constructor can do that by default.

> 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 at Basket{..} = basket { item = changeItem f item }


And that's why /PlainItem/ was separated, so we can have a simple type 
signature here. You might worry that it was exposed, but it will not 
give anyone access to a frozen basket. And of course you are free to 
further restrict access to it. And as we're speaking about freezing, 
that's extremely simple as well.

     freezeItem :: Item c -> Item 'Unchangeable

     freezeItem (Item i) = Item i

     freezeBasket :: Basket c -> Basket 'Unchangeable

     freezeBasket basket at 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.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20160731/25413d11/attachment.html>


More information about the Haskell-Cafe mailing list