在这个例子之前,我将简要地解释一下我的思维链,这样如果其中任何一个都没有意义的话,我们也能解决这个问题。
假设对于我的数据声明的每个类型构造函数(产品类型之和),有一个参数具有给定类型类的实例。在我的头脑中,这意味着我可以向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如何从sum类型中展开类型类并对其进行操作?
我对镜头非常熟悉,但对TemplateHaskell一点也不熟悉,不过,如果使用上述特性是一个可能的解决方案,那就去做吧。
发布于 2018-02-13 19:56:25
您的runOnTransformable函数不可能按指定的方式写入,因为它的类型签名是错误的。
runOnTransformable :: Transformable a => (a -> IO ()) -> Shape -> IO ()这意味着对于调用a的人选择的任何runOnTransformable,他们可以为您提供一个接受特定a的函数,您将用正确类型的a调用该函数,该函数将以某种方式从您拥有的Shape对象中生成。现在,这显然是不可能的,因为它们可能传递给您一个类型为TSquareShape -> IO ()的函数,但是一个没有TSquareShape的形状。更糟糕的是,GHC会担心有人可能定义了instance Transformable Integer where {...},而且您也需要能够处理这种情况,即使您的形状类型没有任何方法来猜测Integer给这个函数提供了什么。
您不想说您的函数对任何Transformable a => a都有效,而是说调用者的函数必须对任何Transformable a => a都有效,这样它就愿意接受任何碰巧存在于您的形状类型中的值。您将需要RankNTypes扩展来使您能够写入正确的签名:
runOnTransformable :: (forall a. Transformable a => a -> IO ()) -> Shape -> IO ()遗憾的是,在您这样做之后,我仍然不知道为Shape的所有不同构造函数实现这个函数的自动化方法。我认为应该有一些通用的、数据的、模板的Haskell之类的东西,但这是我所不知道的。希望我在这里写的内容足以让你朝着正确的方向前进。
发布于 2018-02-13 23:59:09
警告:推测答案,谨慎行事。
下面是一种使用数据族的替代方法。数据族本质上是一个类型级函数,它为其结果引入了全新的类型。在本例中,数据族ShapeData和TShape用于生成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补充意见:
TCircleShape、TSquareShape等,所以我们最好通过一个数据系列来完成。Shape构造函数,然后使用这些数据类型来填补现在参数化的Shape类型的空白。和类型方法的一个显著区别是,可能的形状集现在对扩展开放。如果你需要关闭它,我相信它是可能的,通过达到一些甚至是幻想者:singletons -你会定义一个和类型data Shape' = Circle' | Square',然后使用https://hackage.haskell.org/package/singletons来促进构造函数的类型级别,并使用得到的类型作为Shape的参数。https://stackoverflow.com/questions/48773888
复制相似问题