是否有更新嵌套数据结构的Haskell习惯用法?
假设我有以下数据模型,用于跟踪棒球运动员、球队和教练的统计数据:是否有更新嵌套数据结构的Haskell习惯用法?,haskell,functional-programming,clojure,static-typing,Haskell,Functional Programming,Clojure,Static Typing,假设我有以下数据模型,用于跟踪棒球运动员、球队和教练的统计数据: data BBTeam = BBTeam { teamname :: String, manager :: Coach, players :: [BBPlayer] } deriving (Show) data Coach = Coach { coachname :: String,
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) 这使用了大量的模式匹配,我必须得到所有构造函数的所有参数顺序正确。。。两次。它似乎不能很好地扩展或者很难维护/可读
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的记录语法提供的所有访问器,但我认为它也很难看、重复,而且很难维护和读取
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中有一些关于这个问题(或类似问题?)的东西:
updatein
——所以我想我在函数式编程、Haskell和静态类型的上下文中试图理解updatein
。记录更新语法是编译器的标准语法:
addManStk team = team {
manager = (manager team) {
diet = (diet (manager team)) {
steaks = steaks (diet (manager team)) + 1
}
}
}
糟透了!但是有更好的办法。Hackage上有几个包实现了功能引用和镜头,这正是您想要做的。例如,对于这个包,您可以在所有记录名前面加下划线,然后写入
$(mkLabels ['BBTeam, 'Coach, 'Diet, 'BBPlayer])
addManStk = modify (+1) (steaks . diet . manager)
2017年编辑,添加:如今,人们普遍认为包是一种特别好的实现技术。虽然它是一个非常大的软件包,但在网络上的各个地方都有非常好的文档和介绍材料。稍后,您可能还想看看一些通用编程库:当数据的复杂性增加时,您会发现自己编写了更多的代码和样板代码(比如增加球员的牛排含量、教练的饮食和观察家的啤酒含量)这仍然是陈词滥调,甚至没有那么详细。 可能是最著名的库(与Haskell平台一起提供)。事实上,使用非常类似的问题来演示该方法: 考虑以下描述公司组织结构的数据类型。公司分为多个部门。每个部门都有一名经理,并由多个子单位组成,其中一个单位是单个员工或一个部门。经理和普通员工都只是领取工资的人员 [跳过] 现在假设我们想将公司中每个人的工资提高一个特定的百分比。也就是说,我们必须编写函数: 增加:浮动->公司->公司 (其余部分在论文中-建议阅读) 当然,在您的示例中,您只需要访问/修改微小数据结构的一部分,这样就不需要通用方法(下面仍然是针对您的任务的基于SYB的解决方案),但一旦您看到访问/修改的重复代码/模式,您就需要检查这个或通用编程库
{-# 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)
正如Lambdageek所建议的那样,下面是如何使用语义编辑器组合器(SECs) 首先是几个有用的缩写:
type Unop a = a -> a
type Lifter p q = Unop p -> Unop q
这里的Unop
是一个“语义编辑器”,而screer
是语义编辑器组合器。
一些举重运动员:
onManager :: Lifter Coach BBTeam
onManager f (BBTeam n m p) = BBTeam n (f m) p
onDiet :: Lifter Diet Coach
onDiet f (Coach n c d) = Coach n c (f d)
onStakes :: Lifter Integer Diet
onStakes f (Diet n s e) = Diet n (f s) e
现在,只需编写几段文字来表达您的想法,即在经理(团队)的饮食中增加1:
与SYB方法相比,SEC版本需要额外的工作来定义SEC,我在本例中只提供了所需的部分。SEC允许有针对性的应用程序,如果玩家有节食习惯,这会很有帮助,但我们不想对其进行调整。也许还有一种相当不错的SYB方法来处理这一区别
编辑:以下是基本秒的替代样式:
onManager :: Lifter Coach BBTeam
onManager f t = t { manager = f (manager t) }
我不相信你的风格习惯会让Haskell看起来平易近人(@ChaosPandion--我不是一个高级的哈斯凯尔人,我肯定会很感激改进建议……这是一篇相当长的帖子,我花了很长时间在上面,我希望我能把它写得更简洁,更切题。不幸的是,我对哈斯凯尔自己来说相对较新。我对F#有更多的经验。类似的东西可能会有所帮助。另请参阅谢谢!这正是我想要的!你能解释一下关于镜头的更多信息吗?这就是我所掌握的功能概念吗?@Matt Fenwick:关于这个概念,没有什么其他的解释了;忽略实现问题和性能,它们基本上就是做你想做的事情。最简单的实现就是pai函数的r;一个从更大的东西中提取一个片段,另一个替换同一个片段。这里唯一的诀窍是,你可以通过创建一对组合函数来组合这样的函数对,这可能是你没有看到的函数概念。@Matt Fenwick是我最喜欢的Haskell写作的10篇文章之一。example of record语法不正确,无法编译。因为记录更新绑定比函数应用程序更紧密,所以您需要
manager=(manager team){diet=(diet(manager team)){steaks=steaks(diet(manager team))+1}
@OmariNorman您完全正确,感谢您指出这一点。我将很快进行编辑!
onManager :: Lifter Coach BBTeam
onManager f t = t { manager = f (manager t) }