首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >有没有办法通知Haskell在sum类型中展开类型类?

有没有办法通知Haskell在sum类型中展开类型类?
EN

Stack Overflow用户
提问于 2018-02-13 19:06:08
回答 2查看 367关注 0票数 2

在这个例子之前,我将简要地解释一下我的思维链,这样如果其中任何一个都没有意义的话,我们也能解决这个问题。

假设对于我的数据声明的每个类型构造函数(产品类型之和),有一个参数具有给定类型类的实例。在我的头脑中,这意味着我可以向GHC/Haskell解释如何获得特定的类型,这样我的类型的行为就会像那个类型类的实例一样。

例如:

代码语言:javascript
复制
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...

我的眼睛里有一个清晰的图案,我想用某种方式来抽象化这个解包。

可以尝试为数据类型本身拥有一个实例:

代码语言:javascript
复制
instance Transformable Shape where
    setPosition' v (Circle _ ptr) = setPosition' v ptr
    -- [...]

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

缺点是,除了在实例声明中,我还必须通过手动重新包装类型类来‘重新实现’所有方法。对吗?

回到我的问题:有没有办法告诉Haskell如何从sum类型中展开类型类并对其进行操作?

我对镜头非常熟悉,但对TemplateHaskell一点也不熟悉,不过,如果使用上述特性是一个可能的解决方案,那就去做吧。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-02-13 19:56:25

您的runOnTransformable函数不可能按指定的方式写入,因为它的类型签名是错误的。

代码语言:javascript
复制
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扩展来使您能够写入正确的签名:

代码语言:javascript
复制
runOnTransformable :: (forall a. Transformable a => a -> IO ()) -> Shape -> IO ()

遗憾的是,在您这样做之后,我仍然不知道为Shape的所有不同构造函数实现这个函数的自动化方法。我认为应该有一些通用的、数据的、模板的Haskell之类的东西,但这是我所不知道的。希望我在这里写的内容足以让你朝着正确的方向前进。

票数 6
EN

Stack Overflow用户

发布于 2018-02-13 23:59:09

警告:推测答案,谨慎行事。

下面是一种使用数据族的替代方法。数据族本质上是一个类型级函数,它为其结果引入了全新的类型。在本例中,数据族ShapeDataTShape用于生成Shape字段的类型。

代码语言:javascript
复制
{-# 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

补充意见:

  • 除了数据家族, families,它会导致预先存在的类型,而不是新引入的类型。因为这里我们必须分别定义TCircleShapeTSquareShape等,所以我们最好通过一个数据系列来完成。
  • 我用空数据类型替换了您的Shape构造函数,然后使用这些数据类型来填补现在参数化的Shape类型的空白。和类型方法的一个显著区别是,可能的形状集现在对扩展开放。如果你需要关闭它,我相信它是可能的,通过达到一些甚至是幻想者:singletons -你会定义一个和类型data Shape' = Circle' | Square',然后使用https://hackage.haskell.org/package/singletons来促进构造函数的类型级别,并使用得到的类型作为Shape的参数。
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48773888

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档