为了账号安全,请及时绑定邮箱和手机立即绑定

是否有Haskell惯用法来更新嵌套数据结构?

是否有Haskell惯用法来更新嵌套数据结构?

人到中年有点甜 2019-11-19 10:21:50
假设我有以下数据模型,用于跟踪棒球运动员,球队和教练的统计信息: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 -> BBTeamaddManagerSteak (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 -> BBTeamaddManStk 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-因此,我认为我试图update-in在函数式编程,Haskell和静态类型化的上下文中进行理解。
查看完整描述

3 回答

?
波斯汪

TA贡献1811条经验 获得超4个赞

正如Lambdageek所建议的,这就是使用语义编辑器组合器(SEC)的方式。


首先是几个有用的缩写:


type Unop a = a -> a

type Lifter p q = Unop p -> Unop q

这Unop是一个“语义编辑器”,并且Lifter是语义编辑器的组合器。一些起重器:


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

现在,只需简单地使SEC组成即可说出您想要的内容,即在(经理)经理的饮食中加1:


addManagerSteak :: Unop BBTeam

addManagerSteak = (onManager . onDiet . onStakes) (+1)

与SYB方法相比,SEC版本需要额外的工作来定义SEC,而我仅在此示例中提供了所需的内容。SEC允许有针对性的应用程序,如果玩家节食但我们不想对其进行调整,这将很有帮助。也许还有一种很漂亮的SYB方法来处理这种区别。


编辑:这是基本SEC的替代样式:


onManager :: Lifter Coach BBTeam

onManager f t = t { manager = f (manager t) }


查看完整回答
反对 回复 2019-11-19
?
素胚勾勒不出你

TA贡献1827条经验 获得超9个赞

稍后,您可能还需要看一下一些通用的编程库:当数据的复杂性增加并且发现自己编写了更多的样板代码(例如,增加了球员,教练的饮食和看守者的啤酒含量)时,即使是不太冗长的形式,也仍然是样板。 SYB可能是最著名的库(Haskell Platform附带)。实际上,有关SYB的原始论文使用了非常相似的问题来演示该方法:


考虑以下描述公司组织结构的数据类型。公司分为部门。每个部门都有一个经理,并且由一组子部门组成,其中一个部门可以是一个雇员或一个部门。经理和普通雇员都是领薪的人。


[跳过]


现在假设我们想将公司中每个人的薪水提高指定的百分比。也就是说,我们必须编写函数:


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


(其余内容在论文中-建议阅读)


当然,在您的示例中,您只需要访问/修改一个微小数据结构的一部分,因此它不需要通用方法(仍然在下面为您的任务提供基于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)


查看完整回答
反对 回复 2019-11-19
  • 3 回答
  • 0 关注
  • 379 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信