7

I'm fairly new to Haskell and have been trying to find a way to pass multiple IO-tainted values to a function to deal with a C library. Most people seem to use the <- operator inside a do block, like this:

g x y = x ++ y
interactiveConcat1 = do {x <- getLine;
                         y <- getLine;
                         putStrLn (g x y);
                         return ()}

This makes me feel like I'm doing C, except emacs can't auto-indent. I tried to write this in a more Lispy style:

interactiveConcat2 = getLine >>= (\x ->
                     getLine >>= (\y ->
                     putStrLn (g x y) >>
                     return () ))

That looks like a mess, and has a string of closed parentheses you have to count at the end (except again, emacs can reliably assist with this task in Lisp, but not in Haskell). Yet another way is to say

import Control.Applicative
interactiveConcat3 = return g <*> getLine <*> getLine >>= putStrLn

which looks pretty neat but isn't part of the base language.

Is there any less laborious notation for peeling values out of the IO taint boxes? Perhaps there is a cleaner way using a lift* or fmap? I hope it isn't too subjective to ask what is considered "idiomatic"?

Also, any tips for making emacs cooperate better than (Haskell Ind) mode would be greatly appreciated. Thanks!

John

Edit: I stumbled across https://wiki.haskell.org/Do_notation_considered_harmful and realized that the nested parentheses in the lambda chain I wrote is not necessary. However it seems the community (and ghc implementors) have embraced the Applicative-inspired style using , <*>, etc, which seems to make the code easier to read in spite of the headaches with figuring out operator precedence.

  • 8
    Note: Your "emacs" question doesn't really fit the rest of the question. It should be a question on its own. – Zeta Aug 20 '15 at 11:11
  • 3
    `IO` is not a taint. It's its own distinct type. Thinking of it as a taint will do you a lot of harm when you start seeing type signatures like `IO (IO (), IO ())`. – Carl Aug 20 '15 at 13:14
  • You need to set up Haskell mode for emacs! – dfeuer Aug 20 '15 at 17:24
  • @Carl Already happened! I believe the formal term is type constructor (by that I mean "IO" has to have a type parameter after it, even if that is just the () type). I used the term "taint" for mild tongue-in-cheek humor and because doing an unsafePerformIO or whatever would have made it far easier to port the lisp code I was working with. –  Aug 21 '15 at 11:57
  • @dfeuer I did set up haskell-mode and tried out approximately a dozen indentation schemes I found in melpa and marmalade, none of which worked for simple things, like spreading a function's parameters across multiple lines. I thought I might try a few standalone apps (particularly Leksah) before posting a standalone question. –  Aug 21 '15 at 11:59
  • @flagrant2, terminology varies. `IO` is indeed a type constructor, but it is also correct to call it a type (of kind `* -> *`), and I wouldn't think it odd to hear it called a type function either. – dfeuer Aug 21 '15 at 13:33

2 Answers2

14

Note: This post is written in literate Haskell. You can save it as Main.lhs and try it in your GHCi.


A short remark first: you can get rid of the semicolons and the braces in do. Also, putStrLn has type IO (), so you don't need return ():

interactiveConcat1 = do 
  x <- getLine
  y <- getLine
  putStrLn $ g x y

We're going to work with IO, so importing Control.Applicative or Control.Monad will come in handy:

> module Main where
> import Control.Applicative

> -- Repeat your definition for completeness
> g :: [a] -> [a] -> [a]
> g = (++)

You're looking for something like this:

> interactiveConcat :: IO ()
> interactiveConcat = magic g getLine getLine >>= putStrLn

What type does magic need? It returns a IO String, takes a function that returns an String and takes usual Strings, and takes two IO Strings:

magic :: (String -> String -> String) -> IO String -> IO String -> IO String

We can probably generalize this type to

> magic :: (a -> b -> c) -> IO a -> IO b -> IO c

A quick hoogle search reveals that there are already two functions with almost that type: liftA2 from Control.Applicative and liftM2 from Control.Monad. They're defined for every Applicative and – in case of liftM2Monad. Since IO is an instance of both, you can choose either one:

> magic = liftA2

If you use GHC 7.10 or higher, you can also use <$> and <*> without import and write interactiveConcat as

interactiveConcat = g <$> getLine <*> getLine >>= putStrLn

For completeness, lets add a main so that we can easily check this functionality via runhaskell Main.lhs:

> main :: IO ()
> main = interactiveConcat

A simple check shows that it works as intended:

$ echo "Hello\nWorld" | runhaskell Main.lhs
HelloWorld

References

Zeta
  • 95,453
  • 12
  • 173
  • 214
  • Thanks for the thorough answer. Apparently the adaptation of the notation you mentioned was part of the so-called "functor-applicative-monad proposal" discussed at https://wiki.haskell.org/Functor-Applicative-Monad_Proposal . –  Aug 20 '15 at 13:13
  • Yup, this has been introduced in GHC 7.10. See also [this answer](http://stackoverflow.com/questions/23727768/which-parts-of-real-world-haskell-are-now-obsolete-or-considered-bad-practice/23733494#23733494) if you're working with RWH. – Zeta Aug 20 '15 at 13:25
  • @flagrant2, aside from that page, it's called the `Applicative => Monad` Proposal and abbreviated AMP. – dfeuer Aug 20 '15 at 17:27
4

You can use liftA2 (or liftM2 from Control.Monad):

import Control.Applicative (liftA2)
liftA2 g getLine getLine >>= putStrLn
Lee
  • 133,981
  • 18
  • 209
  • 268