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

时间:2018-02-13 19:06:08

标签: 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...

我眼中有一种清晰的模式,我希望能以某种方式抽象出这种展开方式。

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

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

    setAngle' f (Convex _ _ ptr) = setAngle' f ptr
    -- [...]

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

回到我的问题:有没有办法通知Haskell如何从总和类型解包和操作类型类?

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

2 个答案:

答案 0 :(得分:6)

您的runOnTransformable函数无法按指定编写,因为其类型签名错误。

runOnTransformable :: Transformable a => (a -> IO ()) -> Shape -> IO ()

表示对于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 ()

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

答案 1 :(得分:2)

警告:推测性答案,请小心处理。

以下是使用数据系列的替代方法。数据系列本质上是一种类型级功能,它为其结果引入了全新的类型。在这种情况下,数据系列ShapeDataTShape用于生成Shape字段的类型。

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}

data Circle'
data Square'

data family ShapeData s

newtype instance ShapeData Circle' = DCircle Float
    deriving (Eq, Show)
data instance ShapeData Square' = DSquare Float String
    deriving (Eq, Show)

data family TShape s

data instance TShape Circle' = TCircle
data instance TShape Square' = TSquare 

class Transformable a where
    setAngle' :: Float -> a -> IO ()

instance Transformable (TShape Circle') where 
    setAngle' _ _ = putStrLn ("Setting a circle angle is a no-op")

instance Transformable (TShape Square') where 
    setAngle' x _ = putStrLn ("Setting the square angle to " ++ show x)

data Shape a = Shape (ShapeData a) (TShape a)

instance Transformable (TShape a) => Transformable (Shape a) where 
    setAngle' x (Shape _ t) = setAngle' x t

补充说明:

  • 除了数据系列there are also type families之外,它还会导致预先存在的类型而不是新引入的类型。由于我们必须单独定义TCircleShapeTSquareShape等,我们不妨通过数据系列来实现。

  • 我将Shape构造函数替换为空数据类型,然后用于填充现在参数化Shape类型的间隙。与总和类型方法相关的一个显着差异是,可能的形状集现在可以扩展。如果您需要将其关闭,我相信可以通过达到更高级的东西:singletons - 您可以定义一个总和类型data Shape' = Circle' | Square',然后使用例如singletons machinery用于将构造函数提升为类型级别,并将结果类型用作Shape的参数。