I am writing a script that has a very logically complicated loop:
main = do
inFH <- openFile "..." ReadMode
outFH <- openFile "..." WriteMode
forM myList $ \ item ->
...
if ...
then ...
else do
...
case ... of
Nothing -> ...
Just x -> do
...
...
The code soon flies to the right, so I was thinking breaking it into pieces, using for example where clauses. The problem is, many of these ... contain reading/writing statements to the two handles inFH and outFH, and using a where statement will render those two names out of context. I would have to send in these two variables everytime I use a where statement.
Is there a better way of dealing with this?
In many cases, these deeply-nested indentations are the result of deeply-nested error checking. If that's so for you, you should look into MaybeT and its big brother ExceptT. These offer a clean way to separate the "what do we do when something went wrong" code from the "what do we do assuming everything goes right" code. In your example, I might write:
data CustomError = IfCheckFailed | MaybeCheckFailed
main = handleErrors <=< runExceptT $ do
inFH <- liftIO $ openFile ...
outFH <- liftIO $ openFile ...
forM myList $ \item -> do
when (...) (throwError IfCheckFailed)
...
x <- liftMaybe MaybeCheckFailed ...
...
liftMaybe :: MonadError e m => e -> Maybe a -> m a
liftMaybe err = maybe (throwError err) return
handleErrors :: Either CustomError a -> IO a
handleErrors (Left err) = case err of
IfCheckFailed -> ...
MaybeCheckFailed -> ...
handleErrors (Right success) = return success
Notice that we still increase indentation at the forM loop; but the other checks are done "in-line" in main, and are handled all at the same indentation level in handleErrors.
While there likely are nicer ways to solve your concrete problem (see e.g. Daniel Wagner's answer), you can always use let to introduce a new name within an arbitrary scope. Here is an admittedly nonsensical demo:
main = do
inFH <- return "inf"
outFH <- return "ouf"
let subAction = do
if length inFH > 2
then print "foo"
else subSubAction
subSubAction = case outFH of
[] -> print "bar"
_ -> print "baz"
forM [1..10] $ \ item -> do
print item
subAction
You should do the same thing you would have done with any other programming language. Functions should be easy to understand. This typically means that if it is long there isn't a lot of control flow, otherwise split it up in to separate functions.
So main might look like:
main = do
inFH <- openFile ...
outFH <- openFile ....
mapM prcoessItem myList
Related
I'm trying to learn how to work with IO in Haskell by writing a function that, if there is a flag, will take a list of points from a file, and if there is no flag, it asks the user to enter them.
dispatch :: [String] -> IO ()
dispatch argList = do
if "file" `elem` argList
then do
let (path : otherArgs) = argList
points <- getPointsFile path
else
print "Enter a point in the format: x;y"
input <- getLine
if (input == "exit")
then do
print "The user inputted list:"
print $ reverse xs
else (inputStrings (input:xs))
if "help" `elem` argList
then help
else return ()
dispatch [] = return ()
dispatch _ = error "Error: invalid args"
getPointsFile :: String -> IO ([(Double, Double)])
getPointsFile path = do
handle <- openFile path ReadMode
contents <- hGetContents handle
let points_str = lines contents
let points = foldl (\l d -> l ++ [tuplify2 $ splitOn ";" d]) [] points_str
hClose handle
return points
I get this: do-notation in pattern Possibly caused by a missing 'do'?` after `if "file" `elem` argList.
I'm also worried about the binding issue, assuming that I have another flag that says which method will be used to process the points. Obviously it waits for points, but I don't know how to make points visible not only in if then else, constructs. In imperative languages I would write something like:
init points
if ... { points = a}
else points = b
some actions with points
How I can do something similar in Haskell?
Here's a fairly minimal example that I've done half a dozen times when I'm writing something quick and dirty, don't have a complicated argument structure, and so can't be bothered to do a proper job of setting up one of the usual command-line parsing libraries. It doesn't explain what went wrong with your approach -- there's an existing good answer there -- it's just an attempt to show what this kind of thing looks like when done idiomatically.
import System.Environment
import System.Exit
import System.IO
main :: IO ()
main = do
args <- getArgs
pts <- case args of
["--help"] -> usage stdout ExitSuccess
["--file", f] -> getPointsFile f
[] -> getPointsNoFile
_ -> usage stderr (ExitFailure 1)
print (frobnicate pts)
usage :: Handle -> ExitCode -> IO a
usage h c = do
nm <- getProgName
hPutStrLn h $ "Usage: " ++ nm ++ " [--file FILE]"
hPutStrLn h $ "Frobnicate the points in FILE, or from stdin if no file is supplied."
exitWith c
getPointsFile :: FilePath -> IO [(Double, Double)]
getPointsFile = {- ... -}
getPointsNoFile :: IO [(Double, Double)]
getPointsNoFile = {- ... -}
frobnicate :: [(Double, Double)] -> Double
frobnicate = {- ... -}
if in Haskell doesn't inherently have anything to do with control flow, it just switches between expressions. Which, in Haskell, happen to include do blocks of statements (if we want to call them that), but you still always need to make that explicit, i.e. you need to say both then do and else do if there are multiple statements in each branch.
Also, all the statements in a do block need to be indented to the same level. So in your case
if "file" `elem` argList
...
if "help" `elem` argList
Or alternatively, if the help check should only happen in the else branch, it needs to be indented to the statements in that do block.
Independent of all that, I would recommend to avoid parsing anything in an IO context. It is usually much less hassle and easier testable to first parse the strings into a pure data structure, which can then easily be processed by the part of the code that does IO. There are libraries like cmdargs and optparse-applicative that help with the parsing part.
In the below code, I am using >> to concatenate IO actions together. But AFAIU, m1>>m2 gets de-sugared to m1>>=(\_.m2) and thus it is executing the first IO action right when it is binding. I want all printing to happen in the main, i.e. print statements should not get interleaved with the input statements ("Enter Code"). Since do doesn't allow me to return any other monad than IO like [IO ()]. How can I have the desired printing effect?
f :: [Int] -> IO ()
f inventory = do
putStrLn "Enter Code\n"
x <- getLine
let idx = nameToIndex x
putStrLn "Quantity\n"
y <- getLine
putStrLn "More?\n"
c <- getChar
let q = (read y :: Int)
let curM = if inventory !! idx >= q then (putStrLn "sdaf\n") else (putStrLn "Overflow!\n")
if c == 'Y' then curM>>(f (update inventory idx)) else curM
main = f [1, 2]
I'm not 100% sure I understand the problem, but I think it goes like this: you'd like to do some interactions with the user, storing up information about the interaction, then display all the information at once at the end of the entire interaction.
Here's a very simplified version of your code, that skips all the business logic and just keeps asking the user if they want to continue.
prompt = do
putStrLn "Continue?"
s <- getLine
case s of
"y" -> putStrLn "Okay, let's continue." >> prompt
_ -> return ()
main = prompt
I think the effect you're asking for is to delay the display of "Okay, let's continue." until the user has stopped hitting "y". That's no problem. There's lots of ways you can do this. The most flexible is to have prompt return the action it wants to be executed after it completes:
prompt = do
putStrLn "Continue?"
s <- getLine
case s of
"y" -> do
act <- prompt
return (putStrLn "Okay, let's continue." >> act)
_ -> return (return ())
main = do
act <- prompt
act
(There are combinators that can make this code more compact, as well.) But I don't like this design; it makes it difficult to introspect on the result of prompt. A more specialized but also more maintainable approach is to return some data describing the interaction, which the caller can then turn into an IO action summarizing things. In this case, a list of strings seems like a suitable description.
prompt = do
putStrLn "Continue?"
s <- getLine
case s of
"y" -> do
results <- prompt
return ("Okay, let's continue." : results)
_ -> return []
main = do
results <- prompt
mapM_ putStrLn results
Hopefully this explanation is clear enough that you can combine this idea with your more complicated business logic.
Often times I found myself in need of skipping the rest of the iteration (like continue in C) in Haskell:
forM_ [1..100] $ \ i ->
a <- doSomeIO
when (not $ isValid1 a) <skip_rest_of_the_iteration>
b <- doSomeOtherIO a
when (not $ isValid2 b) <skip_rest_of_the_iteration>
...
However, I failed to find an easy way to do so. The only way I am aware of is probably the Trans.Maybe, but is it necessary to use a monad transform to achieve something so trivial?
Remember that loops like this in Haskell are not magic...they're just normal first-class things that you can write yourself.
For what it's worth, I don't think it's too useful to think of MaybeT as a Monad transformer. To me, MaybeT is just a newtype wrapper to give an alternative implementation of (>>=)...just like how you use Product, Sum, First, And, etc. to give alternative implementations of mappend and mempty.
Right now, (>>=) for you is IO a -> (a -> IO b) -> IO b. But it'd be more useful to have (>>=) here be IO (Maybe a) -> (a -> IO (Maybe b) -> IO (Maybe b). As soon as you get to the first action that returns a Nothing, it's really impossible to "bind" any further. That's exactly what MaybeT gives you. You also get a "custom instance" of guard, guard :: Bool -> IO (Maybe a), instead of guard :: IO a.
forM_ [1..100] $ \i -> runMaybeT $ do
a <- lift doSomeIO
guard (isValid1 a)
b <- lift $ doSomeOtherIO a
guard (isValid2 b)
...
and that's it :)
MaybeT is not magic either, and you can achieve basically the same effect by using nested whens. It's not necessary, it just makes things a lot simpler and cleaner :)
Here's how you would do it using bare-bones recursion:
loop [] = return () -- done with the loop
loop (x:xs) =
do a <- doSomeIO
if ...a...
then return () -- exit the loop
else do -- continuing with the loop
b <- doSomeMoreIO
if ...b...
then return () -- exit the loop
else do -- continuing with the loop
...
loop xs -- perform the next iteration
and then invoke it with:
loop [1..100]
You can tidy this up a bit with the when function from Control.Monad:
loop [] = return ()
loop (x:xs) =
do a <- doSomeIO
when (not ...a...) $ do
b <- doSomeMoreIO
when (not ...b...) $ do
...
loop xs
There is also unless in Control.Monad which you might prefer to use.
Using #Ørjan Johansen 's helpful advice, here is an simple example:
import Control.Monad
loop [] = return ()
loop (x:xs) = do
putStrLn $ "x = " ++ show x
a <- getLine
when (a /= "stop") $ do
b <- getLine
when (b /= "stop") $ do
print $ "iteration: " ++ show x ++ ": a = " ++ a ++ " b = " ++ b
loop xs
main = loop [1..3]
If you want to loop over a list or other container to perform actions and/or produce a summary value, and you're finding the usual convenience tools like for_ and foldM aren't good enough for the job, you might want to consider foldr, which is plenty strong enough for the job. When you're not really looping over a container, you can use plain old recursion or pull in something like https://hackage.haskell.org/package/loops or (for a very different flavor) https://hackage.haskell.org/package/machines or perhaps https://hackage.haskell.org/package/pipes.
I currently have this code which will perform the main' function on each of the filenames in the list files.
Ideally I have been trying to combine main and main' but I haven't made much progress. Is there a better way to simplify this or will I need to keep them separate?
{- Start here -}
main :: IO [()]
main = do
files <- getArgs
mapM main' files
{- Main's helper function -}
main' :: FilePath -> IO ()
main' file = do
contents <- readFile file
case (runParser parser 0 file $ lexer contents) of Left err -> print err
Right xs -> putStr xs
Thanks!
Edit: As most of you are suggesting; I was trying a lambda abstraction for this but wasn't getting it right. - Should've specified this above. With the examples I see this better.
The Control.Monad library defines the function forM which is mapM is reverse arguments. That makes it easier to use in your situation, i.e.
main :: IO ()
main = do
files <- getArgs
forM_ files $ \file -> do
contents <- readFile file
case (runParser f 0 file $ lexer contents) of
Left err -> print err
Right xs -> putStr xs
The version with the underscore at the end of the name is used when you are not interested in the resulting list (like in this case), so main can simply have the type IO (). (mapM has a similar variant called mapM_).
You can use forM, which equals flip mapM, i.e. mapM with its arguments flipped, like this:
forM_ files $ \file -> do
contents <- readFile file
...
Also notice that I used forM_ instead of forM. This is more efficient when you are not interested in the result of the computation.
I have a lot of code of the style:
do
x <- getSomething
case x of
this -> ...
that -> ...
other -> ...
Any way of me combining the "x <- ..." and "case x of" lines to eliminate the need for a variable?
You could use the bind operator >>= to pipe the x.
import System.Environment (getArgs)
main :: IO ()
main = getArgs >>= process
where process ["xxx"] = putStrLn "You entered xxx"
process ["yyy"] = putStrLn "You entered yyy"
process _ = putStrLn "Error"
I do this as
foo "this" = return 2
foo "that" = return 3
main = foo =<< getSomething
The nice thing about this approach is that if foo is pure then this becomes
main = foo <$> getSomething
So the code retains the same shape for somewhat varying circumstances.
If you want something extremely close to:
getSomething >>= caseOf
this -> expr1
that -> expr2
other -> expr3
Then I think you're just out of luck - there is no such syntax in Haskell. But know you are not alone. Mark Jones defined the Habit language to include a sort of monadic case with the syntax:
case<- getSomething of
Nothing -> expr1
Just x -> expr2 x
He refers to this as a "Case-From" statement in the language definition.