Haskell IO操作实现求助:读取输入生成Card自定义类型列表
Hey there! Let's work through this Haskell IO problem together—IO operations can feel a bit counterintuitive when you're starting out, but we'll get this sorted step by step.
First, let's break down what your code is missing and fix the core issues:
Key Issues in Your Current Code
Looking at your snippet, there are two main problems holding it back:
- The
returnCardListfunction has the wrong type: it's declared as[Card] -> [Card], but it usesdonotation andgetLine(which are IO operations), so it should be[Card] -> IO [Card]. - You're using nested
returncalls incorrectly inreadCards—sincereturnCardListwill already return anIO [Card], you don't need to wrap it in anotherreturn.
Step-by-Step Solution
1. First, Define Your Card Type (and Parser)
Since you didn't include your Card definition, let's assume you have something like this (adjust based on your actual Card structure):
-- Example Card type (customize this to match your needs) data Card = Card String Int deriving (Show, Read) -- Optional: Custom parser if you don't want to rely on the default Read instance -- parseCard :: String -> Maybe Card -- parseCard s = case words s of -- [name, numStr] -> case readMaybe numStr of -- Just num -> Just (Card name num) -- Nothing -> Nothing -- _ -> Nothing
2. Fix the Recursive IO Function
We'll rewrite returnCardList to properly handle IO recursion. It takes an accumulator list of Cards, reads input, and either continues building the list or returns it when the user inputs ".".
3. Put It All Together
Here's the complete working code:
import Text.Read (readMaybe) -- For safer parsing (avoids runtime crashes) -- Define your Card type here data Card = Card String Int deriving (Show, Read) readCards :: IO [Card] readCards = returnCardList [] where returnCardList :: [Card] -> IO [Card] returnCardList acc = do line <- getLine if line == "." then return (reverse acc) -- Reverse to preserve original input order else case readMaybe line of Just card -> returnCardList (card : acc) Nothing -> do putStrLn "Invalid card format—please try again!" returnCardList acc
Explanation
- Recursion with Accumulator:
returnCardListstarts with an empty accumulator. Each valid Card is prepended to the accumulator (prepending is more efficient than appending in Haskell). When the user inputs ".", we reverse the accumulator to get the input order right and return it. - Safe Parsing: Using
readMaybefromText.Readprevents the program from crashing if the user enters a string that can't be parsed into a Card. If parsing fails, we show an error message and ask for input again. - IO Type Correctness:
returnCardListhas the type[Card] -> IO [Card], which makes sense because it performs IO (reading input) and eventually returns a list of Cards.
Alternative: More Functional Style with unfoldr
If you want a concise, functional approach without explicit recursion, you can use unfoldr from Data.List:
import Data.List (unfoldr) import Text.Read (readMaybe) data Card = Card String Int deriving (Show, Read) readCards :: IO [Card] readCards = unfoldr step () where step _ = do line <- getLine if line == "." then return Nothing else case readMaybe line of Just card -> return (Just (card, ())) Nothing -> do putStrLn "Invalid card—try again!" step ()
This works because unfoldr builds a list by repeatedly applying a function that returns Maybe (a, b)—when it returns Nothing, the list stops.
内容的提问来源于stack exchange,提问作者Elif Ak

