Why “Empty do” error when my do isn't empty?

后端 未结 3 1527
野的像风
野的像风 2021-01-29 09:38

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?<

3条回答
  •  星月不相逢
    2021-01-29 09:56

    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 = ...
    

提交回复
热议问题