Thoai Nguyen wrote:
Hi fam,
This is a fairly noob question. Considering the following imaginary functions:
someCalculation :: String -> Maybe String
someCalculation = Just
main :: IO ()
main = getLine >>= someCalculation >>= putStrLn
What I want to do is to run a non-deterministic calculation on an input string, then print the output. In this case, 2 different monads are in use (Maybe & IO). Of course this wouldn't work due to incorrect typing.
What would be the right way to handle something like this in Haskell?
The someCalculation implementation is just a placeholder, but you get the idea...
In Haskell, any non-deterministic calculation is going to end up producing
an IO value, like IO String or IO (Maybe Int) or something like that. More formally, people refer to this as "a value of type 'IO a'," where "a" is
what's called a "type variable." (In more complicated programs this often
ends up being somewhat obscured by a bunch of monad transformers that rest
on top of the IO monad, so the return value of an impure function might be "AppT String" instead of "IO String", but under the covers the IO monad is still involved.)
If your someCalculation function is impure, it needs to have a type more
like
someCalculation :: String -> IO (Maybe String)
if it's possible for the function to fail in some way, or
someCalculation :: String -> IO String
if not. It sounds to me as if your function can indeed fail, so I'll assume that it's going to return an IO (Maybe String). If you haven't seen this
kind of notation before, it means "a String value that may or may not be
there, embedded within the IO monad." I recommend reading this kind of thing from the inside out. Note that the type Maybe (IO String) is something completely different (and I'm not sure I could even describe what it is...
I've never seen that kind of construction before, I don't think.)
A dummy implementation of the someCalculation function with this type
signature looks like
someCalculation :: String -> IO (Maybe String)
someCalculation = return . Just
or, equivalently,
someCalculation :: String -> IO (Maybe String)
someCalculation s = do
return (Just s)
In either version, we first call "Just" on the input to convert it from a String to a Maybe String. Next we call "return" on the Maybe String to
convert it to an IO (Maybe String).
As for how you'd actually use it, I'd recommend (at least for now) using do-notation and pattern matching. Your main function as it exists now could
be rewritten
main = do
input <- getLine
result <- someCalculation input
putStrLn result
(although as you point out, this will be rejected by the compiler because "someCalculation input" has the type Maybe String, not the type IO String
that you need in this context). With the new version of someCalculation,
your main function would become
main = do
input <- getLine
maybeResult <- someCalculation input
case maybeResult of
Just result -> putStrLn result
Nothing -> putStrLn "Oops, it didn't work!"
The case statement is what allows you to deal with the two possibilities for "maybeResult": it's either "Just result" or else it's "Nothing". You can do completely different things in the branches of the case statement, if you
want, but both branches must have the same type, and in this particular situation that type must be IO (). This is also the return type of putStrLn,
so we're good there.
If you're in the kind of situation where, in the case of an error, you want
to just swap in a default value and continue on, you could use the
"fromMaybe" function:
import Data.Maybe (fromMaybe)
main = do
input <- getLine
maybeResult <- someCalculation input
let result = fromMaybe "default value" maybeResult
putStrLn result
Hope this helps,
Benjamin
--
Benjamin Esham
https://esham.io
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)