有没有办法通知Haskell在sum类型中解包类型类

lhc*_*tti 2 haskell typeclass

我将在示例之前简要解释我的思想链,以便如果其中任何一个没有意义,我们也能够解决这个问题.

假设对于我的数据声明的每个类型构造函数(产品类型的总和),有一个参数具有给定类型类的实例.在我看来,这意味着我能够向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...
Run Code Online (Sandbox Code Playgroud)

我眼中有一种清晰的图案,我想以某种方式抽象出这种展开方式.

有人可能会尝试为数据类型本身设置一个实例:

instance Transformable Shape where
    setPosition' v (Circle _ ptr) = setPosition' v ptr
    -- [...]

    setAngle' f (Convex _ _ ptr) = setAngle' f ptr
    -- [...]
Run Code Online (Sandbox Code Playgroud)

缺点是我必须通过再次手动解包类型类来"重新实现"所有方法,除非它是在实例声明中.对?

回到我的问题:有没有办法通知Haskell如何从sum类型解包和处理类型类?

我对Lens很熟悉,而没有使用TemplateHaskell,但是,如果使用所述功能可能是一个可能的解决方案,那么就去吧.

ama*_*loy 6

您的runOnTransformable函数无法按指定进行写入,因为其类型签名是错误的.

runOnTransformable :: Transformable a => (a -> IO ()) -> Shape -> IO ()
Run Code Online (Sandbox Code Playgroud)

意味着对于任何a,其中主叫方runOnTransformable选,他们可以为您提供一个功能采取的具体a,你会调用该函数与a正确的类型,你就可以从某种程度上你有Shape对象产生的.现在,这显然是不可能的,因为它们可能会传递一个类型的函数,TSquareShape -> IO ()但是一个没有TSquareShape它的Shape .更糟糕的是,GHC会担心某人可能会定义instance Transformable Integer where {...},并且你需要能够处理这种情况,即使你的Shape类型没有任何方法可以猜测Integer给这个函数什么.

你不想说你的函数适用于任何函数Transformable a => a,而是调用者的函数必须适用于任何函数Transformable a => a,以便它愿意接受你的Shape类型中存在的任何值.您将需要RankNTypes扩展名以使您能够编写正确的签名:

runOnTransformable :: (forall a. Transformable a => a -> IO ()) -> Shape -> IO ()
Run Code Online (Sandbox Code Playgroud)

遗憾的是,在您完成此操作后,我仍然不知道为所有各种构造函数实现此函数的自动方式Shape.我认为应该可以使用Generic或Data,或者模板Haskell或其他东西,但这超出了我的知识范围.希望我在这里写的内容足以让你朝着正确的方向前进.