具有GHC.TypeLits的ScopedTypeVariables和RankNTypes

时间:2016-08-03 22:38:40

标签: haskell types ghc forall

我尝试使用DataKinds和类型级文字来制作类型安全的货币转换库。到目前为止,我已经定义了这些数据类型:

data Currency (s :: Symbol) = Currency Double
    deriving Show

type USD = Currency "usd"
type GBP = Currency "gbp"

usd :: Double -> USD
usd = Currency

gbp :: Double -> GBP
gbp = Currency

data SProxy (s :: Symbol) = SProxy

还有一个允许我在它们之间进行转换的功能:

convert :: forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> Currency b
convert (Currency a) = case (symbolVal (SProxy :: SProxy a), 
                             symbolVal (SProxy :: SProxy b)) of
          ("usd", "gbp") -> Currency (a * 0.75)
          ("gbp", "usd") -> Currency (a * 1.33)

在此,我使用ScopedTypeVariablesKnownSymbol a提供约束symbolVal SProxy。这很好,但是,我希望能够从外部源(可能是文本文件或诸如fixer的API)更新转换率。

显然,我可以将返回类型包装在IO中,形成

convert :: forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> IO (Currency b)

但我希望能够保留纯API。我的第一个想法是使用unsafePerformIO获取转换率图,但这是不安全的,所以我认为我可以使用另一个函数getConvert,其类型为

的效果
getConvert :: IO (forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> Currency b)

(即返回convert类型函数的IO操作),以便可以像这样使用:

do
    convert <- getConvert
    print $ convert (gbp 10) :: USD

然而,我一直无法进行打字检查 - GHC抱怨它:

Couldn't match expected type ‘forall (a :: Symbol) (b :: Symbol). 
                              (KnownSymbol a, KnownSymbol b) => 
                              Currency a -> Currency b’ 
with actual type ‘Currency a0 -> Currency b0’

当我让GHC推断return convert的类型时,它没有推断出我想要的类型,而是将forall a b移动到prenex位置,进行类型检查,直到我尝试使用{{ 1}},此时它没有说有convert' <- getConvert

我的问题是为什么这不是类型检查,以及函数No instance for (KnownSymbol n0)的正确类型是什么?

首先我认为getConvertScopedTypeVariables可能会以不同的方式使用RankNTypes量词,但切换forall没有任何效果。我也尝试将量词器移到GHC建议的前面,但这并没有给出我需要的rank-2类型。

1 个答案:

答案 0 :(得分:3)

ImpredicativeTypes 无法正常工作;避免他们。您可以将汇率换算表换成保留其多态类型的IO (forall a. b. ...)类型,而不是使用data

data ExchangeRates = ExchangeRates {
    getER :: forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> Currency b
}

并返回IO ExchangeRates而不是

-- getConvert :: IO (forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> Currency b)
getConvert    :: IO ExchangeRates
getConvert = return (ExchangeRates convert)

几乎按照您的预期使用它。请注意括号将:: USD类型签名与转换后的值进行分组。

main = do
    convert <- getER <$> getConvert
    print $ (convert (gbp 10) :: USD)