我想使用单例来表示类型级别的端口,并使用端口号的类型文字。像这样:
data Port = Port Integer
foo :: Sing ('Port 80)
foo = sing
bar :: Port
bar = fromSing foo
简短的问题形式是,如何实现这一点,或类似的东西?
我尝试使用单片机2.0.1和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)
所以,听起来它想要数据Port = Port Integer,但也无法构建:
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.
我设法比这更进一步,虽然不是全部,但不是使用单例库,而是自己实现简化版本。
{-# 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
到目前为止,非常好,现在可行了:
foo :: Sing ('Port 80)
foo = sing
但是我坚持从为港口实施。
instance SingKind ('KProxy :: KProxy Port) where
type DemoteRep ('KProxy :: KProxy Port) = Port
fromSing (SPort n) = Port (fromSing n)
此失败的类型错误与上面第一次使用单例库时显示的相同。现在,很清楚为什么:Nat的SingKind实例产生一个Integer,而不是Nat。它似乎必须,因为natVal产生一个整数。
所以,我被卡住了!
答案 0 :(得分:4)
现在主要的单身人士成语是参数化Port
:
data Port nat = Port nat
(包裹在相应的单身人士quasiquoter当然)
而且,您的正常数据级Port
将是:
type Port' = Port Integer
,您的类型级Port
将是:
Port Nat
(目前尚未允许作为同义词,但应在GHC 8.0中允许)
因此,您有类型 Port Integer
,其中包含值Port 1
,Port 2
等,以及种类 Port Nat
居住的Port 1
,Port 2
等
使用单例库的原因是因为Integer
是Nat
的单例,因此Port Nat
的单例是{{1 ,自动,免费。所以当你使用Port Integer
,SingI
,Sing
等时,一切都会像你一样有效 - 反映toSomeSing
- kinded类型会给你一个Port Nat
- 输入值,并确定Port Integer
- 类型值将为您提供Port Integer
- 种类型。
答案 1 :(得分:3)
如果没有单身人士,更简单的事情会为你效劳吗?
{-# 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))
这会给你
*Main> portNumber $ reify webPort
80