1

I'm learning Haskell through learnyouahaskell.com and wanted to test some of the concepts before finishing the input/output module. I haven't been able to google or hoogle my way out of this question, though, even though it seems quite simple.

When I try to run the following code

getName = do
  name <- getLine
  return name

the output of getName becomes an element of type IO String instead of String, even though name is definitely a String

By reading the documentation and other StackVverflow's questions I couldn't figure out why is this happening when I declare getName as a function (when I use the bind <- operation directly on main there's no problem whatsoever).

6
  • Because return has type Monad m => a -> m a. You can not "unwrap" a value out of an IO. You could see an IO as a "recipe" to make a String, that is not the same as a string. Commented Sep 8, 2019 at 20:29
  • 5
    If you're coming from a background in imperative languages, be aware that return in Haskell is very different to return in those languages. return is a function in Haskell, in this case one with type String -> IO String. Commented Sep 8, 2019 at 20:31
  • I see. Thanks a lot, guys. I completely overlooked how return works in this case. Commented Sep 8, 2019 at 20:37
  • 2
    If you just read further into LYAH you'll find all this explained (in the chapters on Monads). IO in Haskell, and do notation, always seems a bit "magical" when first encountered as a "this is how you do IO in Haskell", and these questions are perfectly natural. As I said, they get answered later on. Commented Sep 8, 2019 at 20:54
  • 2
    It's also worth noting that do {x<-foo; return x} is, by the monad laws, exactly the same as foo on its own. So all your code really does is define a new name getName = getLine. Commented Sep 9, 2019 at 8:38

1 Answer 1

4

The return function is not conceptually the same as what return does in languages like C++, Java and Python. return :: Monad m => a -> m a takes an a (here a String), and produces an m a (here IO a).

The do notation is syntacticual sugar. If we desugar the statement, you wrote:

getName = getLine >>= (\name -> return name)

or cleaner:

getName = getLine >>= return

The bind function (>>=) :: Monad m => m a -> (a -> m b) -> m b thus has as first operand an m a, and as second a function a -> m b, and produces an m b. Since getLine :: IO String is an IO String, that thus means that m is the same as IO, and a is the same as String. The return :: Monad m => a -> m a, makes it clear that here b is the same as a.

Then what is IO here. A metaphor that is frequently used is the one of a recipe. In this metaphor an IO a is a set of instructions that when you follow these, you will get an a. But that does not mean that that recipe is an a.

(>>=) here basically says that, on the left hand I have a recipe to make a, on the right hand I have a function that converts that a into a recipe to make b, so we can construct a recipe to make b with these two.

People often ask how to unwrap an a out of an IO a, but conceptually it makes not much sense. You can not "unwrap" the cake out of a recipe to make a cake. You can follow the instructions to make a cake. Following instructions is something the main will eventually do. We thus can construct a (long) recipe the main will do. But we can not unwrap the values.

Strictly speaking there is a function unsafePerformIO :: IO a -> a that can do that. But it is strongly adviced not to use that. Functions in Haskell are supposed to be pure that means that for the same input, we always retrieve the same output. getLine itself is a pure, since it always produces the same recipe (IO String).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.