检索幻像类型的隐藏类型
Retrieve hidden type of a phantom type
我用 Haskell 声明了一个像这样的幻像类型。
newtype Length (a::UnitLength) b = Length b deriving (Eq,Show)
data UnitLength = Meter
| KiloMeter
| Miles
deriving (Eq,Show)
现在,我想写一些函数来使用这个类型。但是我没有碰巧看到和使用隐藏类型。
是否可以检索幻像类型Length
的隐藏类型a
来进行测试,模式匹配,....?
如果你想要你使用的虚拟类型的运行时表示,你必须使用我们所说的单例。 UnitLength
中的每个构造函数都有一个构造函数,它们的类型准确地说明了我们正在考虑的构造函数:
data SUnitLength (a :: UnitLength) where
SMeter :: SUnitLength Meter
SKiloMeter :: SUnitLength KiloMeter
SMiles :: SUnitLength Miles
现在你已经有了这个,你可以写一个显示函数,根据虚参数选择正确的单位缩写:
display :: Show b => SUnitLength a -> Length a b -> String
display sa l = show (payload l) ++
case sa of
SKiloMeter -> "km"
_ -> "m"
现在,这并不真正符合您的需求:参数 a
可以在类型 Length a b
中使用,但我们仍然需要手工制作 witness。这很烦人。避免这个问题的一种方法是定义一个类型 class 来为我们完成这项工作。 CUnitLength a
告诉我们,提供类型 Length a b
的值,我们可以获得 a
具有的形状的见证 SUnitLength a
。
class CUnitLength (a :: UnitLength) where
getUnit :: Length a b -> SUnitLength a
我们很容易为各种UnitLength
构造函数编写CUnitLength
的实例:getUnit
甚至可以忽略它的参数!
instance CUnitLength Meter where
getUnit _ = SMeter
instance CUnitLength KiloMeter where
getUnit _ = SKiloMeter
instance CUnitLength Miles where
getUnit _ = SMiles
那么为什么要为 getUnit
的论点烦恼呢?好吧,如果我们删除它,getUnit
需要以某种方式神奇地猜测它应该描述哪个 a
。有时可以根据调用站点的预期类型推断出 ̀a
但有时不是。使用 Length a b
参数可以保证所有调用都是明确的。无论如何,我们总是可以恢复更简单的 getUnit'
:
getUnit' :: CUnitLength a => SUnitLength a
getUnit' = getUnit (undefined :: Length a ())
这导致我们找到最后一个定义 display'
,它与 display
具有相同的作用,但不需要额外的参数:
display' :: (CUnitLength a, Show b) => Length a b -> String
display' = display getUnit'
我已将所有内容(包括 LANGUAGE 扩展,以及 payload
的定义以从 Length a b
中提取 b
)放在 self-contained gist 中,以备不时之需玩代码。
我用 Haskell 声明了一个像这样的幻像类型。
newtype Length (a::UnitLength) b = Length b deriving (Eq,Show)
data UnitLength = Meter
| KiloMeter
| Miles
deriving (Eq,Show)
现在,我想写一些函数来使用这个类型。但是我没有碰巧看到和使用隐藏类型。
是否可以检索幻像类型Length
的隐藏类型a
来进行测试,模式匹配,....?
如果你想要你使用的虚拟类型的运行时表示,你必须使用我们所说的单例。 UnitLength
中的每个构造函数都有一个构造函数,它们的类型准确地说明了我们正在考虑的构造函数:
data SUnitLength (a :: UnitLength) where
SMeter :: SUnitLength Meter
SKiloMeter :: SUnitLength KiloMeter
SMiles :: SUnitLength Miles
现在你已经有了这个,你可以写一个显示函数,根据虚参数选择正确的单位缩写:
display :: Show b => SUnitLength a -> Length a b -> String
display sa l = show (payload l) ++
case sa of
SKiloMeter -> "km"
_ -> "m"
现在,这并不真正符合您的需求:参数 a
可以在类型 Length a b
中使用,但我们仍然需要手工制作 witness。这很烦人。避免这个问题的一种方法是定义一个类型 class 来为我们完成这项工作。 CUnitLength a
告诉我们,提供类型 Length a b
的值,我们可以获得 a
具有的形状的见证 SUnitLength a
。
class CUnitLength (a :: UnitLength) where
getUnit :: Length a b -> SUnitLength a
我们很容易为各种UnitLength
构造函数编写CUnitLength
的实例:getUnit
甚至可以忽略它的参数!
instance CUnitLength Meter where
getUnit _ = SMeter
instance CUnitLength KiloMeter where
getUnit _ = SKiloMeter
instance CUnitLength Miles where
getUnit _ = SMiles
那么为什么要为 getUnit
的论点烦恼呢?好吧,如果我们删除它,getUnit
需要以某种方式神奇地猜测它应该描述哪个 a
。有时可以根据调用站点的预期类型推断出 ̀a
但有时不是。使用 Length a b
参数可以保证所有调用都是明确的。无论如何,我们总是可以恢复更简单的 getUnit'
:
getUnit' :: CUnitLength a => SUnitLength a
getUnit' = getUnit (undefined :: Length a ())
这导致我们找到最后一个定义 display'
,它与 display
具有相同的作用,但不需要额外的参数:
display' :: (CUnitLength a, Show b) => Length a b -> String
display' = display getUnit'
我已将所有内容(包括 LANGUAGE 扩展,以及 payload
的定义以从 Length a b
中提取 b
)放在 self-contained gist 中,以备不时之需玩代码。