如何在Haskell中修改自定义数据类型的字段

时间:2016-03-14 22:14:49

标签: haskell syntax record

我正在尝试修改Haskell中自定义数据类型的一个字段。

以下数据类型与我想要修改的数据类型类似。

type BookTitle    = String
type ChapterTitle = String
type Page         = String
data Chapter      = Chapter ChapterTitle [Page]
data Book         = MyData [BookTitle] [Chapter]

我希望能够在书中添加更多章节并修改本书的现有章节。 我已阅读this question and its answersthis book,但无法找到我要找的内容。这可能不改变这些声明吗?我正在使用GHCi。

2 个答案:

答案 0 :(得分:2)

我试图让事情变得非常简单 - 目标是使这个工作:

λ> let myBook = beginBook "Haskell"
λ> let myBook' = addChapter (Chapter "Intro" ["Hello","World"]) myBook
λ> let myBook'' = addChapter (Chapter "Two"  ["Haskell","is","fun"]) myBook'
λ> let myBook''' = modifyChapter 
                      (\ (Chapter _ pages) -> Chapter "Chapter 2" pages) 
                      "Two" myBook''
λ> myBook'''
Book "Haskell" 
     [ Chapter "Intro" ["Hello","World"]
     , Chapter "Chapter 2" ["Haskell","is","fun"]]

重新思考定义

首先我更改了您的定义(为什么在MyData中使用type = typeConstructor时使用Chapter? - 为什么有一本书的标题列表? - 我也是想要show本书,请添加deriving Show):

type BookTitle    = String
type ChapterTitle = String
type Page         = String
data Chapter      = Chapter ChapterTitle [Page] deriving Show
data Book         = Book BookTitle [Chapter] deriving Show

开始写一本书

接下来我添加了一个简单的函数,用它的标题开始一些书:

beginBook :: BookTitle -> Book
beginBook title = Book title []

添加章节

addChapter并不困难(请注意我使用++ - 在更大的场景中,您可能希望以前后方式表示章节并重写Show按需逆转 - 但现在这很好):

addChapter :: Chapter -> Book -> Book
addChapter nextChapter (Book title chapters) =
  Book title (chapters ++ [nextChapter])

它只是使用模式匹配来将书籍解构为它的部分,在最后添加新的章节并使用旧标题和新章节重新构建新的Book

修改章节

你没有指定你想如何修改章节所以我选择了一个非常通用的形式:基本上你必须提供一个修改单个章节的函数,接下来要修改章节的名称(以书为结尾)想要修改):

modifyChapter :: (Chapter -> Chapter) -> ChapterTitle -> Book -> Book
modifyChapter modify chapterTitle (Book titles chapters) =
  Book titles chapters'
  where chapters' = map modifyAll chapters
        modifyAll chapter@(Chapter chapterTitle' _)
          | chapterTitle == chapterTitle' = modify chapter
          | otherwise                     = chapter

它再次将书解构为它的部分然后通过将chapters函数映射到它们来更改modifyAll

此功能只查找具有正确标题的章节,修改它并使所有其他标题保持不变。

最后,本书再次从它的部分重建 - 标题不变,修改后的章节被放入。

使用它可以轻松编写更具体的修改函数。

我们假设您要更改章节的章节标题 - 您只需将modify函数写入modifyChapter

changeChapterTitle :: ChapterTitle -> ChapterTitle -> Book -> Book
changeChapterTitle oldTitle newTitle book =
  modifyChapter (\ (Chapter _ pages) -> Chapter newTitle pages) oldTitle book

练习

使用上述功能:

  • 编写一个能够开始书中新篇章的功能
  • 编写一个将章节添加到章节的功能
  • 这个更难:如果两章的标题相同怎么办?也许代表性不是很好,最好不要通过标题而是通过索引访问章节 - 尝试相应地重写函数
  • 可能您使用(!!)完成了最后一个 - 更好的方法是将data Book = Book BookTitle (Map ChapterIndex Chapter)type ChapterIndex = IntMap is Data.Map一起使用 - 您是否可以重写函数以使用它?请注意,Data.Map使用与此处非常类似的功能来修改地图(例如adjust);)

关于镜片的评论

镜头(其中提到的)基本上提供了一种抽象机制来帮助您免于编写这样的函数 - 但IMO对于感受这个情人级功能编程的东西并不是一个坏主意

答案 1 :(得分:0)

您可以将自己的图书声明为

data Book = MyData { titles :: [BookTitle], chapters :: [Chapter]}

然后

addChapter chapter book = book { chapters = chapter : chapters book }

然而, 会在更深层次的更新中变得笨拙。 cabal中有一个lens包,可以解决这个问题。