如何在单身人士中使用类型级文字数字?

时间:2016-04-11 20:24:06

标签: haskell

我想使用单例来表示类型级别的端口,并使用端口号的类型文字。像这样:

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产生一个整数。

所以,我被卡住了!

2 个答案:

答案 0 :(得分:4)

现在主要的单身人士成语是参数化Port

data Port nat = Port nat

(包裹在相应的单身人士quasiquoter当然)

而且,您的正常数据级Port将是:

type Port' = Port Integer

,您的类型级Port将是:

Port Nat

(目前尚未允许作为同义词,但应在GHC 8.0中允许)

因此,您有类型 Port Integer,其中包含值Port 1Port 2等,以及种类 Port Nat居住的Port 1Port 2

使用单例库的原因是因为IntegerNat的单例,因此Port Nat的单例是{{1 ,自动,免费。所以当你使用Port IntegerSingISing等时,一切都会像你一样有效 - 反映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