Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prove double negation for type level booleans?

I have the following module:

{-# LANGUAGE DataKinds, KindSignatures, TypeFamilies, RoleAnnotations #-}
module Main where

import Data.Coerce (coerce)

-- logical negation for type level booleans
type family Not (x :: Bool) where
    Not True = False
    Not False = True

-- a 3D vector with a phantom parameter that determines whether this is a
-- column or row vector
data Vector (isCol :: Bool) = Vector Double Double Double

type role Vector phantom

-- convert column to row vector or row to column vector
flipVec :: Vector isCol -> Vector (Not isCol)
flipVec = coerce

-- scalar product is only defined for vectors of different types
-- (row times column or column times row vector)
sprod :: Vector isCol -> Vector (Not isCol) -> Double
sprod (Vector x1 y1 z1) (Vector x2 y2 z2) = x1*x2 + y1*y2 + z1*z2

-- vector norm defined in terms of sprod
norm :: Vector isCol -> Double
-- this definition compiles
norm v = sqrt (v `sprod` flipVec v)
-- this does not (without an additional constraint, see below)
norm v = sqrt (flipVec v `sprod` v)

main = undefined

The second definition of norm does not compile, because flipVec v returns Vector (Not isCol) and hence sprod wants a Vector (Not (Not isCol)) as second argument:

Main.hs:22:34:                                                                                                                      
    Couldn't match type ‘isCol’ with ‘Not (Not isCol)’                                                                              
      ‘isCol’ is a rigid type variable bound by                                                                                     
              the type signature for norm :: Vector isCol -> Double                                                                 
              at Main.hs:20:9                                                                                                       
    Expected type: Vector (Not (Not isCol))                                                                                         
      Actual type: Vector isCol                                                                                                     
    Relevant bindings include                                                                                                       
      v :: Vector isCol (bound at Main.hs:22:6)                                                                                     
      norm :: Vector isCol -> Double (bound at Main.hs:22:1)                                                                        
    In the second argument of ‘sprod’, namely ‘v’                                                                                   
    In the first argument of ‘sqrt’, namely ‘(flipVec v `sprod` v)’

I could of course add the constraint isCol ~ Not (Not isCol) to the type of norm:

norm :: isCol ~ Not (Not isCol) => Vector isCol -> Double

At the call site, the actual value of isCol is known and the compiler will see that this constraint is indeed satisfied. But it seems weird that the implementation details of norm are leaking into the type signature.

My question: is it possible to somehow convince the compiler that isCol ~ Not (Not isCol) is always true, so that the superfluous constraint is not necessary?

like image 730
Tobias Brandt Avatar asked Sep 19 '15 15:09

Tobias Brandt


People also ask

What is double negation in Boolean algebra?

In classical logic, the double negation of any truth value or proposition is itself. More abstractly, double negation is the identity function on any boolean algebra.

What is double negation law?

Double Negation Law – A term that is inverted twice is equal to the original term A = A A double complement of a variable is always equal to the variable de Morgan’s Theorem – There are two “de Morgan’s” rules or theorems,

What is the meaning of 0 and 1 in Boolean logic?

In boolean logic, zero (0) represents false and one (1) represents true. In many applications, zero is interpreted as false and a non-zero value is interpreted as true. Mention the six important laws of boolean algebra. The six important laws of boolean algebra are:

What are the rules of Boolean algebra?

Boolean Algebra Rules. Following are the important rules used in Boolean algebra. Variable used can have only two values. Binary 1 for HIGH and Binary 0 for LOW. The complement of a variable is represented by an overbar. Thus, complement of variable B is represented as (bar{B}). Thus if B = 0 then (bar{B})=1 and B = 1 then (bar{B}) = 0.


1 Answers

The answer: yes, it is. The proof is quite trivial if you have the correct datatypes:

data family Sing (x :: k) 

class SingI (x :: k) where 
  sing :: Sing x 

data instance Sing (x :: Bool) where 
  STrue :: Sing True 
  SFalse :: Sing False 

type SBool x = Sing (x :: Bool)

data (:~:) x y where 
  Refl :: x :~: x 

double_neg :: SBool x -> x :~: Not (Not x) 
double_neg x = case x of 
                 STrue -> Refl 
                 SFalse -> Refl 

As you can see, the compiler will see that the proof is trivial upon inspection of the different cases. You'll find all of these data definitions in several packages, for example singletons. You use the proof like so:

instance Sing True where sing = STrue 
instance Sing False where sing = SFalse

norm :: forall isCol . SingI isCol => Vector isCol -> Double
norm v = case double_neg (sing :: Sing isCol) of 
           Refl -> sqrt (flipVec v `sprod` v)

Of course this a lot of work for such a trivial thing. If you are really sure you know what you're doing, you can "cheat":

import Unsafe.Coerce
import Data.Proxy 

double_neg' :: Proxy x -> x :~: Not (Not x) 
double_neg' _ = unsafeCoerce (Refl :: () :~: ())

This allows you to get rid of the SingI constraint:

norm' :: forall isCol . Vector isCol -> Double
norm' v = case double_neg' (Proxy :: Proxy isCol) of 
           Refl -> sqrt (flipVec v `sprod` v)
like image 123
user2407038 Avatar answered Oct 07 '22 04:10

user2407038