I'll try to address the questions in the reverse order.
Does this approach give me any more power than the previous approaches? The one power is that we stop the user from being able to construct the Frozen type, and we leave it to the compiler to return that type based on the inference. Correct? Is there any other power.The main power lies in the minimalism. You don't need special handling of an Either or manual tracking because the types do everything you need.
In your approach or in the approach suggested by others, ultimately, I end up handling the 'Frozen' type during run-time.Actually, not really. That's the beauty of phantom types: It's almost exclusively a development-time tool that vanishes once the program is compiled. You can't handle the Frozen type during run-time because it will have been dropped - unless you use tricks to keep it, of course. It might feel like you're dealing with the type, but it's all in your head.
There is no way from stopping somene write code that calls update's on Frozen. (For example while mapping..). Is that understanding correct?
Here's my Item type again for reference:
data Item (c :: Changeability) = Item { plainItem :: PlainItem }If you don't export the Item constructor, you have full control over who gets to do what with your items. If your only exported update function is
changeItem :: (PlainItem -> PlainItem) -> Item 'Changeable -> Item 'Changeablethen only non-frozen items can be changed, as the type signature says. Of course you have to be careful, e.g. you wouldn't want to export a function like
asChangableItem :: PlainItem -> Item 'Changeablebecause then somebody could "unfreeze" items. But as long as you watch your types and exports, you should be fine. (apart from unsafe casting and the like, naturally)
1. With the definition of Basket c, how do I create a type such asCart = [Basket]. The only way to create such a cart is Cart c = [Basket c]. But, that ties the Cart to one definition of Basket. Is there a way around this? I might be missing something simple.
_______________________________________________That depends. How does a cart behave, depending on the changeability of its contents?
If a cart with any frozen value behaves differently from a cart with only changeable baskets then add a phantom type to the cart as well. Control the channels into it and have the cart type change depending on what you insert.
If all carts behave the same and contents don't matter just store PlainBaskets, i.e. a Basket without the annotation.
If you want to be able to map over baskets in a cart while handling frozen ones differently, then you need both run-time information about basket changeability and a way to share functions between the two different types. The canonical way to share a function between types is a class:
class IsBasket b instance IsBasket (Basket c) type Cart = (IsBasket b) => [b] -- not a good definition, but you get the pointNow Baskets can share functions but behave differently. Of course once you need such a class anyway you could ask why you would not want to use it exclusively instead of the phantom types. It's a matter of taste, I guess.
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.