No, you cannot write such a function.
A function of type
Int -> NatSing n
says that it can transform any integer into a polymorphic NatSing
. But there is no polymorphic NatSing
.
What you seem to want here is to have n
determined by the incoming Int
. That'd be a dependent type:
(n :: Int) -> NatSing n
Such a thing isn't possible in Haskell. You'd have to use Agda or Idris or another dependently typed language. The hack with singletons is exactly Haskell's way to get around this. If you want to make a distinction based on a value, you have to lift the value to the type level, which is what NatSing
is.
You could write a function that returns a NatSing
for some n
, by wrapping the n
up in an existential type:
data ExNatSing where
ExNatSing :: NatSing n -> ExNatSing
But this wouldn't give you much benefit in practice. By wrapping the n
up, you lose all information about it, and cannot make decisions based on it later.
By exactly the same argument, you can also not hope to define a function
list :: [a] -> List n a
The only approach you can use to save yourself some typing work is to define a type class that construct the NatSing
value automatically:
class CNatSing (n :: Nat) where
natSing :: NatSing n
instance CNatSing Zero where
natSing = ZeroSing
instance CNatSing n => CNatSing (Succ n) where
natSing = SuccSing natSing
Then, you can say:
list :: CNatSing n => [a] -> List n a
list xs = list' xs natSing
where
list' :: [a] -> NatSing n -> List n a
list' = ... -- same as before, but still partial
Here, the type context you use this in makes GHC fill in the right NatSing
. However, this function is still partial, because still the caller of the function can choose at what n
to use this. If I want to use a [Int]
of length 3
as a List (Succ Zero) Int
it's going to crash.
Again, you could wrap this up in an existential instead:
data SomeList a where
SomeList :: NatSing n -> List n a -> SomeList a
Then you could write
list :: [a] -> SomeList a
list [] = SomeList ZeroSing Nil
list (x : xs) = case list xs of
SomeList n xs -> SomeList (SuccSing n) (x : xs')
Again, the benefit is small, but in contrast to ExNatSing
, there at least is one: you now can temporarily unwrap a SomeList
and pass it to functions that operate on a List n a
, getting type-system guarantees on how the length of the list is transformed by these functions.