Haskell: 函数应该接收多少类型?如何避免完全“重构”?

10

我有这些数据类型:

data PointPlus = PointPlus
    { coords :: Point
    , velocity :: Vector
    } deriving (Eq)

data BodyGeo = BodyGeo
    { pointPlus :: PointPlus
    , size :: Point
    } deriving (Eq)

data Body = Body
    { geo :: BodyGeo
    , pict :: Color
    } deriving (Eq)

这是我游戏中的字符、敌人、物品等的基本数据类型(目前只有两个矩形作为玩家和地面:p)。

按下按键时,角色通过改变其速度(velocity)向右、向左移动或跳跃。移动是通过将velocity添加到coords来实现的。当前代码如下:

move (PointPlus (x, y) (xi, yi)) = PointPlus (x + xi, y + yi) (xi, yi)

我只需要获取我的BodyPointPlus部分,而不是整个Body,否则就是:

move (Body (BodyGeo (PointPlus (x, y) (xi, yi)) wh) col) = (Body (BodyGeo (PointPlus (x + xi, y + yi) (xi, yi)) wh) col)

第一个版本的move更好吗?不管怎样,如果move只改变了PointPlus,那么肯定有另一个函数在新的Body内调用它。我解释一下:有一个名为update的函数,它被调用来更新游戏状态;现在只传递当前游戏状态中的单个Body,并返回更新后的Body

update (Body (BodyGeo (PointPlus xy (xi, yi)) wh) pict) = (Body (BodyGeo (move (PointPlus xy (xi, yi))) wh) pict)

这让我感觉有些有趣。在Body中除了PointPlus之外,一切都保持不变。是否有方法可以避免手动进行完整的“重构”呢?就像这样:

update body = backInBody $ move $ pointPlus body

当然,无需定义 backInBody

1个回答

14

您正在寻找 "镜头"。有几个不同的镜头包;这里有一个很好的它们的概述。

我的理解是,对于某个字段 b 的数据类型 a 上的镜头提供两种操作:一种是获取 b 的值的方法,另一种是获取具有不同 b 值的新 a 的方法。因此,您可以使用镜头来处理深度嵌套的 PointPlus

镜头包提供了有用的函数来处理镜头以及自动生成镜头(使用模板Haskell),这可能非常方便。

我认为在您的项目中值得研究它们,特别是因为由于数据类型的结构,您可能会在其他地方遇到类似嵌套的问题。


太完美了,特别是自动生成的部分! 那么move函数呢?它最好是接受整个Body还是只有它的PointPlus部分? - L01man
3
@L01man,我在我的一篇有关镜片的博客文章中也描述了一个与您非常相似的具体例子,链接在此:这里 - Gabriella Gonzalez
你的分步解释帮助我完全理解了透镜。 - L01man
你不觉得 Haskell 应该默认实现 Lenses 吗?类型是语言的一部分,而记录语法在某种程度上与 Lenses 重叠,但功能不如它强大。 - L01man

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接