Skip to content

Part2_Sec8_1_eqproof

rpeszek edited this page Nov 20, 2018 · 3 revisions

Markdown generated from:
Source idr program: /src/Part2/Sec8_1_eqproof.idr
Source lhs program: /src/Part2/Sec8_1_eqproof.lhs

Section 8.1. Equality proofs vs Haskell

Dependent types are so rich that the type checker needs help in figuring things out. A proof of equality between natural numbers is needed to implement

exactLength : (len : Nat) -> (input : Vect m a) -> Maybe (Vect len a)

that, basically, checks if m is len and returns the vector if it is.

Idris code example

The following code matches section 8.1 in the book. exactLength is reimplemented in Part2_Sec8_3_deceq using the DecEq interface.

module Part2.Sec8_1_eqproof
import Data.Vect

%default total 

{- 
Returns proof that num1 and num2 are equal 

data (=) : a -> b -> Type where
     Refl : x = x 
cong : {func : a -> b} -> x = y -> func x = func y
-}
checkEqNat : (num1 : Nat) -> (num2 : Nat) -> Maybe (num1 = num2)
checkEqNat Z Z = Just Refl
checkEqNat Z (S k) = Nothing
checkEqNat (S k) Z = Nothing
checkEqNat (S k) (S j) = case checkEqNat k j of
                              Nothing => Nothing
                              Just prf => Just (cong prf)

{-
Notes: if the last line in defintion of exactLength is changed to
Just prf => ?hole

idris repl:
*Part2/Sec8_1_eqproof> :t hole
  a : Type
  m : Nat
  len : Nat
  prf : len = m
  input : Vect m a
--------------------------------------
hole : Maybe (Vect len a)


When last line is replaced with:
Just prf => Just input 
causes compiler error:

./Part2/Sec8_1_eqproof.idr:34:49-58:
   |
34 |                                     Just prf => Just input
   |                                                 ~~~~~~~~~~
When checking right hand side of Part2.Sec8_1_eqproof.case block in exactLength at ./Part2/Sec8_1_eqproof.idr:32:14-29 with expected type
        Maybe (Vect len a)

When checking argument x to constructor Prelude.Maybe.Just:
        Type mismatch between
                Vect m a (Type of input)
        and
                Vect len a (Expected type)
        
        Specifically:
                Type mismatch between
                        m
                and
                        len

Just prf => ?hole -- Just input

Using rewrite syntax from section 8.2 fixes the problem.
But order of len and m is important
`checkEqNat m len` would not work! (equational profs are not automatically symmetric -
sym function in 8.2)

TODO, also using Dec (section 8.3) fixes the problem, why?
This code compiles fine:

exactLength {m} len input = case decEq m len of
         Yes Refl => Just input
         No contra => Nothing
-}
exactLength : (len : Nat) -> (input : Vect m a) -> Maybe (Vect len a) 
exactLength {m} len input 
      = case checkEqNat len m of
                                    Nothing => Nothing
                                    Just prf => rewrite prf in Just input  -- syntax from 8.2

{- 
`exactLength` implementation in the book (repeated below) does not need `rewrite` syntax.
Maybe because it keeps nat value in scope (look at SameNat constructor) and is Nat sepecific. 
The need to have evidence of type/value being checked in scope seems to be important.
This relates to some of my Haskell experience (see F2 type family and solutions to example 1 and 2). 
-}

data EqNat : (num1 : Nat) -> (num2 : Nat) -> Type where
     SameNat : (num : Nat) -> EqNat num num

{- nicely generated by Idris by case splitting on third variable (eq) -}
sameS : (k : Nat) -> (j : Nat) -> (eq : EqNat k j) -> EqNat (S k) (S j)
sameS j j (SameNat j) = SameNat (S j)

checkEqNat1 : (num1 : Nat) -> (num2 : Nat) -> Maybe (EqNat num1 num2)
checkEqNat1 Z Z = Just (SameNat Z)
checkEqNat1 Z (S k) = Nothing
checkEqNat1 (S k) Z = Nothing
checkEqNat1 (S k) (S j) = case checkEqNat1 k j of
                              Nothing => Nothing
                              Just eq => Just (sameS _ _ eq)


{- The proof of equality allows this type of method to typecheck and
 rewrite statement is not used. -}
exactLength1 : (len : Nat) -> (input : Vect m a) -> Maybe (Vect len a) 
exactLength1 {m} len input 
      = case checkEqNat1 m len of
                                    Nothing => Nothing
                                    Just (SameNat len) => Just input

{- exercise 1, (x :) is just a function  -}
same_cons : {xs : List a} -> {ys : List a} ->
                      xs = ys -> x :: xs = x :: ys
same_cons prf = cong prf


{- exercise 2, knowing that x's are indentical, this theorem reduces to same_cons -}
same_lists : {xs : List a} -> {ys : List a} ->
                      x = y -> xs = ys -> x :: xs = y :: ys
same_lists Refl = same_cons

{- exercise 3, 4 -}
data ThreeEqual : a -> b -> c -> Type where
  Refl3 : ThreeEqual a a a

{- Idris knows that all 3 a's are the same, interstingly no need for cong -}
allSameS : (x, y, z : Nat) -> ThreeEqual x y z -> ThreeEqual (S x) (S y) (S z)
allSameS a a a Refl3 = Refl3

Compared to Haskell

As before, I try to keep Haskell code very close to Idris. This code uses the programmable :~: type equality defined in Data.Type.Equality that is a close equivalent of Idris' =

{-# LANGUAGE 
   GADTs
   , DataKinds
   , TypeOperators
   , TypeFamilies
   , PolyKinds 
#-}
{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
{-# OPTIONS_GHC -fwarn-unused-imports #-}

module Part2.Sec8_1_eqproof where
import Data.CodedByHand (Vect(..), Nat(..), SNat(..), vlength)
import Data.Type.Equality ((:~:)(Refl))

{- mimics concept of type level function in Idris-}
type family F (a :: k1) :: k2 

-- | type family is a type level partial function so if x = y => f x = f y
-- not really needed (mimics Idris)
cong :: (x :~: y) -> F x :~: F y
cong Refl = Refl 

checkEqNat :: SNat num1 -> SNat num2 -> Maybe (num1 :~: num2)
checkEqNat SZ SZ = Just Refl
checkEqNat SZ (SS k) = Nothing
checkEqNat (SS k) (SZ) = Nothing
checkEqNat (SS k) (SS j) = case checkEqNat k j of
      Nothing -> Nothing
      Just prf -> Just $ cong prf
      -- Note: SS :: SNat n -> SNat ('S n) which explains the use of cong
type instance F n = 'S n

{-| Note, GHC can figure out cong on its own -}
checkEqNat' :: SNat num1 -> SNat num2 -> Maybe (num1 :~: num2)
checkEqNat' SZ SZ = Just Refl
checkEqNat' SZ (SS k) = Nothing
checkEqNat' (SS k) (SZ) = Nothing
checkEqNat' (SS k) (SS j) = case checkEqNat' k j of
      Nothing -> Nothing
      Just prf -> case prf of Refl -> Just Refl

exactLength :: SNat len -> Vect m a -> Maybe (Vect len a) 
exactLength len vect = case checkEqNat (vlength vect) len of
        Nothing -> Nothing
        Just (Refl) -> Just vect

{- exercise examples -}
{- 
 type level partial application would be nice! 
 would make F2 redundant, this mimics Idris but is not really needed 
-}
type family F2 (a :: k1) (b:: k2 ):: k3 

{- I need z in scope for this to work -}
cong2 :: z -> (x :~: y) -> F2 z x :~: F2 z y
cong2 _ Refl = Refl 

same_cons :: x -> xs :~: ys -> x ': xs :~: x ': ys
same_cons x prf = cong2 x prf
-- ^ cong2 is not needed as this works as well:
-- same_cons x Refl = Refl
type instance F2 x xs = x ': xs

{-| cong-less version -}
same_cons' :: x -> xs :~: ys -> x ': xs :~: x ': ys
same_cons' x prf = case prf of Refl -> Refl

{- again need value of at least one type in scope -}
same_lists :: x -> x :~: y -> xs :~: ys -> (x ': xs) :~: (y ': ys) 
same_lists x Refl = same_cons x

data ThreeEqual a b c where
  Refl3 :: ThreeEqual a a a

allSameS :: SNat x -> SNat y -> SNat z -> ThreeEqual x y z -> ThreeEqual ('S x) ('S y) ('S z)
allSameS _ _ _ Refl3 = Refl3

{- Good enough to have evidence of just one -}
allSameS2 :: SNat x -> ThreeEqual x y z -> ThreeEqual ('S x) ('S y) ('S z)
allSameS2 _ Refl3 = Refl3

Conclusions

Maybe this are just basics but Haskell typechecker is great.

Why Proofs? The book presents the need for proofs as a natural consequence of using dependent types. This makes a lot of sense to me. If a type parameter has some underlying properties (e.g. Vect is parametrized by n : Nat which comes with an algebra), then it is only natural that the program will need to provide typechecker with some help (like n + m = m + n).