GHC.TypeLits 的 ScopedTypeVariables 和 RankNTypes
ScopedTypeVariables and RankNTypes with GHC.TypeLits
我正在尝试使用 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
。这工作正常,但是,我希望能够从外部源更新转换率,可能是文本文件或 API,例如 fixer。
显然,我可以将 return 类型包裹在 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)
(即一个 IO 操作 returning 一个 convert
类型的函数)以便它可以像这样使用:
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 位置,进行了类型检查,直到我尝试使用convert' <- getConvert
,此时它没有说有 No instance for (KnownSymbol n0)
我的问题是为什么不进行类型检查,函数的正确类型是什么 getConvert
?
首先我认为这可能是 ScopedTypeVariables
和 RankNTypes
以不同方式使用 forall
量词的事实,但切换 RankNTypes
没有效果。我也尝试按照 GHC 的建议将量词移到前面,但这并没有给我我需要的 rank-2 类型。
ImpredicativeTypes
不要 不能 真的行得通;避开他们。您可以将汇率转换 table 包装在保留其多态类型的 data
类型中,而不是使用 IO (forall a. b. ...)
。
data ExchangeRates = ExchangeRates {
getER :: forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> Currency b
}
和 return 一个 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)
我正在尝试使用 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
。这工作正常,但是,我希望能够从外部源更新转换率,可能是文本文件或 API,例如 fixer。
显然,我可以将 return 类型包裹在 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)
(即一个 IO 操作 returning 一个 convert
类型的函数)以便它可以像这样使用:
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 位置,进行了类型检查,直到我尝试使用convert' <- getConvert
,此时它没有说有 No instance for (KnownSymbol n0)
我的问题是为什么不进行类型检查,函数的正确类型是什么 getConvert
?
首先我认为这可能是 ScopedTypeVariables
和 RankNTypes
以不同方式使用 forall
量词的事实,但切换 RankNTypes
没有效果。我也尝试按照 GHC 的建议将量词移到前面,但这并没有给我我需要的 rank-2 类型。
ImpredicativeTypes
不要 不能 真的行得通;避开他们。您可以将汇率转换 table 包装在保留其多态类型的 data
类型中,而不是使用 IO (forall a. b. ...)
。
data ExchangeRates = ExchangeRates {
getER :: forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> Currency b
}
和 return 一个 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)