【问题标题】:Type class instance with more restrictive signature具有更严格签名的类型类实例
【发布时间】:2017-02-02 03:07:36
【问题描述】:

假设我正在编写一个数据类型来表示笛卡尔坐标系中的坐标。我想在该数据类型上定义函数并使用 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) 不应该类型检查!从某种意义上说,如何解决这个问题,我怎样才能使两个坐标的实例比它更宽松?

我尝试了一堆扩展(InstanceSigs、FunctionalDependencies、类型约束......),但没有任何编译,很难判断这是否完全是错误的方式,或者我只需要稍微调整我的代码。

谢谢...

【问题讨论】:

  • 这是一个关于 typeclass 诡计的有趣问题。但是,在您的具体用例中,您可能需要考虑放弃 Slide 类,给 Coordinate x 一个 Num 实例并编写 newX 2 + newX 3,甚至是 2 + 3 :: Coordinate X
  • @duplode 我正在翻译一些 Python 代码并希望保留相同的 API,但在顶部添加一些类型安全性。现在我看到了答案,实际上看起来并没有那么棘手(与我见过的其他一些事情相比:>)。

标签: haskell typeclass


【解决方案1】:

考虑这个类声明的内容:

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

对于 any 类型 xSlide 的一个实例承诺给定一个 Coordinate x 和一个 a,它会给你一个 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

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

【讨论】:

  • 谢谢,这正是我几分钟前在#haskell 频道的帮助下想到的。似乎工作得很好!顺便说一句,FlexibleInstances 也是必需的,对吧?
  • 我的用例是将一些 Python 代码转换为 Haskell,我想保持 API 不变,但增加一些类型安全性。再次感谢...
  • 添加了缺少的扩展
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-29
  • 2021-09-21
相关资源
最近更新 更多