I'm new to Haskell and using do notation to validate a user's choice with an Either.
userChoice :: String -> Either String String
userChoice option
| option == "1" = Right "You proceed with option 1."
| option == "2" = Right "You proceed with option 2"
| option == "3" = Right "You proceed with option 3"
| option == "4" = Right "You proceed with option 4"
| otherwise = Left $ "\n\nInvalid Option...\nWhat would you like to do?\n" ++ displayOptions(0)
displayOptions :: Int -> String
displayOptions option
| option == 0 = "1 - Option 1\n2 - Option 2\n3 - Option 3\n4 - Option 4"
| otherwise = "invalid"
main = do
putStrLn "Welcome."
let start startMessage = do
putStrLn startMessage
let ask message = do
putStrLn message
choice <- getLine
either ask ask $ userChoice(choice)
ask $ "What would you like to do?\n" ++ displayOptions(0)
start "Choose a number between 1-4."
This works fine, but after the user chooses a correct option, I'd like to proceed to the next part of the program. I could use return but I lose which option the user chose.
As an example, I can put return here when the user chooses a right, but then it will not say the String "You proceed with option...".
main = do
putStrLn "Welcome."
let start startMessage = do
putStrLn startMessage
let ask message = do
putStrLn message
choice <- getLine
either ask return $ userChoice(choice)
ask $ "What would you like to do?\n" ++ displayOptions(0)
start "Choose a number between 1-4."
-- putStrLn userChoice2(choice)
let continue continueMessage = do
putStrLn continueMessage
let ask message = do
putStrLn message
choice <- getLine
either continue continue $ userChoice(choice)
ask $ "What would you like to do?"
continue "We are now here..."
If I try to put continue as the right option then it throws a scope error. Similarly, if I use return and try to make a primitive clone function for userChoice I no longer have access to the scope choice is in.
userChoice2 :: String -> String
userChoice2 option
| option == "1" = "You proceed with option 1."
| option == "2" = "You proceed with option 2"
| option == "3" = "You proceed with option 3"
| option == "4" = "You proceed with option 4"
| otherwise = "\n\nInvalid Option...\nWhat would you like to do?\n" ++ displayOptions(0)
Is there a way to chain these together elegantly?
It sounds like you just need to bind the value returned in the monad from start
, like this:
main = do
putStrLn "Welcome."
let start startMessage = do
putStrLn startMessage
let ask message = do
putStrLn message
choice <- getLine
either ask return $ userChoice(choice)
ask $ "What would you like to do?\n" ++ displayOptions(0)
-- THE NEW BIT I ADDED:
opt <- start "Choose a number between 1-4."
putStrLn opt -- THIS PRINTS "You proceed with ..."
-- SNIP
You can read about "desugaring do
notation" to learn how this translates to lambdas and the methods of the Monad
class (>>=
and return
).
A couple other suggestions:
You can use (and should prefer) pattern matching instead of guards and ==
(a method of the Eq
class, which not all types implement). E.g.
userChoice2 option = case option of
"1" -> "You proceed with option 1."
"2" -> "You proceed with option 2"
Remember that in haskell the syntax for calling foo
on a
and b
is foo a b
not foo(a,b)
It can be very helpful to turn on warnings (both when you're learning and on production code bases) e.g. here I load your file in ghci after turning on -Wall
:
Prelude> :set -Wall
Prelude> :l k.hs
[1 of 1] Compiling Main ( k.hs, interpreted )
k.hs:15:1: warning: [-Wmissing-signatures]
Top-level binding with no type signature: main :: IO b
|
15 | main = do
| ^^^^
k.hs:24:5: warning: [-Wunused-do-bind]
A do-notation statement discarded a result of type ‘String’
Suppress this warning by saying
‘_ <- start "Choose a number between 1-4."’
|
24 | start "Choose a number between 1-4."
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ok, one module loaded.
Notice it warns you that start
returns IO String
but that you've done nothing with the String
value, which indeed was an error. If you really want to discard the value you can use void
to make it explicit and silence the error.