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