[Haskell-cafe] Predicates in data types
Ertugrul Söylemez
es at ertes.de
Wed Dec 12 14:42:25 CET 2012
Navid Hallajian <navidhg at gmail.com> wrote:
> I'm a beginner in Haskell, so forgive me if this is a basic question,
> but I'd like to know if it's possible to have a predicate as part of a
> data type, so that when the data type is created, it can only be done
> if it satisfies the predicate else a type error is thrown.
>
> For instance, a matrix with integer elements could be modelled as
> [[Int]], given the restrictions that
>
> - it must have at least one column and one row (so there must be at
> least one list), and
> - every list must have the same length
>
> so that when a matrix is created, the type system wont allow it if the
> predicates aren't met.
You should model your data type such that it doesn't allow invalid
values to be created in the first place. This is actually easy with the
DataKinds and GADTs extensions since GHC 7.6. First, as almost always,
we need type-level naturals. We will not use the predefined from
GHC.TypeLits, because we need natural numbers with structure:
data Nat :: * where
Z :: Nat
S :: Nat -> Nat
Now we define a type-indexed list type that encodes its length in the
type, commonly called 'Vec':
data Vec :: Nat -> * -> * where
Nil :: Vec Z a
(:.) :: a -> Vec n a -> Vec (S n) a
infixr 5 :.
Unlike the regular list type this one does not give rise to a monad (I'm
lying on purpose here, but disregard that). However, it gives you a
very useful applicative functor:
import Control.Applicative
instance Functor (Vec n) where
fmap f Nil = Nil
fmap f (x :. xs) = f x :. fmap f xs
instance Applicative (Vec Z) where
pure = const Nil
_ <*> _ = Nil
instance (Applicative (Vec n)) => Applicative (Vec (S n)) where
pure x = x :. pure x
f :. fs <*> x :. xs = f x :. (fs <*> xs)
These instances give you an arbitrary-arity zipWith, where liftA2 is
equivalent to zipWith and liftA3 is equivalent to zipWith3. Using these
we can write a type-correct matrix transposition function:
transposeMat ::
(Applicative (Vec w))
=> Vec h (Vec w a)
-> Vec w (Vec h a)
transposeMat Nil = pure Nil
transposeMat (xs :. xss) = liftA2 (:.) xs (transposeMat xss)
We have had to use two extensions which I don't like, FlexibleContexts
and FlexibleInstances. This is because of the Applicative class. To
get rid of those instances you can write this in terms of a custom
class:
class ZipV (n :: Nat) where
pureV :: a -> Vec n a
zipV :: Vec n (a -> b) -> Vec n a -> Vec n b
infixl 4 `zipV`
instance ZipV Z where
pureV = const Nil
zipV _ _ = Nil
instance (ZipV n) => ZipV (S n) where
pureV x = x :. pureV x
zipV (f :. fs) (x :. xs) = f x :. zipV fs xs
transposeMat :: (ZipV w) => Vec h (Vec w a) -> Vec w (Vec h a)
transposeMat Nil = pureV Nil
transposeMat (xs :. xss) =
pureV (:.)
`zipV` xs
`zipV` transposeMat xss
There is only one issue left: How do we actually get these values from
the outside world? For example how do we read such a vector from the
user? There are two (and I think only two) ways to do it: Higher-rank
types or existential types. The first one is the simpler one:
fromList :: [a] -> (forall n. Vec n a -> b) -> b
fromList [] k = k Nil
fromList (x:xs') k = fromList xs' (\xs -> k (x :. xs))
The point of the higher rank type is that the second argument to
fromList promises that it can handle vectors of any length. It's a
function that works "for all" n. This can be extended to actually read
such a vec from the user:
getVec :: (Read a) => (forall n. Vec n a -> IO b) -> IO b
getVec k = fmap read getLine >>= \xs -> fromList xs k
You can't pass a function that handles only non-empty lists to getVec,
because that function cannot handle any 'n'. This lets the type system
force you to handle empty lists:
nonEmpty :: Vec n a -> b -> (forall n'. Vec (S n') a -> b) -> b
I invite you to write this function as an exercise and hope that this
mail helped.
Greets,
Ertugrul
--
Not to be or to be and (not to be or to be and (not to be or to be and
(not to be or to be and ... that is the list monad.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: not available
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20121212/7f25c048/attachment.pgp>
More information about the Haskell-Cafe
mailing list