Question

I have created a film database and functions related to the database.

I am now creating a demo function where it gives results of particular functions carried out when the number is pressed in ghci. For example, when demo 2 is typed, it shows all the films in the database.

I have managed to create most the demo functions however, i am having problem with 3 of them and keep being displayed with errors. I have commented out the ones which do not work and need help understanding what the problem is.

I have included all the functions i have created with the demo function below.

import Data.List 
import Text.Printf
import Data.Ord
import Data.Char

type Rating = (String, Int)
type Title = String
type Director = String
type Year = Int
type Film = (Title, Director, Year,[Rating])

testDatabase :: [Film]
testDatabase = [("Blade Runner","Ridley Scott",1982,[("Amy",6), ("Bill",9), ("Ian",7), ("Kevin",9), ("Emma",4), ("Sam",5), ("Megan",4)]),
                ("The Fly","David Cronenberg",1986,[("Megan",4), ("Fred",7), ("Chris",5), ("Ian",0), ("Amy",5)]),
                ("Psycho","Alfred Hitchcock",1960,[("Bill",4), ("Jo",4), ("Garry",8), ("Kevin",7), ("Olga",8), ("Liz",10), ("Ian",9)]),
                ("Body Of Lies","Ridley Scott",2008,[("Sam",3), ("Neal",7), ("Kevin",2), ("Chris",5), ("Olga",6)]),
                ("Avatar","James Cameron",2009,[("Olga",2), ("Wally",8), ("Megan",9), ("Tim",5), ("Zoe",8), ("Emma",3)]),
                ("Titanic","James Cameron",1997,[("Zoe",7), ("Amy",2), ("Emma",5), ("Heidi",3), ("Jo",8), ("Megan",5), ("Olga",7), ("Tim",10)]),
                ("The Departed","Martin Scorsese",2006,[("Heidi",2), ("Jo",8), ("Megan",5), ("Tim",2), ("Fred",5)]),
                ("Aliens","Ridley Scott",1986,[("Fred",8), ("Dave",6), ("Amy",10), ("Bill",7), ("Wally",2), ("Zoe",5)]),
                ("Prometheus","Ridley Scott",2012,[("Garry",3), ("Chris",4), ("Emma",5), ("Bill",1), ("Dave",3)]),
                ("E.T. The Extra-Terrestrial","Steven Spielberg",1982,[("Ian",7), ("Amy",2), ("Emma",7), ("Sam",8), ("Wally",5), ("Zoe",6)]),
                ("The Birds","Alfred Hitchcock",1963,[("Garry",7), ("Kevin",9), ("Olga",4), ("Tim",7), ("Wally",3)]),
                ("Goodfellas","Martin Scorsese",1990,[("Emma",7), ("Sam",9), ("Wally",5), ("Dave",3)]),
                ("The Shawshank Redemption","Frank Darabont",1994,[("Jo",8), ("Sam",10), ("Zoe",3), ("Dave",7), ("Emma",3), ("Garry",10), ("Kevin",7)]),
                ("Gladiator","Ridley Scott",2000,[("Garry",7), ("Ian",4), ("Neal",6), ("Wally",3), ("Emma",4)]),
                ("The Green Mile","Frank Darabont",1999,[("Sam",3), ("Zoe",4), ("Dave",8), ("Wally",5), ("Jo",5)]),
                ("True Lies","James Cameron",1994,[("Dave",3), ("Kevin",4), ("Jo",0)]),
                ("Minority Report","Steven Spielberg",2002,[("Dave",5), ("Garry",6), ("Megan",2), ("Sam",7), ("Wally",8)]),
                ("The Wolf of Wall Street","Martin Scorsese",2013,[("Dave",6), ("Garry",6), ("Megan",0), ("Sam",4)]),
                ("War Horse","Steven Spielberg",2011,[("Dave",6), ("Garry",6), ("Megan",3), ("Sam",7), ("Wally",8), ("Zoe",8)]),
                ("Lincoln","Steven Spielberg",2012,[("Ian",3), ("Sam",7), ("Wally",3), ("Zoe",4), ("Liz",7), ("Megan",4)]),
                ("Vertigo","Alfred Hitchcock",1958,[("Bill",7), ("Emma",5), ("Zoe",9), ("Olga",6), ("Tim",10)]),
                ("The Terminal","Steven Spielberg",2004,[("Olga",3), ("Heidi",8), ("Bill",2), ("Sam",6), ("Garry",8)]),
                ("Jaws","Steven Spielberg",1975,[("Fred",3), ("Garry",0), ("Jo",3), ("Neal",9), ("Emma",7)]),
                ("Hugo","Martin Scorsese",2011,[("Sam",4), ("Wally",3), ("Zoe",4), ("Liz",7)])] 

------------------------------------------------------------
-----------------FUNCTIONAL CODE----------------------------
------------------------------------------------------------                

--when adding need to be addFilm string string int and the list name called testDatabase 
addFilm :: String -> String -> Int -> [Film] -> [Film]
addFilm title director year database = (title, director, year, [])  : database  

--Some functions needed later on:
averageFilmRating :: [(String,Int)] -> Float
averageFilmRating ratings
     = (fromIntegral(sum $ map snd ratings)) / (fromIntegral(length ratings))

--Formats the films for decimal, gives average rating of films instead of all users ratings.
formatFilmOutput :: Film -> String
formatFilmOutput (title, director, year, rating)
     = printf "%s by %s. Year: %d, Average Rating: %.1f" (title) (director) (year) (averageFilmRating rating)    

--Shows all films in the database    
displayAllFilm :: [String]
displayAllFilm = map formatFilmOutput testDatabase


--Shows films by director name
displayByDirector :: String -> [Film]
displayByDirector name
     =  filter(\(_,director,_,_) -> director == name) testDatabase

--Gives the average of directors films  
directorAverage :: String -> Float
directorAverage dir
     = averageFilmRating [rating | (title, director, year, ratings) <- displayByDirector dir, rating <- ratings]     

--These two functions give the films rated of average 6 or over  
filmsRated :: Int -> [Film]
filmsRated rating
         = filter(\(_,_,_,a) -> averageFilmRating a >= fromIntegral rating) testDatabase 

filmsaveragesix = filmsRated 6  

--Shows what films the user has rated.
userRatedFilms :: String -> [Film]
userRatedFilms username
     = filter ((username `elem`) . (\(_,_,_,xs) -> map fst xs)) testDatabase

-- Allows user to rate or re-rate film.
databaseNoFilm:: [Film] -> Title -> [Film]
databaseNoFilm database t = [(title, director, year, ratings) | (title, director, year, ratings) <- database, title /= t]
rateFilm :: [Film] -> Title -> Rating -> [Film]
rateFilm database findtitle (u, r) = databaseNoFilm database findtitle ++ [(title,director,year,(u, r):[(username,rtg) | (username,rtg) <- ratings, username /= u]) | (title, director, year, ratings) <- database, title == findtitle] 

--Displays films by year in descending order of rating
filmsByYear :: Int -> [Film]
filmsByYear year = sortFilms $ filter(\(_,_,yr,_) -> yr >= year) testDatabase

sortFilms :: [Film] -> [Film]
sortFilms = sortBy $ flip $ comparing averageFilmRating'
  where
    averageFilmRating' (_,_,_,rs) = averageFilmRating rs

------------------------------------------------------------
-----------------DEMO FUNCTION------------------------------
------------------------------------------------------------
demo :: Int -> IO ()
demo choice = do
    case choice of
        -- 1 -> do
            -- putStrLn addFilm "Gravity" "Alfonso Cuaron" 2013 testDatabase
        2 -> do
            putStrLn (unlines displayAllFilm)
        3 -> do
            putStrLn (unlines (map formatFilmOutput(displayByDirector "James Cameron")))
        4 -> do
            putStrLn (unlines (map formatFilmOutput(filmsaveragesix)))
            -- Get the director average of James Cameron
        -- 5 -> do
            -- putStrLn (directorAverage "James Cameron")
        6 -> do
            putStrLn (unlines (map formatFilmOutput(userRatedFilms "Zoe")))
        -- all films after Zoe rates "Jaws" 8   
        -- 7 -> do
            -- putStrLn rateFilm testDatabase "Jaws" ("Zoe", 8)
        -- 77 all films after Zoe rates "Vertigo" 3
        8 -> do
            putStrLn (unlines (map formatFilmOutput(filmsByYear 2009)))

The problem here relates to the demo functions which have been commented out. When uncommented and run the error for demo 1 relates to:

Couldn't match type `String -> String -> Int -> [Film] -> [Film]'
                  with `[Char]'
    Expected type: String
      Actual type: String -> String -> Int -> [Film] -> [Film]
    In the first argument of `putStrLn', namely `addFilm'
    In a stmt of a 'do' block:
      putStrLn addFilm "Gravity" "Alfonso Cuaron" 2013 testDatabase
    In the expression:
      do { putStrLn
             addFilm "Gravity" "Alfonso Cuaron" 2013 testDatabase }

It is a similar error for the rest of the commented demo functions

Was it helpful?

Solution

The problem you were having has to do with the line

putStrLn addFilm "Gravity" "Alfonso Cuaron" 2013 testDatabase

To the compiler, this looks like you're trying to apply 5 arguments to putStrLn, which is a function that only takes 1 argument, hence the compiler error. If you were to do

putStrLn (addFilm "Gravity" "Alfonso Cuaron" 2013 testDatabase)

Or equivalently (and prettier)

putStrLn $ addFilm "Gravity" "Alfonso Cuaron" 2013 testDatabase

Then you're applying the 4 arguments to addFilm, then applying that result to putStrLn. This still won't compile because addFilm returns a list of Films, not a String which is what putStrLn expects. You can instead use print, which is defined as

print :: Show a => a -> IO ()
print x = putStrLn $ show x

Or you could use your formatFilmOutput function:

putStrLn $ unlines $ map formatFilmOutput $ addFilm "Gravity" ...

and unlines converts the list of strings into a single string joined by new lines.

In case you're wondering what the $ operator is, it's literally defined as

($) :: (a -> b) -> a -> b
($) = id
infixr 0 $

The important part of the definition is the infixr 0 $ line. This means that it's right associative with a fixity of 0, which is the lowest precedence. Function application has a precedence of 9, meaning it always takes priority over operators, so

add1 x * 2

Is always the same as

(add1 x) * 2

The $ operator just acts as an alternative to parentheses. You can use it as

f $ g $ h $ i $ j $ k $ l $ m x
-- f through m are functions of one argument

Which means apply x to m, then apply that to l, then to k, then to j, and so on, or you could write it as

f (g (h (i (j (k (l (m x)))))))

Which isn't fun to balance parentheses for. This only works for applying the last argument to a function, not any of the middle ones. So

add = (+)

add $ 1 + 2 $ 3 + 4

won't work, that parses as

add (1 + 2 (3 + 4))
add (3 (7))
add (3 7)

Which just doesn't make sense.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top