2015-09-19 13 views
10

Ho il seguente modulo:Come provare la doppia negazione per i booleani di livello testo?

{-# 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 

La seconda definizione di norm non si compila, perché flipVec v rendimenti Vector (Not isCol) e quindi sprod vuole un Vector (Not (Not isCol)) come secondo argomento:

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)’ 

naturalmente potrei aggiungere il vincolo isCol ~ Not (Not isCol) al tipo di norm:

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

Nel sito di chiamata, il valore effettivo di isCol è noto e il compilatore vedrà che questo vincolo è effettivamente soddisfatto. Ma sembra strano che i dettagli di implementazione di norm stiano perdendo nella firma del tipo.

La mia domanda: è possibile convincere in qualche modo il compilatore che isCol ~ Not (Not isCol) è sempre vero, in modo che il vincolo superfluo non è necessario?

risposta

5

La risposta: sì, lo è. La prova è piuttosto banale se si hanno i tipi di dati corretti:

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 

Come si può vedere, il compilatore vede che la prova è banale previo controllo dei diversi casi. Troverai tutte queste definizioni di dati in diversi pacchetti, ad esempio singletons. Si utilizza la prova in questo modo:

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) 

Ovviamente questo è un sacco di lavoro per una cosa così banale. Se siete davvero sicuri di sapere cosa si sta facendo, è possibile "barare":

import Unsafe.Coerce 
import Data.Proxy 

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

Ciò consente di sbarazzarsi del vincolo SingI:

norm' :: forall isCol . Vector isCol -> Double 
norm' v = case double_neg' (Proxy :: Proxy isCol) of 
      Refl -> sqrt (flipVec v `sprod` v) 
+0

Impressionante, grazie! –

+4

È un po 'insoddisfacente che non possiamo eliminare il vincolo (senza imbrogliare) anche se sembra che davvero non abbiamo bisogno del dizionario in fase di runtime. C'è una ragione fondamentale per questo? O quale tipo di miglioramento per GHC sarebbe necessario per evitare il vincolo? –

+0

È un'uguaglianza proposizionale quindi richiede ancora una prova (banale). –