I've been working with natural numbers with additional type-level parity information. succ
has been successfully implemented in the most straightforward way:
succ :: Natural p -> Natural (Opp p)
succ = Succ
However, I'm still struggling with getting pred
to typecheck. A minimal example:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
data Parity = Even | Odd
type family Opp (p :: Parity) = (r :: Parity) | r -> p where
Opp 'Odd = 'Even
Opp 'Even = 'Odd
data Natural :: Parity -> * where
Zero :: Natural 'Even
Succ :: Natural p -> Natural (Opp p)
pred :: Natural (Opp p) -> Natural p
pred (Succ n) = n
What can I do to successfully implement pred
? Right now I'm getting many different large and complicated type errors, especially could not deduce Opp p ~ p1
.
Calculating a parity bit, even or odd, is a straightforward thing to do. For odd parity, count the 1s in the message, and if their number is even, add a 1 at a specified position; otherwise, add a 0. For even parity, do the opposite: if the number of 1s is even, add a 0; otherwise, add a 1.
In an even parity check, parity bits ensure there are an even number of 1s and 0s in the transmission. In an odd parity check, there are an odd number of 1s and 0s in the transmission.
There are two kinds of parity bits: In even parity, the number of bits with a value of one are counted. If that number is odd, the parity bit value is set to one to make the total number of ones in the set (including the parity bit) an even number.
EXAMPLE. Consider the data unit to be transmitted is 10010001 and even parity is used. On receiving the modified code word, receiver finds the number of 1's is even and even parity is used. So, receiver assumes that no error occurred in the data during transmission though the data is corrupted.
As @chi indicated,
Injectivity annotations are not exploited by GHC for anything except for allowing some types that would be otherwise considered ambiguous. Only for that. They are not used, as one would expect, to infer
a ~ b
fromF a ~ F b
. Personally, I consider them to be nearly useless, in their current form.
So you have to define Natural
a bit differently:
data Natural :: Parity -> * where
Zero :: Natural 'Even
Succ :: (p ~ Opp s, s ~ Opp p) => Natural p -> Natural s
Now you can get both of the things you need.
Given singletons for Parity
:
data SParity :: Parity -> Type where
SEven :: SParity Even
SOdd :: SParity Odd
We can prove the injectivity of Opp
oppInj' :: Opp p ~ Opp q => SParity p -> SParity q -> p :~: q
oppInj' SEven SEven = Refl
oppInj' SOdd SOdd = Refl
Now we can define:
data Natural' :: Parity -> Type where
Zero' :: Natural' Even
Succ' :: SParity p -> Natural' p -> Natural' (Opp p)
pred' :: SParity p -> Natural' (Opp p) -> Natural' p
pred' p (Succ' q n) = case oppInj' p q of Refl -> n
You may safely perform erasure to get rid of all the singleton junk:
-- for maximum symmetry, instead of relying on type applications we could
-- just substitute Proxy# in place of SParity everywhere, but meh
oppInj :: forall p q. Opp p ~ Opp q => p :~: q
oppInj = unsafeCoerce Refl -- we know this is OK because oppInj' exists
data Natural :: Parity -> Type where
Zero :: Natural Even
Succ :: Natural p -> Natural (Opp p)
pred :: forall p. Natural (Opp p) -> Natural p
pred (Succ (n :: Natural q)) = case oppInj @p @q of Refl -> n
This pattern, doing something with singletons and then erasing them for an improvement in space and time (here it's just a constant factor) is common when doing dependently typed programming in Haskell. Normally, you wouldn't write Natural'
or pred'
, but they are useful as guides for writing the erased versions.
PS: Make sure to handle the Zero
case!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With