是否有更新嵌套数据结构的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) }