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

标签 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 则一无所知,但是,如果使用上述功能是一个可能的解决方案,那么无论如何,就去吧。

最佳答案

您的 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/

相关文章:

haskell - 如何避免在 Haskell 中检查空值?

haskell - 为什么 Monoidal 和 Applicative 定律告诉我们同样的事情?

haskell - 是否有不可折叠的东西的 map 累积?

haskell - 为什么 Haskell 中没有元组的 "general"访问器函数?

f# - 与 F# 中的 Haskell GADT 和类型类最接近的是什么?

haskell - 为什么分配后签名会改变

haskell - 让 GHCi 加载并解释带有 "foreign export"声明的模块(对于带有 C 的 FFI)?

Haskell:我应该使用 Data.Text.Lazy.Builder 来构造我的 Text 值吗?

haskell - rpar 和 rseq 表达式何时在 Haskell 程序中实际计算?

haskell - 在 ghci 下执行 `(read "[红色 ]")::[Color]` 时会发生什么?