猿问

是否有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)这使用了很多模式匹配,我必须正确获取所有构造函数的所有参数顺序...两次。似乎扩展性不佳或难以维护/可读。


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中有一个针对此问题(或类似问题?)的东西: update-in-因此,我认为我试图update-in在函数式编程,Haskell和静态类型化的上下文中进行理解。


人到中年有点甜
浏览 381回答 3
3回答

波斯汪

正如Lambdageek所建议的,这就是使用语义编辑器组合器(SEC)的方式。首先是几个有用的缩写:type Unop a = a -> atype Lifter p q = Unop p -> Unop q这Unop是一个“语义编辑器”,并且Lifter是语义编辑器的组合器。一些起重器:onManager :: Lifter Coach BBTeamonManager f (BBTeam n m p) = BBTeam n (f m) ponDiet :: Lifter Diet CoachonDiet f (Coach n c d) = Coach n c (f d)onStakes :: Lifter Integer DietonStakes f (Diet n s e) = Diet n (f s) e现在,只需简单地使SEC组成即可说出您想要的内容,即在(经理)经理的饮食中加1:addManagerSteak :: Unop BBTeamaddManagerSteak = (onManager . onDiet . onStakes) (+1)与SYB方法相比,SEC版本需要额外的工作来定义SEC,而我仅在此示例中提供了所需的内容。SEC允许有针对性的应用程序,如果玩家节食但我们不想对其进行调整,这将很有帮助。也许还有一种很漂亮的SYB方法来处理这种区别。编辑:这是基本SEC的替代样式:onManager :: Lifter Coach BBTeamonManager f t = t { manager = f (manager t) }

素胚勾勒不出你

稍后,您可能还需要看一下一些通用的编程库:当数据的复杂性增加并且发现自己编写了更多的样板代码(例如,增加了球员,教练的饮食和看守者的啤酒含量)时,即使是不太冗长的形式,也仍然是样板。 SYB可能是最著名的库(Haskell Platform附带)。实际上,有关SYB的原始论文使用了非常相似的问题来演示该方法:考虑以下描述公司组织结构的数据类型。公司分为部门。每个部门都有一个经理,并且由一组子部门组成,其中一个部门可以是一个雇员或一个部门。经理和普通雇员都是领薪的人。[跳过]现在假设我们想将公司中每个人的薪水提高指定的百分比。也就是说,我们必须编写函数:增加::浮动->公司->公司(其余内容在论文中-建议阅读)当然,在您的示例中,您只需要访问/修改一个微小数据结构的一部分,因此它不需要通用方法(仍然在下面为您的任务提供基于SYB的解决方案),但是一旦您看到重复的代码/访问/模式您想检查此修改或其他通用编程库。{-# LANGUAGE DeriveDataTypeable #-}import Data.Genericsdata 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 -> BBTeamaddManagerSteak = everywhere (mkT incS)
随时随地看视频慕课网APP
我要回答