Search code examples
haskellcompiler-errorsfunctortype-mismatchalgebraic-data-types

Fmap over a list containing single elements and lists


I have a data structure like so

data ShoppingList a
  = Empty
  | Item a 
  | Listofitems [ShoppingList a]
  deriving (Show)

I am trying to write the fmap for this

instance Functor ShoppingList where
  fmap f Empty    = Empty
  fmap f (Item i) = Item (f i)
  fmap f (Listofitems [Empty]) = Empty
  fmap f (Listofitems ((Item a):Listofitems [as])) = Listofitems $ (fmap f (Item a)) (fmap f Listofitems [as])

This is what I wrote so far ,but its not compiling ,can you please help me understand what the problem here is , an explanation would be awesome . The two errors that I am getting

src\Ml.hs:19:33: error:
    * Couldn't match expected type `[ShoppingList a]'
                  with actual type `ShoppingList a0'
    * In the pattern: Listofitems [as]
      In the pattern: (Item a) : Listofitems [as]
      In the pattern: Listofitems ((Item a) : Listofitems [as])
    * Relevant bindings include
        a :: a (bound at src\Ml.hs:19:30)
        f :: a -> b (bound at src\Ml.hs:19:8)
        fmap :: (a -> b) -> ShoppingList a -> ShoppingList b
          (bound at src\Ml.hs:16:3)
   |
19 |   fmap f (Listofitems ((Item a):Listofitems [as])) = Listofitems $ (fmap f (Item a)) (fmap f Listofitems [as])
   |                                 ^^^^^^^^^^^^^^^^

src\Ml.hs:19:68: error:
    * Couldn't match expected type `b -> [ShoppingList b]'
                  with actual type `ShoppingList b'
    * The function `fmap' is applied to three arguments,
      but its type `(a -> b) -> ShoppingList a -> ShoppingList b'
      has only two
      In the second argument of `($)', namely
        `(fmap f (Item a)) (fmap f Listofitems [as])'
      In the expression:
        Listofitems $ (fmap f (Item a)) (fmap f Listofitems [as])
    * Relevant bindings include
        f :: a -> b (bound at src\Ml.hs:19:8)
        fmap :: (a -> b) -> ShoppingList a -> ShoppingList b
          (bound at src\Ml.hs:16:3)
   |
19 |   fmap f (Listofitems ((Item a):Listofitems [as])) = Listofitems $ (fmap f (Item a)) (fmap f Listofitems [as])
   |                                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Basically if I have a list = [ Item Apple ,Empty , [Item Banana ,Empty ] ] I want fmap (++ M )list to return [Item AppleM .Empty .[Item BananaM ,Empty]]


Solution

  • Note on the data type

    First of all, your data type is overly complicates. A list of items should indeed be a list, so you can should define data ShoppingList' a = ShoppingList' [a] as you are using lists in yours anyways. There is no need a Shopping List should be nested.

    Solution to your question

    If, however, you need it that way, this is a solution. Note I assumed you did not need a list of ShoppingLists, as your data definition already has a list in it. So you can call

    --fmap :: (a -> b) -> ShoppingList a -> ShoppingList b
    fmap _ Empty = Empty
    fmap f (Item i) = Item (f i)
    fmap f (Listofitems ls) = Listofitems $ map (fmap f) ls
    
    
    >>fmap (++ "M") $ Listofitems [Item "Apple", Empty, Listofitems [Item "Banana", Empty]]
    Listofitems [Item "AppleM",Empty,Listofitems [Item "BananaM",Empty]]
    

    Note:

    • You need quotes around your strings,
    • You need to call it on a Listofitems

    Also remember the String append operations is not efficient with long strings in case you need performance