Titania Lo
Titania Lo

Reputation: 65

Why "Empty do" error when my do isn't empty?

I am trying to create a menu which gives output based on user input. However, I get a empty do error even though I have code for it to do underneath it. Am I missing something?

main :: IO()
main = do 
       contents <- readFile "spa.txt"
       let storage = (read contents :: [Spa])
       putStrLn "Please Enter Your Name: "
       name <- getLine
       putStrLn ""
       putStrLn ("Welcome " ++ name)
       menu storage
       putStrLn ""

       where menu resDB = do
                            putStrLn "\nPlease select an option:"
                            putStrLn "1: Add a new spa to the database "
                            putStrLn "2: Exit"                      
                            putStr "\nSelected option: "
                            putStrLn ""
                            option <- getLine    
                            output :: Int -> IO ()
                            output = do 
                                case option of
                                    1 -> putStrLn "Enter Spa ID: "

Upvotes: 0

Views: 151

Answers (3)

SergeyKuz1001
SergeyKuz1001

Reputation: 875

Try this:

main :: IO()
main = do 
       contents <- readFile "spa.txt"
       let storage = (read contents :: [Spa])
       putStrLn "Please Enter Your Name: "
       name <- getLine
       putStrLn ""
       putStrLn ("Welcome " ++ name)
       menu storage
       putStrLn "" where
            menu resDB = do
                putStrLn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" 
                putStrLn "\nPlease select an option:"
                putStrLn "1: Add a new spa to the database "
                putStrLn "2: Show all spas in the database"
                putStrLn "3: Give all spas operating in a certain area"
                putStrLn "4: Give all spas that have a performance of 8 or higher "
                putStrLn "5: Give the average performance for the spas in a certain area "
                putStrLn "6: Give the names of the spas a given supervisor has rated the service level, along with that rating result for each spa."
                putStrLn "7: Give the names of the spas a given supervisor has yet to rate the service level, along with that spa performance."
                putStrLn "8: Allow a given chef rating to be entered (or updated) for a restaurant he has rated (note that only the latest result from the supervsior should remain recorded)"
                putStrLn "9: Exit"                      
                putStr "\nSelected option: "
                putStrLn ""
                option <- getLine    
                putStrLn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

            output :: Int -> IO ()
            output option = do 
                case option of
                    1 -> putStrLn "Enter Spa ID: "

Upvotes: 1

leftaroundabout
leftaroundabout

Reputation: 120711

This is indeed an indentation problem. Let me just give a version that parses correctly and is eye-friendly:

main :: IO ()
main = do 
  contents <- readFile "spa.txt"
  let storage = read contents :: [Spa]
  -- ...
  menu storage
 where menu resDB = do
         putStrLn "~~~" 
         putStrLn "\nPlease select an option:"
         putStrLn "1: Add a new spa to the database "
         -- ...
         option <- getLine    
         putStrLn "~~~"

         output option

       output :: Int -> IO ()
       output option = case option of
         1 -> putStrLn "Enter Spa ID: "

Note that output is indented only to the level of the where block, not the do block. Generally, do blocks are for writing statements (monadic actions), not for giving declarations like you tried here. You can always embed declarations in a do block, but you need to put them in a let block: this also works, and allows omitting option as an explicit argument to output because they're now inside the same local scope:

 where menu resDB = do
         putStrLn "~~~" 
         option <- getLine    

         let output :: IO ()
             output = case option of
               1 -> putStrLn "Enter Spa ID: "

         output

But, if you're only defining output in order to immediately invoke it exactly once, then you might as well inline the declaration entirely:

 where menu resDB = do
         putStrLn "~~~" 
         option <- getLine    
         case option of
           1 -> putStrLn "Enter Spa ID: "

Depending on the amount of code, a named declaration does make sense though.

You can reduce the needed indentation even more: this style avoids the seven-space indented where block. I personally don't like it as much though.

main :: IO ()
main = do 
  contents <- readFile "spa.txt"
  let storage = read contents :: [Spa]
  -- ...
  menu storage
 where
  menu resDB = do
    putStrLn "~~~" 
    -- ...

And both menu and output could also be declared at the top-level (i.e. with no indentation at all), provided that you do use explicit arguments to pass around the data. Furthermore, you can use just different clauses for that case distinction in output:

main :: IO ()
main = do 
  contents <- readFile "spa.txt"
  menu $ read storage

menu :: [Spa] -> IO ()
menu resDB = do
  putStrLn "~~~" 
  -- ...
  option <- getLine    
  output option

output :: Int -> IO ()
output 1 = putStrLn "Enter Spa ID: "
output 2 = ...

Upvotes: 4

amalloy
amalloy

Reputation: 91897

The statements in a do block must be indented further than the start of the line containing the do. But you have other problems too, like using a let, which does not make sense here.

Upvotes: 0

Related Questions