Hello great programmers out there,
I\'m doing my first steps in haskell and have a function that confuses me:
import Data.List.Split
getncheck_guessl
When you're writing a function that uses the IO monad, any value you want to return from the function must also be in the IO monad.
This means that, instead of returning a value with type [Int]
, you have to return something with type IO [Int]
. To do this, you use the return
function, which "wraps up" the value into IO
(it actually works for any monad).
Just change the last line to wrap your value with return
, like this:
getncheck_guesslist = do
line <- getLine
let tmp = splitOneOf ",;" line
return (map read tmp :: [Int])
import Data.List.Split
import Control.Applicative
getncheck_guesslist :: IO [Int]
getncheck_guesslist = map read . splitOneOf ",;" <$> getLine
Every time code bogs down to foo >>= return . bar
, which yours does by desugaring the do-block (and correcting the type error), you are not using the monadic features of your monad, but only its functor part, that is, bluntly said, you are not messing with the IO
part of the type, but with the a
of IO a
:
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
(fmap
would be the non-infix name for <$>
. Both are closely related to map
.)
That code above is pretty much idiomatic for ad-hoc code, but clean code would look like
import Data.Maybe
maybeRead :: Read a => String -> Maybe a
maybeRead = fmap fst . listToMaybe . reads
-- That fmap is using "instance Functor Maybe"
parseGuessList :: String -> [Int]
parseGuessList = catMaybes . map maybeRead . splitOneOf ",;"
getncheck_guesslist = parseGuessList <$> getLine
, or, alternatively, if you don't want to ignore non-int input but error out,
parseGuessList xs = if success then Just . catMaybes $ ys else Nothing
where ys :: String -> [Mabye Int]
ys = map maybeRead . splitOneOf ",;" $ xs
success = all isJust ys
(Beware, though, I have only proven that code correct, not actually tried it.)
If it gets any more complex than that you'd want to use a proper parsing library, I think.
If something is within the IO
monad, you can't take it to the pure outside world for further processing. Instead, you pass your pure functions to work inside the functions within IO
.
In the end, your program reads some input and writes some output, so your main function is going to work with IO
, else you'll just not be able to output anything. Instead of reading IO String
and creating an [Int]
, pass the function that consumes that [Int]
into your main function and use it inside do
.
The "right way" to do it in Haskell is to separate IO from, well, everything else. The direct translation of your code would be this:
getncheck_guesslist :: IO [Int]
getncheck_guesslist = do line <- getLine -- get
return (check_guesslist line) -- check
check_guesslist :: String -> [Int]
check_guesslist line = let tmp = splitOneOf ",;" line
in map read tmp
Notice that getncheck_guesslist
is simply an IO action. The function doesn't have input parameters, even though it does require (IO) input from getLine
.
Also notice that getncheck_guesslist
is a simple modification of the getLine
IO action. Isn't there a combinator that would let me push a function to act on the value inside a monad? Stop. Hoogle time!
I have a function (a -> b)
. I have a value of the input type, but it's stuck in a monad m a
. I want to perform the function inside the monad, so the result will inevitably be stuck in the monad too m b
. Putting that all together, we hoogle (a -> b) -> m a -> m b. Lo and behold, fmap
is just what we were looking for.
get_guesslist = check_guesslist `fmap` getLine
-- or, taking it a step further
get_guesslist = (map read . splitOneOf ",;") `fmap` getLine :: IO [Int]
As a final note, whenever you code a method with the name like somethingAndSomethingElse
, it's usually better coding style to write and invoke something
and somethingElse
as two separate methods. For the final versions, I just renamed it get_guesslist
, since conceptually that's what it does. It gets the guesses as a list of Ints.
As a final final note, I have left off at the point where barsoap started. ;) fmap
is the same as <$>
.