键入具有更严格签名的类实例

时间:2017-02-01 12:54:27

标签: haskell typeclass

让我们说我正在编写一种数据类型来表示笛卡尔坐标系中的坐标。我想在该数据类型上定义函数,并使用Haskell类型检查来防止将位于x轴上的数字与y轴上的数字混合。

这里是数据类型定义,具有跟踪坐标轴的幻像类型和构造值的两个函数:

data X
data Y

newtype Coordinate axis = Coordinate Int64 deriving (Show)

newX :: Int64 -> Coordinate X
newX = Coordinate

newY :: Int64 -> Coordinate Y
newY = Coordinate

让我们定义一个滑动函数,通过Int值或另一个Coordinate值滑动坐标。在第一种情况下,坐标应保持其轴,在第二种情况下,两个参数应具有相同的轴:

slideByInt :: Coordinate a -> Int64 -> Coordinate a
slideByInt (Coordinate x) y = Coordinate $ x + y

slideByCoord :: Coordinate a -> Coordinate a -> Coordinate a
slideByCoord (Coordinate x) (Coordinate y) = Coordinate (x + y)

这一切都很有效,它可以防止我在操纵坐标的函数中混淆X和Y轴。

我的问题是:如何在类后面包含slideByIntslideByCoord功能,以便我可以使用slide函数。这编译:

class Slide a where
  slide :: Coordinate x -> a -> Coordinate x

instance Slide Int64 where
  slide (Coordinate x) y = Coordinate (x + y)

instance Slide (Coordinate x) where
  slide (Coordinate x) (Coordinate y) = Coordinate (x + y)

但它不像独立函数那样类型安全:slide (newX 1) (newY 1)不应该键入check!从某种意义上说,如何解决这个问题,如何让两个坐标的实例不那么宽松呢?

我尝试了一堆扩展(InstanceSigs,FunctionalDependencies,类型约束......)但没有编译,很难判断这是完全错误的方式还是我只是我必须稍微调整一下我的代码。

...谢谢

1 个答案:

答案 0 :(得分:5)

考虑这个类声明的内容:

class Slide a where
  slide :: Coordinate x -> a -> Coordinate x

任何类型xSlide的实例承诺给出Coordinate xa,它会返回给你Coordinate x。对,那是你的问题。您不希望任何 x

我认为实现所需内容的最简单方法是使用坐标类型的第二个类型参数:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

class Slide x a where
  slide :: Coordinate x -> a -> Coordinate x

instance Slide X Int64 where
  slide = slideByInt

instance Slide Y Int64 where
  slide = slideByInt

instance Slide X (Coordinate X) where
  slide = slideByCoord

instance Slide Y (Coordinate Y) where
  slide = slideByCoord

最后两个实例实际上可以替换为这个更通用的实例:

{-# LANGUAGE TypeFamilies #-}

instance (a ~ b) => Slide a (Coordinate b) where
  slide = slideByCoord

对于它的价值,我喜欢避免以这种方式使用类型类。我不认为超载功能的直接便利是值得的样板和长期维护负担。但那只是我的意见。