0

I am trying to modify one of the fields of a custom data type in Haskell.

Following data type is similar to the data type I want to modify.

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

I want to be able to add more chapters to a book and modify an existing chapter of the book. I have read this question and its answers and this book but couldn't find what I was looking for. Is this possible without changing these declarations? I am using GHCi.

Community
  • 1
  • 1
Troglodyte
  • 3
  • 1
  • 3
  • This question is really multiple, smaller questions, such as "How can I insert an item into a list at a particular index?" – Rein Henrichs Mar 14 '16 at 23:06
  • I recommend looking up 'record syntax' and 'lenses' which would be a first step, well lenses maybe a second or third step if you are brand new to haskell. – epsilonhalbe Mar 14 '16 at 23:24

2 Answers2

2

I tried to keep things really simple - the goals is to make this working:

λ> 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"]]

rethinking the definitions

First I changed your definition a bit (why use MyData when you are fine with the type = typeConstructor in Chapter? - and why has a book a list of titles? - also I want to show books so add 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

start a book

next I added a simple function to start some book with it's title:

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

adding a chapter

addChapter is not much more difficult (note that I use ++ - in bigger scenarios you maybe want to represent the chapters in a back-to-front fashion and rewrite the Show to reverse on demand - but for now this is fine):

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

it just uses pattern-matching to deconstruct the book into it's parts, add the new chapter at the end and reconstructs a new Book with the old title and the new chapters.

modifying a chapter

you did not specify how you want to modify a chapter so I choose a very general form: basically you have to give a function that will modify a single chapter and next the name of the chapter to modify (ending with the book you want to modify):

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

it again deconstructs the book into it's parts put then changes the chapters by mapping the modifyAll function over them.

This function just looks for a chapter with the right title, modifies it and leaves all other unchanged.

At the end the book again is reconstructed from it's parts - the title is unchanged and the modified chapters are put in.


using this it's easy to write more specific modify-functions.

Let's say you want to change the chapter-title of a chapter - you just have to give the write modify function to modifyChapter:

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

exercises

Using the functions above:

  • write a function that starts a new chapter in a book
  • write a function that adds a page to a chapter
  • This one is harder: What if two chapters have the same title? Maybe the representation is not so good and it would be better to access the chapters not by their title but by their index - try to rewrite the functions accordingly
  • Probably you did the last one using (!!) - a better way might be to use data Book = Book BookTitle (Map ChapterIndex Chapter) with type ChapterIndex = Int and Map is Data.Map - can you rewrite the functions to use this too? Note that Data.Map uses functions quite similar to those here to modify maps (like adjust) ;)

remarks about lenses

Lenses (which where mentioned) basically gives an abstract mechanism to save you from writing functions like these - but IMO it's not a bad idea to get a feeling for this lover level functional-programming stuff anyway

Random Dev
  • 51,810
  • 9
  • 92
  • 119
1

You can declare your book as

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

and then

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

However, it will get clumsy on deeper updates. There is a lens package in cabal, that solves this problem.

Heimdell
  • 617
  • 5
  • 11