-
Notifications
You must be signed in to change notification settings - Fork 5
Part2_Sec9_1_elem
Markdown generated from:
Source idr program: /src/Part2/Sec9_1_elem.idr
Source lhs program: /src/Part2/Sec9_1_elem.lhs
This note is about type safe elem
function and mimicking this in Haskell.
{-
Code examples about
data Elem : a -> Vect k a -> Type where
Here : Elem x (x :: xs)
There : (later : Elem x xs) -> Elem x (y :: xs)
and
isElem : DecEq a => (value : a) -> (xs : Vect n a) -> Dec (Elem value xs)
-}
module Part2.Sec9_1_elem
import Data.Vect
%default total
%access public export
{- This type of code derives automatically with term search !-}
twoTest : Elem 2 [1,2,3]
twoTest = There Here
{-
second condition derived via interactive development resulted in absurd and I just
commented it out, the function is still total, uncommented compiles too
Note:
absurd : Uninhabited t => t -> a
later is uninhabited
-}
removeElem : (value : a) -> (xs : Vect (S n) a) ->
(prf : Elem value xs) ->
Vect n a
removeElem value (value :: ys) Here = ys
-- removeElem {n = Z} value (y :: []) (There later) = absurd later
removeElem {n = (S k)} value (y :: ys) (There later) = y :: removeElem value ys later
removeElem_auto : (value : a) -> (xs : Vect (S n) a) ->
{auto prf : Elem value xs} -> Vect n a
removeElem_auto value xs {prf} = removeElem value xs prf
{-
repl:
*Part2/Sec9_1_elem> removeElem 2 [1,2,3,4,5] (There Here)
[1, 3, 4, 5] : Vect 4 Integer
*Part2/Sec9_1_elem> removeElem_auto 2 [1,2,3,4,5]
[1, 3, 4, 5] : Vect 4 Integer
-}
data Test : (Vect 1 String) -> Type where
MkTest : Test (removeElem_auto "str" ["hello", "str"])
data Test1 : (Vect 1 String) -> Type where
MkTest1 : Test1 (removeElem "str" ["hello", "str"] (There Here))
{-
Note: removeElem seems to be not resolved/reduced at compile time, same it true for Haskell
repl:
*Part2/Sec9_1_elem> :t MkTest
MkTest : Test (removeElem_auto "str" ["hello", "str"])
*Part2/Sec9_1_elem> :t MkTest1
MkTest1 : Test1 (removeElem "str" ["hello", "str"] (There Here))
-}
{-
However: this does not compile :) but it does in Haskell :(
data TestWrong : (Vect 1 String) -> Type where
MkTestWrong : TestWrong (removeElem "str1" ["hello", "str"] (There Here))
Compiler error:
|
57 | MkTestWrong : TestWrong (removeElem "str1" ["hello", "str"] (There Here))
| ~~~~~~~~~~
When checking type of Part2.Sec9_1_elem.MkTestWrong:
When checking argument later to constructor Data.Vect.There:
Type mismatch between
Elem x (x :: xs) (Type of Here)
and
Elem "str1" ["str"] (Expected type)
Specifically:
Type mismatch between
"str1"
and
"str"
-}
{-
Type safe version of elem function
elem : Eq ty => (value : ty) -> (xs : Vect n ty) -> Bool
-}
notInNil : Elem value [] -> Void
notInNil Here impossible
notInNil (There _) impossible
notInTail : (notThere : Elem value xs -> Void) ->
(notHere : (value = x) -> Void) -> Elem value (x :: xs) -> Void
notInTail notThere notHere Here = notHere Refl
notInTail notThere notHere (There later) = notThere later
isElem' : DecEq a => (value : a) -> (xs : Vect n a) -> Dec (Elem value xs)
isElem' value [] = No notInNil
isElem' value (x :: xs) = case decEq value x of
Yes Refl => Yes Here
No notHere => case isElem' value xs of
Yes prf => Yes (There prf)
No notThere => No (notInTail notThere notHere)
{-
*Part2/Sec9_1_elem> isElem 1 [2,3,1]
Yes (There (There Here)) : Dec (Elem 1 [2, 3, 1])
-}
I am using only Data.SingBased
moving forward.
{-# LANGUAGE
TypeOperators
, GADTs
, TypeFamilies
, DataKinds
, PolyKinds
, ScopedTypeVariables
, StandaloneDeriving
, KindSignatures
, TypeInType -- helpful when type variable is both type and kind SomeConstraint k => xs :: Vect n k
, AllowAmbiguousTypes
, EmptyCase
, UndecidableInstances
, FlexibleContexts
#-}
{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
{-# OPTIONS_GHC -fwarn-unused-imports #-}
module Part2.Sec9_1_elem where
import Data.SingBased.Nat (Nat(..), type SNat, type Sing(..), s0, s1, s3)
import Data.SingBased.Vect (Vect(..), SomeKnownSizeVect(..), type Sing(..))
import Data.SingBased (sVectToVect)
import GHC.TypeLits hiding (Nat)
import Data.Void
import Data.Type.Equality
import Data.Singletons
import Part2.Sec8_3_deceq (Dec(..), DecEqSing(..), DecEq(..))
data Elem (ax :: a) (bx :: [a]) where
Here :: Elem x (x ': xs)
There :: Elem x xs -> Elem x (y ': xs)
deriving instance Show (Elem a b)
oneInList :: Elem 1 (1 ': 2 ':3 ': '[])
oneInList = Here
threeInList :: Elem 3 '[1,2,3]
threeInList = There (There Here)
This will not compile (good)
twoInList :: Elem 2 (1 ': 2 ':3 ': '[])
twoInList = Here
ghc error:
• Couldn't match type ‘2’ with ‘1’
Expected type: Elem 2 '[1, 2, 3]
Actual type: Elem 2 '[2, 2, 3]
The same approach works for Vectors
data VElem (ax :: a) (vx :: Vect k a) where
VHere :: VElem x (x '::: xs)
VThere :: VElem x xs -> VElem x (y '::: xs)
deriving instance Show (VElem a b)
twoInVect :: VElem 2 (1 '::: 2 '::: 3 '::: 'VNil)
twoInVect = VThere VHere
strInVect :: VElem "str" ("hello" '::: "str" '::: 'VNil)
strInVect = VThere VHere
Data.Void
defines function absurd :: Void -> a
which mimics void
in Idris.
This shuffles things around to mimic Idris.
void :: Void -> a
void = absurd
class Uninhabited t where
uninhabited :: t -> Void
{-| absurd' mimicking Idris -}
absurd' :: Uninhabited t => t -> Void
absurd' = void . uninhabited
A type family version of this (needed later)
TODO this needs more thinking
type family VoidF :: Void -> a where
type family UninhabitedF :: a -> Void where
This is very similar to Idris except the final result is demoted and fwarn-incomplete-patterns
does not work so well.
Side Note, I was not able to auto-generate singletons for Vect n a
itself.
removeElemDem :: forall (n :: Nat) (val :: a) (xs :: Vect (S n) a) . SingKind a =>
SNat n -> Sing val -> Sing xs -> VElem val xs -> Vect n (Demote a)
removeElemDem _ val (_ `SVCons` ys) VHere = sVectToVect ys
removeElemDem (SS n1) val (y `SVCons` ys) (VThere later) = (fromSing y) ::: (removeElemDem n1 val ys later)
{- While Idris knows that the following case is invalid,
GHC does not and I get, Pattern match(es) are non-exhaustive
attempt to implement it as 'absurd' later' causes compilation errors as well
using SVect GADT defined by hand instead of 'Sing xs' does not solve the issue either
-}
removeElemDem SZ _ (SVCons _ _) (VThere _) = undefined -- absurd' later
testRemoveElemDem = removeElemDem s0 s3 (SVCons s3 SVNil) VHere
This (equivalent, really) version does not Demote a
and it seems a bit closer to Idris' dependent pair:
removeElem :: forall (n :: Nat) (val :: a) (xs :: Vect (S n) a) .
SNat (S n) -> Sing val -> Sing xs -> VElem val xs -> SomeKnownSizeVect n a
removeElem (SS n) val (_ `SVCons` ys) VHere = MkSomeKnownSizeVect n ys
removeElem (SS (SS n)) val (y `SVCons` ys) (VThere later) =
let res = removeElem (SS n) val ys later
in case res of
MkSomeKnownSizeVect _ rys -> MkSomeKnownSizeVect (SS n) $ SVCons y rys
{- The absurd case remains, GHC does not know that we are dealing with one element list,
for example, replacing `SVCons _ _` with `SVCons _ SVNil` would cause Pattern match(es) are non-exhaustive
error
-}
removeElem (SS SZ) val (SVCons _ _) (VThere _) = undefined -- absurd' later
testRemoveElem = removeElem s1 s3 (SVCons s3 SVNil) VHere
ghci:
*Part2.Sec9_1_elem> testRemoveElemDem
VNil
*Part2.Sec9_1_elem> someKnownSizeVectToVect testRemoveElem
VNil
Nice!
I had somewhat less luck mimicking Idris' removeElem
using type families
type family RemoveElem (val :: a) (xs :: Vect (S n) a) (prf :: VElem val xs) :: Vect n a where
RemoveElem val (val '::: xs) 'VHere = xs
RemoveElem val (y '::: ys) ('VThere later) = y '::: RemoveElem val ys later
*Part2.Sec9_1_elem> :kind! (RemoveElem "str" ("hello" '::: "str" '::: 'VNil) ('VThere 'VHere))
(RemoveElem "str" ("hello" '::: "str" '::: 'VNil) ('VThere 'VHere)) :: Vect
('S 'Z) Symbol
= "hello" '::: 'VNil
*Part2.Sec9_1_elem> :kind! (RemoveElem "str1" ("hello" '::: "str" '::: 'VNil) ('VThere 'VHere))
<interactive>:1:52: error:
• Expected kind ‘VElem "str1" ("hello" '::: ("str" '::: 'VNil))’,
but ‘'VThere 'VHere’ has kind ‘VElem
"str1" ("hello" '::: ("str1" '::: 'VNil))’
• In the third argument of ‘RemoveElem’, namely
‘( 'VThere 'VHere)’
In the type ‘(RemoveElem "str1" ("hello"
::: ("str" ::: 'VNil)) ( 'VThere 'VHere))’
Good!
The following code is not very useful (similarly to Idris RemoveElem
is not resolved)
data Test (a :: Vect ('S 'Z) Symbol) where
Test :: Test (RemoveElem "str" ("hello" '::: "str" '::: 'VNil) strInVect)
Test
compiles (size is reduced by type family), but there is no evidence of type family actually working.
Unlike Idris, the following also compiles :(
data TestWrong (a :: Vect ('S 'Z) Symbol) where
TestWrong :: TestWrong (RemoveElem "str1" ("hello" '::: "str" '::: 'VNil) strInVect)
TODO I need to study these limitations more.
Interestingly RemoveElem
also complies when I include the absurd case!
type family RemoveElem' (val :: a) (xs :: Vect (S n) a) (prf :: VElem val xs) :: Vect n a where
RemoveElem' val (val '::: xs) 'VHere = xs
RemoveElem' val (y '::: 'VNil) ('VThere later) = VoidF (UninhabitedF later)
RemoveElem' val (y '::: ys) ('VThere later) = y '::: RemoveElem' val ys later
I can type the not logical case to invoke the uninhabited case for RemoveElem'
and the corresponding case is just not reduced for RemoveElem
ghci:
*Part2.Sec9_1_elem> :kind! (RemoveElem' "na" ("str" '::: 'VNil) ('VThere _))
(RemoveElem' "na" ("str" '::: 'VNil) ('VThere _)) :: Vect 'Z Symbol
= VoidF (UninhabitedF _)
*Part2.Sec9_1_elem> :kind! (RemoveElem "na" ("str" '::: 'VNil) ('VThere _))
(RemoveElem "na" ("str" '::: 'VNil) ('VThere _)) :: Vect 'Z Symbol
= RemoveElem "na" ("str" '::: 'VNil) ('VThere _)
I cannot evaluate similar nonsense in Idris
idris repl
*Part2/Sec9_1_elem> removeElem_auto "na" ["str"]
(input):1:1-28:When checking argument prf to function Part2.Sec9_1_elem.removeElem_auto:
Can't find a value of type
Elem "na" ["str"]
The notInNil
uses EmptyCase
instead of the impossible
Idris keyword
notInNil :: VElem ax 'VNil -> Void
notInNil x = case x of { }
notInTail :: (VElem ax xs -> Void) ->
((ax :~: x) -> Void) -> VElem ax (x '::: xs) -> Void
notInTail notThere notHere VHere = notHere Refl
notInTail notThere notHere (VThere later) = notThere later
isElemSing :: DecEqSing k => forall n (a :: k) (xs :: Vect n k) .
Sing a -> Sing xs -> Dec (VElem a xs)
isElemSing val SVNil = No notInNil
isElemSing val (SVCons x xs) = case decEqSing val x of
Yes Refl -> Yes VHere
No notHere -> case isElemSing val xs of
Yes prf -> Yes (VThere prf)
No notThere -> No (notInTail notThere notHere)
isElemNat :: SNat n -> Sing xs -> Dec (VElem n xs)
isElemNat val SVNil = No notInNil
isElemNat val (SVCons x xs) = case decEq val x of
Yes Refl -> Yes VHere
No notHere -> case isElemNat val xs of
Yes prf -> Yes (VThere prf)
No notThere -> No (notInTail notThere notHere)
ghci:
*Part2.Sec9_1_elem> isElemSing s1 (SVCons s1 SVNil)
Yes
*Part2.Sec9_1_elem> isElemNat s1 (SVCons s1 SVNil)
Yes
(s1
represents Nat number 1, see imports above)
I was not able to get far using the more general DecEq
from Sec8_3_deceq in implementing
isElem
.