StackOverflow!
For reasons that would like to remain between me and God, I'm currently playing around with promoting runtime naturals to the type level. I've been following this approach with GHC.TypeLits, which has worked out fine so far.
However, in one instance, I have an additional constraint of 1 <= n
, i.e. my promoted natural not to be just any natural, but at least 1. This is also from GHC.TypeLits And I am unsure if/how it is possible to extract and make that information known.
Here's a minimal non-working example:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
import Data.Maybe
import Data.Proxy
import GHC.TypeLits
import Numeric.Natural
data AnyNat (n :: Nat) where
AN :: AnyNat n
data AtLeast1Nat (n :: Nat) where
AL1N :: AtLeast1Nat n
promote0 :: Natural -> AnyNat n
promote0 k = case sn of
SomeNat (_ :: Proxy p) -> AN
where
sn = (fromJust . someNatVal . toInteger) k
promote1 :: (KnownNat n, 1 <= n) => Natural -> AtLeast1Nat n
promote1 k = case sn of
SomeNat (_ :: Proxy p) -> AL1N
where
sn = (fromJust . someNatVal . toInteger) k
main :: IO ()
main = do nat_in <- getLine
let nat = read nat_in :: Natural
let p0 = promote0 nat
let p1 = promote1 nat
putStrLn "Last statement must be an expression"
This produces this error (full error here, but this is the relevant part):
* Couldn't match type `1 <=? n0' with 'True
arising from a use of `promote1'
The type variable `n0' is ambiguous
Honestly, this isn't too surprising and I (think I) do understand why this happens. The Natural
that we give in could be any of them, so why would we be able to derive that 1 <= n
? That's why it works fine for promote0
and not promote1
.
My question is hence, is there any way to also check (and propagate to type-level) this information so I can use it as intended, or am I using the wrong approach here?
You're using the wrong approach.
As discussed in the comments,
promote0
(and similarlypromote1
) isn't doing what you're hoping. The problem is that theAN
on the right-hand-side of the case has typeAnyNat n
for somen
entirely unrelated to the termsn
. You could have written:and gotten much the same effect. Note the critical difference between your code and the other Stack Overflow answer you link: in that answer, the type variable
n
in the case scrutinee is used to type something (viaScopedTypeVariables
) in the case branch. You bind a type variablep
in your scrutinee but then don't use it for anything.If we consider your actual problem, suppose we want to write something like this:
This won't type check. It gives an error on
V.fromList
about the lack of aKnownNat
constraint. The issue is thatv
has been assigned a typeS.Vector k Int
for somek :: Nat
. ButV.fromList
performs a runtime check that the length of the input list (the run time valuen
) is equal to the type-levelk
. To do this,k
must be converted to a runtime integer which requiresKnownNat k
.The general solution, as you've guessed, is to construct a
SomeNat
that basically contains aKnownNat n => n
that's unknown at compile time. However, you don't want to try to promote it to a known type-levelNat
(i.e., you don't wantpromote0
). You want to leave it as-is andcase
match on it at the point you need its type-level value. That type-level value will be available within thecase
but unavailable outside thecase
, so no types that depend onn
can "escape" the case statement.So, for example, you can write:
but you can't write:
You'll get an error that a type variable is escaping its scope. (If you want to let sized vectors leak outside the
case
, you need to make use ofV.SomeSized
or something similar.)As for the main part of your question about handling a
1 <= n
constraint, dealing with inequalities for type-level naturals is a major headache. I think you'll need to post a minimal example of exactly how you want to use this constraint in the context of a sized vector imlementation, in order to get a decent answer.