I have a closed type family that I use to calculate something at the type level. The result of that calculation is only valid if some input passes some constraints and I use a custom TypeError to give a type error if the input is wrong. Here is a simple example:
import Data.Proxy
import GHC.TypeLits
import Data.Bool
import Data.Function
type Foo (a :: Nat) (b :: Nat) = Foo1 (Div a b) (1 <=? b)
type family Foo1 (result :: Nat) (valid :: Bool) :: Nat where
Foo1 c 'True = c
Foo1 _ 'False = TypeError ('Text "Oops, divide by zero")
f :: (KnownNat a, KnownNat b, r ~ (Foo a b)) => Proxy a -> Proxy b -> Proxy b
f _ x = g x
g :: (KnownNat a, 1 <= a) => Proxy a -> Proxy a
g = id
Where an explicit check for division by zero is done. Obviously in this case 1 <= b trivially holds since I check for this exact case in my Foo type. This however doesn't compile since GHC doesn't understand this and it can't deduce the 1 <= b constraint:
• Could not deduce: (1 <=? b) ~ 'True arising from a use of ‘g’
from the context: (KnownNat a, KnownNat b, r ~ Foo a b)
bound by the type signature for:
f :: forall (a :: Nat) (b :: Nat) (r :: Nat).
(KnownNat a, KnownNat b, r ~ Foo a b) =>
Proxy a -> Proxy b -> Proxy b
at Test.hs:57:1-77
• In the expression: g x
In an equation for ‘f’: f _ x = g x
• Relevant bindings include
f :: Proxy a -> Proxy b -> Proxy b (bound at Test.hs:58:1)
|
58 | f _ x = g x
Is there a way for me to say to GHC "introduce" this constraint since I know for sure it holds? I don't want to write the "1 <= b" in my original type signature because it's essentially duplicate and will also pollute a lot of code that depends on my function with that constraint.
Edit:
I think the Singletons package can do what I want but I just don't know how to express it correctly...
import Data.Proxy
import GHC.TypeLits
import Data.Bool
import GHC.Err
import Data.Singletons.TypeLits ( Sing (SNat), SNat )
import Data.Singletons.Decide
import Data.Singletons.Prelude.Bool
import Data.Kind (Type)
import Data.Singletons.Prelude.Ord ((%<=))
type Foo (a :: Nat) (b :: Nat) = Foo1 (Div a b) (1 <=? b)
type family Foo1 (r :: Nat) (valid :: Bool) :: Nat where
Foo1 r 'True = r
Foo1 _ 'False = TypeError ('Text "Oops, divide by zero")
f :: (KnownNat a, KnownNat b, r ~ (Foo a b)) => SNat a -> SNat b -> SNat r
f _ b = case ((SNat @1) %<= b) %~ STrue of
(Proved k) -> g b SNat
(Disproved k) -> undefined
g :: (KnownNat b, 1 <= b) => SNat b -> SNat r -> SNat r
g _ x = x
1 <= bholds because if it didn't then yourTypeErrorwould be triggered? - luquifthat will pass with the given signature? I do think you'll have to rephraseFoo1or add an auxiliary family that describes the property -- I don't think it can be proven the wayFoo1is currently presented. (Because a type error is fatal AFAIK, you can't keep reasoning after you hit it) - luquifandg. I think this should be possible using the singletons library. - CryptoNoobFoo1? - CryptoNoob