我尝试使用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)
在此,我使用ScopedTypeVariables
向KnownSymbol 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)
的正确类型是什么?
首先我认为getConvert
和ScopedTypeVariables
可能会以不同的方式使用RankNTypes
量词,但切换forall
没有任何效果。我也尝试将量词器移到GHC建议的前面,但这并没有给出我需要的rank-2类型。
答案 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)