> {-# LANGUAGE UndecidableInstances #-} > {-# LANGUAGE MultiParamTypeClasses #-} > {-# LANGUAGE FlexibleContexts #-} > {-# LANGUAGE ConstraintKinds #-} > {-# LANGUAGE FlexibleInstances #-} > {-# LANGUAGE TypeOperators #-} > {-# LANGUAGE TypeFamilies #-} > {-# LANGUAGE DataKinds #-} > {-# LANGUAGE PolyKinds #-} > {-# LANGUAGE KindSignatures #-} > {-# LANGUAGE GADTs #-} > {-# LANGUAGE TypeApplications #-} > {-# LANGUAGE ScopedTypeVariables #-} > module Vector where > import Data.Kind > import Data.Proxy > import GHC.TypeLits (Symbol, ErrorMessage(..)) > import Data.Type.Bool > import Data.Type.Equality > import Data.Type.Require Firstly, we define size-indexed vectors, the stapple example of a dependent Haskell type. > data Nat = Z | S Nat > type family Lt (m :: Nat) (n :: Nat) :: Bool where > Lt Z (S n) = True > Lt _ Z = False > Lt (S m) (S n) = Lt m n > infixr 3 :< > data Vec (n :: Nat) (a :: Type) :: Type where > VNil :: Vec Z a > (:<) :: a -> Vec n a -> Vec (S n) a And singletons for natutals: > data SNat (n :: Nat) where > SZ :: SNat Z > SS :: SNat n -> SNat (S n) With this we can implement a get function, that takes the element in a given index of the vector. There are many ways to do that in a safe way: * use a value of type 'Fin n' as the input for the index. < get :: Fin n -> Vec n a -> a * use 'SNat' but also define a GADT 'Lt :: Nat -> Nat -> Type' that encodes a proof that the index is smaller than the vector size. < get :: SNat n -> Lt n m -> Vec m a -> a * use a 'SNat', with no proof argument, and let index overflows to be ill-typed. Since this is Haskell all type information is static, meaning that we will know at compile time if the index is out of bound. < get :: SNat n -> Vec m a -> a In the latter approach is where |Require| fits. The require infrastructure allows us to have nice type errors when an out of bound lookup occurs, instead of something like 'no instance for ..' We introduce an /operation/ for the vector, the get operation. This is a product datatype having all information we need: an index and vector: > data OpGet (n :: Nat) (k :: Nat) (a :: Type) :: Type where > OpGet :: SNat n -> Vec k a -> OpGet n k a This operation /requires/ some properties about its arguments, in this case, that the index given is less than vector's length. A well-typed lookup will satisfy the constraint 'Require (OpGet n k a)' We will decide according on the result of '(Lt n k)' . Since typeclass resolution does not backtrack we need to have all informartion on the head of the instance. This is a well known trick. We build a wrapper where the first Boolean argument will contain the result of the comparisson: > data OpGet' (cmp :: Bool) (n :: Nat) (k :: Nat) (a :: Type) :: Type where > OpGet' :: Proxy cmp -> SNat n -> Vec k a -> OpGet' cmp n k a Then, the wrapper instance: > instance > Require (OpGet' (Lt n k) n k a) ctx > => > Require (OpGet n k a) ctx where > type ReqR (OpGet n k a) = > ReqR (OpGet' (Lt n k) n k a) > req :: Proxy ctx -> OpGet n k a -> ReqR (OpGet n k a) req Proxy ctx proxy (OpGet (SNat n n :: SNat n) (Vec k a v :: Vec k a)) = > Proxy ctx -> OpGet' (Lt n k) n k a -> ReqR (OpGet' (Lt n k) n k a) forall op (ctx :: [ErrorMessage]). Require op ctx => Proxy ctx -> op -> ReqR op req Proxy ctx proxy (Proxy (Lt n k) -> SNat n -> Vec k a -> OpGet' (Lt n k) n k a forall (cmp :: Bool) (n :: Nat) (k :: Nat) a. Proxy cmp -> SNat n -> Vec k a -> OpGet' cmp n k a OpGet' (Proxy (Lt n k) forall k (t :: k). Proxy t Proxy @ (Lt n k)) SNat n n Vec k a v) Now we program the good cases: > instance > Require (OpGet' 'True Z k a) ctx where > type ReqR (OpGet' 'True Z k a) = a > req :: Proxy ctx -> OpGet' 'True 'Z k a -> ReqR (OpGet' 'True 'Z k a) req Proxy ctx _ (OpGet' Proxy 'True _ SNat 'Z SZ (a a :< Vec n a _)) = a ReqR (OpGet' 'True 'Z k a) a > instance > Require (OpGet n k a) ctx > => > Require (OpGet' 'True (S n) (S k) a) ctx where > type ReqR (OpGet' 'True (S n) (S k) a) = > ReqR (OpGet n k a) > req :: Proxy ctx -> OpGet' 'True ('S n) ('S k) a -> ReqR (OpGet' 'True ('S n) ('S k) a) req Proxy ctx ctx (OpGet' Proxy 'True _ (SS SNat n n) (a _ :< Vec n a as)) = Proxy ctx -> OpGet n n a -> ReqR (OpGet n n a) forall op (ctx :: [ErrorMessage]). Require op ctx => Proxy ctx -> op -> ReqR op req Proxy ctx ctx (SNat n -> Vec n a -> OpGet n n a forall (n :: Nat) (k :: Nat) a. SNat n -> Vec k a -> OpGet n k a OpGet SNat n n Vec n a as) Finally, when (Lt n k ~ 'False) we have an ill-typed get operation. We build a |Require| instance for the |OpError| operation. When defining it, we have in scope 'n', 'k', and 'a', everything needed to write a good type error using 'GHC.TypeLits' infraestructure. > instance > Require (OpError > (Text "Type error!, index out of bound:" :$$: > Text "Trying to lookup the " :<>: ShowTE n :<>: Text "th index" :$$: > Text "in a vector of size " :<>: ShowTE k > ) > ) ctx > => > Require (OpGet' False n k a) ctx where > type ReqR (OpGet' False n k a) = OpError (Text "lala") > req :: Proxy ctx -> OpGet' 'False n k a -> ReqR (OpGet' 'False n k a) req = [Char] -> Proxy ctx -> OpGet' 'False n k a -> OpError ('Text "lala") forall a. HasCallStack => [Char] -> a error [Char] "unreachable" > get :: SNat n -> Vec k a -> ReqR (OpGet n k a) get SNat n n Vec k a v = Proxy '[ 'Text "getting"] -> OpGet n k a -> ReqR (OpGet n k a) forall op (ctx :: [ErrorMessage]). Require op ctx => Proxy ctx -> op -> ReqR op req (Proxy '[ 'Text "getting"] forall k (t :: k). Proxy t Proxy @ '[Text "getting"]) (SNat n -> Vec k a -> OpGet n k a forall (n :: Nat) (k :: Nat) a. SNat n -> Vec k a -> OpGet n k a OpGet SNat n n Vec k a v) > vecEx :: Vec ('S ('S ('S ('S 'Z)))) Char vecEx = Char 'a' Char -> Vec ('S ('S ('S 'Z))) Char -> Vec ('S ('S ('S ('S 'Z)))) Char forall a (n :: Nat). a -> Vec n a -> Vec ('S n) a :< Char 'b' Char -> Vec ('S ('S 'Z)) Char -> Vec ('S ('S ('S 'Z))) Char forall a (n :: Nat). a -> Vec n a -> Vec ('S n) a :< Char 'c' Char -> Vec ('S 'Z) Char -> Vec ('S ('S 'Z)) Char forall a (n :: Nat). a -> Vec n a -> Vec ('S n) a :< Char 'd' Char -> Vec 'Z Char -> Vec ('S 'Z) Char forall a (n :: Nat). a -> Vec n a -> Vec ('S n) a :< Vec 'Z Char forall a. Vec 'Z a VNil > a :: ReqR (OpGet' (Lt 'Z ('S ('S ('S ('S 'Z))))) 'Z ('S ('S ('S ('S 'Z)))) Char) a = SNat 'Z -> Vec ('S ('S ('S ('S 'Z)))) Char -> ReqR (OpGet' (Lt 'Z ('S ('S ('S ('S 'Z))))) 'Z ('S ('S ('S ('S 'Z)))) Char) forall (n :: Nat) (k :: Nat) a. Require (OpGet' (Lt n k) n k a) '[ 'Text "getting"] => SNat n -> Vec k a -> ReqR (OpGet' (Lt n k) n k a) get SNat 'Z SZ Vec ('S ('S ('S ('S 'Z)))) Char vecEx > c :: ReqR (OpGet' (Lt ('S ('S 'Z)) ('S ('S ('S ('S 'Z))))) ('S ('S 'Z)) ('S ('S ('S ('S 'Z)))) Char) c = SNat ('S ('S 'Z)) -> Vec ('S ('S ('S ('S 'Z)))) Char -> ReqR (OpGet' (Lt ('S ('S 'Z)) ('S ('S ('S ('S 'Z))))) ('S ('S 'Z)) ('S ('S ('S ('S 'Z)))) Char) forall (n :: Nat) (k :: Nat) a. Require (OpGet' (Lt n k) n k a) '[ 'Text "getting"] => SNat n -> Vec k a -> ReqR (OpGet' (Lt n k) n k a) get (SNat ('S 'Z) -> SNat ('S ('S 'Z)) forall (n :: Nat). SNat n -> SNat ('S n) SS (SNat ('S 'Z) -> SNat ('S ('S 'Z))) -> SNat ('S 'Z) -> SNat ('S ('S 'Z)) forall a b. (a -> b) -> a -> b $ SNat 'Z -> SNat ('S 'Z) forall (n :: Nat). SNat n -> SNat ('S n) SS SNat 'Z SZ) Vec ('S ('S ('S ('S 'Z)))) Char vecEx < e = get (SS $ SS $ SS $ SS SZ) vecEx --> • Error: Type error!, index out of bound: Trying to lookup the ShowTE ('S ('S ('S ('S 'Z))))th index in a vector of size ShowTE ('S ('S ('S ('S 'Z)))) trace: getting