检索隐藏类型的幻像类型

时间:2015-12-21 23:13:06

标签: haskell phantom-types

我用Haskell声明了这样的幻像类型。

newtype Length (a::UnitLength) b = Length b deriving (Eq,Show)
data UnitLength = Meter
                | KiloMeter
                | Miles
                deriving (Eq,Show)

现在,我想写一些函数来使用这种类型。但我没有碰巧看到并使用隐藏类型。

是否可以检索幻像类型a的隐藏类型Length以执行测试,模式匹配,....?

1 个答案:

答案 0 :(得分:4)

如果您想要使用您使用的幻像类型的运行时表示,则必须使用我们称之为单例的方法。对于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"

现在,这并不符合您的要求:参数aLength a b类型中可用,但我们仍然需要手工制作见证。那很烦人。避免此问题的一种方法是定义一个为我们工作的类型类。 CUnitLength a告诉我们,如果提供Length a b类型的值,我们就可以获得SUnitLength a形状的见证a

class CUnitLength (a :: UnitLength) where
  getUnit :: Length a b -> SUnitLength a

我们很容易为各种CUnitLength构造函数编写UnitLength的实例: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'

我已将payload中的所有内容(包括LANGUAGE扩展名和b的定义)从Length a b中提取ObjectFactory,以防您想玩用代码。