Search code examples
listhaskellinsertrecord

Updating a single value in a record, within a collection of records Haskell


So I have a custom data type Person

   data Person = Person{ fname :: String
                        , lname :: String
                        , age :: Int
                        , siblings :: [String]
                        }

I have a list of this type, foo = [Person].

I'm trying to update a particular Person. My process is to match their fname (Assuming each name is unique) and then to update their siblings values.

addSiblingToPerson :: String -> String -> [Person] -> [Person]
addSiblingToPerson siblingParam fnameParam fooParam = 

I'm really struggling to think 'functionally', if I were to do this in an imperative language I could go through each item in [Person] checking to see if name == fname then this.siblings push newSibling (Or something along those lines)

I know how to update a record in haskell but I want to return the list of Person after updating a single person in the collection of Person.

I just can't wrap my head around how to 'think Haskell'

Thank you :(


Solution

  • You shouldn't think about "updating" something, even though we use that terminology, unless you have a mutable reference and are working in the IO monad. In this situation the thought process should be "how do I compute a new list that is exactly like the previous one except...".

    You could either update a single entry or map a modification function across the entire list. Lets look at the manual, single entry, solution first:

    addSiblingToPerson :: String -> String -> [Person] -> [Person]
    addSiblingToPerson siblingParam fnameParam allPeople = 
        case allPeople of
           []     -> []
           (p:ps) | fname p == fnameParam ->
                         p { siblings = siblingParam : siblings p } : ps
                  | otherwise ->
                         p : addSiblingToPerson siblingParam fnameParam ps
    

    That is, we traverse the list, keeping any non-matching people and updating the first person with a matching fname, being sure to include the rest of the list.

    The map solution is functionally different - it will update all people who share the given fname and it will traverse the whole list.

    addSiblingToPerson :: String -> String -> [Person] -> [Person]
    addSiblingToPerson siblingParam fnameParam allPeople =
          let update p | fname p == fnameParam = p { siblings = siblingParam : siblings p }
                       | otherwise = p
          in map update allPeople