So I have a situation very similar to this (much simplified) code:
import Data.Maybe
import Data.List
data Container a = Container [a]
-- Assumption: an Element can only obtained from a Container. The operation
-- guarentees the Element is in the Container.
data Element a = Element (Container a) a
-- This operation is only valid for Elements that have the same Container.
before :: Eq a => Element a -> Element a -> Bool
before (Element (Container xs) x) (Element (Container ys) y)
| xs == ys = fromJust (elemIndex x xs) < fromJust (elemIndex y xs)
| otherwise = error "Elements from different Containers"
How can I use Haskell’s type system (or GHC extensions) to restrict before and similar operations from taking Elements from different Containers? I’ve been looking into GADTs and DataKinds but, well, that’s going to take a long while and I could use some suggestions or pointers. (Another idea I’ve thought of but haven’t worked out: use a similar trick as the ST monad’s s parameter…)
Am I too pessimistic if I conclude that this would require a dependently typed language? Because, as my limited understanding of dependent typing goes, I think that I’m trying to index the Element type by values of the Container type.
EDIT: Just for extra color, this all ultimately arises because I’m trying to define something very much like this:
import Data.Function
import Data.Ix
-- I want to use Elements as Array indexes; so Elements need to know
-- their Container to calculate their Int
instance Eq a => Ix (Element a) where
-- Ignore how crap this code is
range (l, u) = map (getSibling l) [getIndex l .. getIndex u]
index (l,u) i = index (getIndex l, getIndex u) (getIndex i)
inRange (l,u) i = inRange (getIndex l, getIndex u) (getIndex i)
getIndex :: Eq a => Element a -> Int
getIndex (Element (Container xs) x) = fromJust $ elemIndex x xs
getList :: Element a -> [a]
getList (Element (Container xs) _) = xs
getSibling :: Element a -> Int -> Element a
getSibling (Element (Container xs) _) i = Element (Container xs) (xs!!i)
instance Eq a => Eq (Element a) where
(==) = (==) `on` getIndex
instance Eq a => Ord (Element a) where
compare = compare `on` getIndex
All of this code requires that you never “mix” Elements from different Containers.
You can tell containers apart statically by associating a type with each container. Container elements are tagged with the type to determine whether two given elements came from the same container. This uses
-XExistentialQuantificationand-XRank2Types. Basically, this is dependent typing, except that types depend on the tag instead of the container value.Any time
openContaineris called, it will yield a collection of values belonging to the same container. Two different calls toopenContainerwill be assumed to refer to different containers.This is safe, but conservative, because it is not based on which container is used, but rather which call to
openContainerwas used.. CallingopenContaineron a container, then calling it again, will produce incompatible elements.Now you can write
beforewithout explicitly testing for equality. Since both elements have the same index, they must have come from the same container.