I want to use singletons to represent ports at the type level, with type literals for the port numbers. Something like this:
data Port = Port Integer
foo :: Sing ('Port 80)
foo = sing
bar :: Port
bar = fromSing foo
Short form of question is, how to implement this, or something resembling it?
I tried using singletons-2.0.1 with ghc-7.10.3
{-# LANGUAGE ScopedTypeVariables, TemplateHaskell, TypeFamilies, GADTs, KindSignatures, DataKinds, PolyKinds, TypeOperators, FlexibleContexts, RankNTypes, UndecidableInstances, FlexibleInstances, InstanceSigs, DefaultSignatures, DataKinds, PolyKinds #-}
import Data.Singletons
import Data.Singletons.TH
import Data.Singletons.Prelude
import GHC.TypeLits
$(singletons [d|
data Port = Port Nat
|])
foo.hs:8:3:
Couldn't match type ‘Integer’ with ‘Nat’
Expected type: DemoteRep 'KProxy
Actual type: Nat
In the first argument of ‘toSing’, namely ‘b_a4Vk’
In the expression: toSing b_a4Vk :: SomeSing (KProxy :: KProxy Nat)
So, sounds like it wants data Port = Port Integer, but that also fails to build too:
foo.hs:8:3:
‘Port’ of kind ‘*’ is not promotable
In the kind ‘Port -> *’
In the type ‘(Sing :: Port -> *)’
In the type declaration for ‘SPort’
Failed, modules loaded: none.
I managed to get further than this, although not all the way, by not using the singletons library, but implementing a simplified version myself.
{-# LANGUAGE DataKinds, PolyKinds, TypeOperators, TypeFamilies, GADTs, FlexibleInstances, UndecidableInstances, ScopedTypeVariables, FlexibleContexts #-}
import GHC.TypeLits
import Data.Proxy (KProxy(..), Proxy(..))
data Port = Port Nat
data family Sing (x :: k)
class SingI t where
sing :: Sing t
class (kparam ~ 'KProxy) => SingKind (kparam :: KProxy k) where
type DemoteRep kparam :: *
fromSing :: Sing (a :: k) -> DemoteRep kparam
type SNat (x :: Nat) = Sing x
data instance Sing (n :: Nat) = KnownNat n => SNat
instance KnownNat n => SingI n where sing = SNat
instance SingKind ('KProxy :: KProxy Nat) where
type DemoteRep ('KProxy :: KProxy Nat) = Integer
fromSing (SNat :: Sing n) = natVal (Proxy :: Proxy n)
data instance Sing (x :: Port) where
SPort :: Sing n -> Sing ('Port n)
instance KnownNat n => SingI ('Port (n :: Nat)) where sing = SPort sing
So far, so good, now this works:
foo :: Sing ('Port 80)
foo = sing
But I'm stuck implementing fromSing for Port.
instance SingKind ('KProxy :: KProxy Port) where
type DemoteRep ('KProxy :: KProxy Port) = Port
fromSing (SPort n) = Port (fromSing n)
This fails with the same type error as shown for the first use of the singletons library above. And now, it's clear why: the SingKind instance for Nat produces an Integer, not a Nat. It seems it has to, because natVal produces an Integer.
So, I'm stuck!
The main singletons idiom right now is to parameterize Port:
data Port nat = Port nat
(wrapped in the corresponding singletons quasiquoter, of course)
And, your normal data-level Port would be:
type Port' = Port Integer
and your type-level Port would be:
Port Nat
(which isn't yet allowed as a kind synonym, but should be allowed in GHC 8.0)
So, you have the type Port Integer inhabited by the values Port 1, Port 2, etc., and the kind Port Nat inhabited by the types Port 1, Port 2, etc.
The reason why this works out with the singletons library is because Integer is the singleton for Nat, so the singleton for Port Nat is Port Integer, automatically, for free. So everything works out like you'd expect when you use SingI, Sing, toSomeSing, etc. -- reflecting a Port Nat-kinded type will give you a Port Integer-type value, and reifying a Port Integer-type value will give you a Port Nat-kinded type.
Would something much simpler, without singletons, work for you?
{-# language DataKinds, ScopedTypeVariables #-}
import Data.Proxy
import GHC.TypeLits
data Port = Port{ portNumber :: Integer }
data PortT = PortT Nat
webPort :: Proxy ('PortT 80)
webPort = Proxy
reify :: forall n. (KnownNat n) => Proxy ('PortT n) -> Port
reify _ = Port (natVal (Proxy :: Proxy n))
this gives you
*Main> portNumber $ reify webPort
80
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