我将在示例之前简要解释我的思路,以便如果其中任何一个没有意义,我们也可以解决它。
假设对于我的数据声明(产品类型的总和)的每个类型构造函数,都有一个参数具有给定类型类的实例。在我看来,这意味着我将能够向 GHC/Haskell 解释如何获取该特定类型,以便我的类型最终表现得像该类型类的实例。
例如:
data Vector
-- The class type I talked about
class Transformable a where
setPosition' :: Vector -> a -> IO ()
setOrigin' :: Vector -> a -> IO ()
setAngle' :: Float -> a -> IO ()
-- ... this goes a long way
data TCircleShape
data TSquareShape
data TTriangleShape
data TConvexShape
-- Large sum type that defines different types of Shape
data Shape = Circle Float TCircleShape
| Square Float String TSquareShape
| Triangle Float TTriangleShape
| Convex [Vector] Float TConvexShape
-- ...
-- Almost all of the the Shape constructors have at least one
-- parameter that has an instance of the Transformable typeclass:
instance Transformable TCircleShape
instance Transformable TSquareShape
instance Transformable TTriangleShape
instance Transformable TConvexShape
-- What I would like to write then is:
runOnTransformable :: Transformable a => (a -> IO ()) -> Shape -> IO ()
runOnTransformable = undefined -- (???)
-- What I am doing right now is simply expanding Shape manually:
setShapePosition :: Vector -> Shape -> IO ()
setShapePosition v (Circle _ ptr) = setPosition' v ptr
setShapePosition v (Square _ _ ptr) = setPosition' v ptr
-- and so on...
setShapeAngle' :: Float -> Shape -> IO ()
setShapeAngle' f (Circle _ ptr) = setAngle' f ptr
setShapeAngle' f (Convex _ _ ptr) = setAngle' f ptr
-- and so on...
我的眼睛里有一个清晰的图案,我希望有某种方法以某种方式抽象出这种展开。
人们可能会尝试为数据类型本身创建一个实例:
instance Transformable Shape where
setPosition' v (Circle _ ptr) = setPosition' v ptr
-- [...]
setAngle' f (Convex _ _ ptr) = setAngle' f ptr
-- [...]
缺点是我必须通过再次手动解包类型类来“重新实现”所有方法,除非它是在实例声明中。对吗?
回到我的问题:有没有办法告诉 Haskell 如何从求和类型中解包并作用于类型类?
我对 Lens 的了解非常薄弱,对 TemplateHaskell 则一无所知,但是,如果使用上述功能是一个可能的解决方案,那么无论如何,就去吧。
最佳答案
您的 runOnTransformable
函数无法按照指定的方式编写,因为它的类型签名错误。
runOnTransformable :: Transformable a => (a -> IO ()) -> Shape -> IO ()
意味着对于 runOnTransformable
的调用者选择的任何 a
,它们可以为您提供一个采用特定 a 的函数
,然后您将使用正确类型的 a
来调用该函数,您将以某种方式从您拥有的 Shape 对象中生成该类型。现在,这显然是不可能的,因为它们可能会向您传递一个 TSquareShape -> IO ()
类型的函数,但其中没有 TSquareShape
的 Shape。更糟糕的是,GHC 会担心有人可能会在 {...} 处定义实例 Transformable Integer,并且您也需要能够处理这种情况,即使您的 Shape 类型没有任何方法可以猜测赋予该函数什么整数。
您不想说您的函数适用于任何 Transformable a => a
,而是说调用者的函数必须适用于任何 可转换 a => a
,这样它将愿意接受 Shape 类型中出现的任何值。您将需要 RankNTypes
扩展才能编写正确的签名:
runOnTransformable :: (forall a. Transformable a => a -> IO ()) -> Shape -> IO ()
遗憾的是,完成此操作后,我仍然不知道为 Shape
的所有各种构造函数实现此函数的自动化方法。我认为通用或数据、Haskell 模板或其他东西应该可以实现某些功能,但这超出了我的知识范围。希望我在这里写的内容足以让您朝着正确的方向前进。
关于haskell - 有没有办法通知 Haskell 在 sum 类型中解包类型类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48773888/