Haskell有更新嵌套数据结构的习惯用法吗?

假设我有以下数据模型,用于跟踪棒球运动员,球队和教练的统计数据:

data BBTeam = BBTeam { teamname :: String, manager :: Coach, players :: [BBPlayer] } deriving (Show) data Coach = Coach { coachname :: String, favcussword :: String, diet :: Diet } deriving (Show) data Diet = Diet { dietname :: String, steaks :: Integer, eggs :: Integer } deriving (Show) data BBPlayer = BBPlayer { playername :: String, hits :: Integer, era :: Double } deriving (Show) 

现在让我们说,那些通常是牛排狂热分子的经理们想要吃更多的牛排 – 所以我们需要能够增加经理人饮食的牛排内容。 这个函数有两个可能的实现:

1)这使用了大量的模式匹配,我必须得到所有的构造函数的所有参数sorting…两次。 看起来它不会很好地扩展,或者是非常易于维护/可读的。

 addManagerSteak :: BBTeam -> BBTeam addManagerSteak (BBTeam tname (Coach cname cuss (Diet dname oldsteaks oldeggs)) players) = BBTeam tname newcoach players where newcoach = Coach cname cuss (Diet dname (oldsteaks + 1) oldeggs) 

2)这使用了Haskell的logging语法提供的所有访问器,但它也是丑陋和重复的,难以维护和阅读,我想。

 addManStk :: BBTeam -> BBTeam addManStk team = newteam where newteam = BBTeam (teamname team) newmanager (players team) newmanager = Coach (coachname oldcoach) (favcussword oldcoach) newdiet oldcoach = manager team newdiet = Diet (dietname olddiet) (oldsteaks + 1) (eggs olddiet) olddiet = diet oldcoach oldsteaks = steaks olddiet 

我的问题是,其中一个比另一个更好,或在Haskell社区更受欢迎? 有没有更好的方法来做到这一点(在保持上下文的同时修改数据结构内部的值)? 我不担心效率,只是代码优雅/一般性/可维护性。

我注意到在Clojure中有这样一个问题(或类似的问题): update-in – 所以我认为我正在尝试理解函数式编程和Haskell以及静态types的上下文中的update-in

logging更新语法标准与编译器:

 addManStk team = team { manager = (manager team) { diet = (diet (manager team)) { steaks = steaks (diet (manager team)) + 1 } } } 

可怕! 但是有一个更好的方法。 Hackage上有几个包可以实现function参考和镜头,这绝对是你想要做的。 例如,使用fclabels软件包,您可以在所有logging名称前加上下划线,然后写入

 $(mkLabels ['BBTeam, 'Coach, 'Diet, 'BBPlayer]) addManStk = modify (+1) (steaks . diet . manager) 

在2017年编辑补充道:这些日子, 镜头包装是一个特别好的实现技术,广泛的共识。 虽然它是一个非常大的软件包,但在networking上的各个地方也有很好的文档和介绍材料。

正如Lambdageek所build议的,您可以使用语义编辑器组合器(SEC)。

首先有几个有用的缩写:

 type Unop a = a -> a type Lifter pq = Unop p -> Unop q 

Unop这里是一个“语义编辑器”,而Lifter是语义编辑器组合器。 有些举重者:

 onManager :: Lifter Coach BBTeam onManager f (BBTeam nmp) = BBTeam n (fm) p onDiet :: Lifter Diet Coach onDiet f (Coach ncd) = Coach nc (fd) onStakes :: Lifter Integer Diet onStakes f (Diet nse) = Diet n (fs) e 

现在简单地组成证券交易委员会来说你想要的东西,即给经理(团队)饮食的风险加1:

 addManagerSteak :: Unop BBTeam addManagerSteak = (onManager . onDiet . onStakes) (+1) 

与SYB方法相比,SEC版本需要额外的工作来定义SEC,而我只提供了这个例子中所需要的。 证交会允许有针对性的应用,如果玩家有饮食,这将是有益的,但我们不想调整他们。 也许有一个相当SYB的方式来处理这个区别。

编辑:这是基本证交会的另一种风格:

 onManager :: Lifter Coach BBTeam onManager ft = t { manager = f (manager t) } 

后来你可能还想看看一些generics编程库:当数据的复杂性增加,你发现自己编写了更多的样板代码(比如增加了玩家的牛排内容,教练的饮食和观察者的啤酒内容)即使是不太冗长的forms,仍然是样板。 SYB可能是最知名的库(并且带有Haskell平台)。 实际上,SYB上的原始文件使用非常相似的问题来演示该方法:

考虑描述公司组织结构的以下数据types。 一个公司分为多个部门。 每个部门都有一个经理,由一个子单位组成,其中一个单位是单个员工或一个部门。 经理和普通员工都只是领薪水的人。

[skiped]

现在假设我们想要将公司中每个人的工资提高一个特定的百分比。 那就是我们必须写这个函数:

增加::浮动 – >公司 – >公司

(其余的在纸上 – 推荐阅读)

当然在你的例子中,你只需要访问/修改一个微小的数据结构,所以它不需要通用的方法(仍然基于SYB的解决scheme为您的任务下面),但一旦你看到重复代码/访问/修改你我想检查这个或其他通用编程库。

 {-# LANGUAGE DeriveDataTypeable #-} import Data.Generics data BBTeam = BBTeam { teamname :: String, manager :: Coach, players :: [BBPlayer]} deriving (Show, Data, Typeable) data Coach = Coach { coachname :: String, favcussword :: String, diet :: Diet } deriving (Show, Data, Typeable) data Diet = Diet { dietname :: String, steaks :: Integer, eggs :: Integer} deriving (Show, Data, Typeable) data BBPlayer = BBPlayer { playername :: String, hits :: Integer, era :: Double } deriving (Show, Data, Typeable) incS d@(Diet _ s _) = d { steaks = s+1 } addManagerSteak :: BBTeam -> BBTeam addManagerSteak = everywhere (mkT incS)