I have a type parametrized by a natural number n:
data MyType (n :: Nat) = MyType
Operations on this type only make sense when n > 0, so I specified that as a constraint:
myFunction :: (KnownNat n, 1 <= n) => MyType n -> MyType n
myFunction = id
(Note that real versions of these functions do use n by turning it into a value with natVal.)
I want to create an existential type (SomeMyType) that lets us choose an n at runtime:
data SomeMyType where
SomeMyType :: forall n. (KnownNat n, 1 <= n) => MyType n -> SomeMyType
To produce values of SomeMyType, I'm writing a someMyTypeVal function that works like someNatVal:
someMyTypeVal :: Integer -> Maybe SomeMyType
someMyTypeVal n
| n > 0 = case someNatVal n of
Just (SomeNat (_ :: p n)) -> Just (SomeMyType (MyType @n))
Nothing -> Nothing
| otherwise = Nothing
This would work perfectly fine without the 1 <= n constraint, but with the constraint I get the following type error:
• Couldn't match type ‘1 <=? n’ with ‘'True’
arising from a use of ‘SomeMyType’
How can I get around this limitation? Since I've checked that n > 0 at runtime, I would not mind using an operation like unsafeCoerce here to convince GHC that 1 <= n is true—but I can't just use unsafeCoerce because that would lose the type-level value of n.
What's the best way of dealing with this?
After poking around a bit more, I found Justin L's answer to a similar question. He wrapped up his solution into the typelits-witnesses package which I was able to use to solve this problem pretty cleanly:
someMyTypeVal :: Integer -> Maybe SomeMyType
someMyTypeVal n = someNatVal n >>= check
where check :: SomeNat -> Maybe SomeMyType
check (SomeNat (_ :: p n)) = case (SNat @1) %<=? (SNat @n) of
LE Refl -> Just (SomeMyType (MyType @n))
NLE _ _ -> Nothing
a %<=? b lets us compare two type-level natural numbers and gives us a witness for whether a is less than b (LE) or not (NLE). This gives us the extra constraint in the LE case to return a SomeMyType, but trying to do that in the NLE case would still give us the "can't match ‘1 <=? n’ with ‘'True’" error.
Note the explicit type signature for check—without it, the type of check is inferred as check :: SomeNat -> Maybe a and I would get the following type error:
• Couldn't match type ‘a’ with ‘SomeMyType’
‘a’ is untouchable
inside the constraints: 'True ~ (1 <=? n)
With the explicit type signature everything works and the code is even (reasonably) readable.
As a more direct answer, the constraint 1 <= n is just a type alias for 1 <=? n ~ 'True. You can unsafely create such a type equality constraint with:
{-# LANGUAGE DataKinds, TypeOperators #-}
import Data.Type.Equality
import GHC.TypeLits
import Unsafe.Coerce
unsafeDeclarePositive :: p n -> (1 <=? n) :~: 'True
unsafeDeclarePositive _ = unsafeCoerce Refl
which is more or less what typelits-witnesses is doing under the hood.
With that definition in place, the following should type-check:
someMyTypeVal :: Integer -> Maybe SomeMyType
someMyTypeVal n
| n > 0 = case someNatVal n of
Just (SomeNat (pxy :: p n)) ->
case unsafeDeclarePositive pxy of
Refl -> Just (SomeMyType (MyType @n))
Nothing -> Nothing
| otherwise = Nothing
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