Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.haskell > #508 > unrolled thread

Get mapM to be lazy for IO

Started bykfjwheeler@gmail.com
First post2018-12-11 05:19 -0800
Last post2018-12-13 10:46 -0500
Articles 9 — 5 participants

Back to article view | Back to comp.lang.haskell


Contents

  Get mapM to be lazy for IO kfjwheeler@gmail.com - 2018-12-11 05:19 -0800
    Re: Get mapM to be lazy for IO Mark Carroll <mtbc@bcs.org> - 2018-12-11 13:36 +0000
      Re: Get mapM to be lazy for IO Kaylen Wheeler <kfjwheeler@gmail.com> - 2018-12-12 17:52 -0800
        Re: Get mapM to be lazy for IO Paul Rubin <no.email@nospam.invalid> - 2018-12-12 19:29 -0800
      Re: Get mapM to be lazy for IO Kaylen Wheeler <kfjwheeler@gmail.com> - 2018-12-12 18:21 -0800
        Re: Get mapM to be lazy for IO Paul Rubin <no.email@nospam.invalid> - 2018-12-12 19:31 -0800
    Re: Get mapM to be lazy for IO Barry Fishman <barry@ecubist.org> - 2018-12-13 09:52 -0500
      Re: Get mapM to be lazy for IO Barry Fishman <barry@ecubist.org> - 2018-12-13 10:21 -0500
        Re: Get mapM to be lazy for IO Barry Fishman <barry@ecubist.org> - 2018-12-13 10:46 -0500

#508 — Get mapM to be lazy for IO

Fromkfjwheeler@gmail.com
Date2018-12-11 05:19 -0800
SubjectGet mapM to be lazy for IO
Message-ID<24548724-4bdd-402d-8a76-1403b7e87169@googlegroups.com>
I'm kind of a beginner when it comes to Haskell.  Specifically with regards to IO.

I'm tying to write a very simple program that asks the user to input a list of words.  It gets words until the user enters "done".

I'm getting close, but it's still asking for input after I enter "done".  I can't seem to get mapM to be lazy in the way I want.

Here's what I have so far:

untilExit = do
  putStrLn "Hello.  Please enter words until 'done'."
  words <- mapM (\n -> do
                    putStrLn $"Input number " ++ show n ++ ": "
                    l <- getLine
                    return l)
          [1..3] >>= return . takeWhile (/="done")
  putStrLn "Here are the words:"
  mapM putStrLn $ takeWhile (/="done") words
  return words

[toc] | [next] | [standalone]


#509

FromMark Carroll <mtbc@bcs.org>
Date2018-12-11 13:36 +0000
Message-ID<87bm5s4249.fsf@ixod.org>
In reply to#508
On 11 Dec 2018, kfjwheeler wrote:

> I'm kind of a beginner when it comes to Haskell.  Specifically with
> regards to IO.

You're making a promising start!

(snip)
> I'm getting close, but it's still asking for input after I enter
> "done".  I can't seem to get mapM to be lazy in the way I want.

You're doing the three IO actions in sequence then selecting from their
results. Broadly, with IO you don't get to skip stuff after the >>= just
by not consuming the result. So, you need to move the check of the input
value amid the sequence itself, only do further IO actions if necessary.
You could try rolling that yourself (do another >>= only if necessary)
or use the help of something like unfoldM, we'd be happy to help you
with either.

-- Mark

[toc] | [prev] | [next] | [standalone]


#510

FromKaylen Wheeler <kfjwheeler@gmail.com>
Date2018-12-12 17:52 -0800
Message-ID<a9bc88e6-c0cc-4de0-85a0-32e76dc4eb17@googlegroups.com>
In reply to#509
On Tuesday, 11 December 2018 08:36:08 UTC-5, Mark Carroll  wrote:
> On 11 Dec 2018, kfjwheeler wrote:
> 
> > I'm kind of a beginner when it comes to Haskell.  Specifically with
> > regards to IO.
> 
> You're making a promising start!
> 
> (snip)
> > I'm getting close, but it's still asking for input after I enter
> > "done".  I can't seem to get mapM to be lazy in the way I want.
> 
> You're doing the three IO actions in sequence then selecting from their
> results. Broadly, with IO you don't get to skip stuff after the >>= just
> by not consuming the result. So, you need to move the check of the input
> value amid the sequence itself, only do further IO actions if necessary.
> You could try rolling that yourself (do another >>= only if necessary)
> or use the help of something like unfoldM, we'd be happy to help you
> with either.
> 
> -- Mark

I thought I improved it a little, but it's still not lazy.

-- Some random I/O stuff
untilExit = do
  putStrLn "Hello.  Please enter words until 'done'."
  words <- liftM (takeWhile (/= "done")) $
           mapM (\n -> do
                    putStrLn $"Input number " ++ show n ++ ": "
                    l <- getLine
                    return l)
           [1..3]
  putStrLn "Here are the words:"
  mapM putStrLn $ words
  return words

[toc] | [prev] | [next] | [standalone]


#512

FromPaul Rubin <no.email@nospam.invalid>
Date2018-12-12 19:29 -0800
Message-ID<87wooep0i9.fsf@nightsong.com>
In reply to#510
Kaylen Wheeler <kfjwheeler@gmail.com> writes:
>            mapM (\n -> do
>                     putStrLn $"Input number " ++ show n ++ ": "
>                     l <- getLine
>                     return l)
>            [1..3]

That mapM runs the three actions and binds them together.  It's just
like mapM_ except it returns the result list.  So you run all three
actions before looking for "done".

>  mapM putStrLn $ words

In principle you want mapM_ there, though it won't matter.

This seems to work:

main = do
  putStrLn "Hello.  Please enter words until 'done'."
  let go [] xs = return xs
      go (n:ns) xs = do
        putStrLn $"Input number " ++ show n ++ ": "
        l <- getLine
        if l == "done" then return xs
          else go ns (l:xs)

  words <- reverse `fmap` go [1..3] []

  putStrLn "Here are the words:"
  mapM_ putStrLn $ words

[toc] | [prev] | [next] | [standalone]


#511

FromKaylen Wheeler <kfjwheeler@gmail.com>
Date2018-12-12 18:21 -0800
Message-ID<551a276a-536d-4ba1-af9f-9f471dcac4f9@googlegroups.com>
In reply to#509
On Tuesday, 11 December 2018 08:36:08 UTC-5, Mark Carroll  wrote:
> On 11 Dec 2018, kfjwheeler wrote:
> 
> > I'm kind of a beginner when it comes to Haskell.  Specifically with
> > regards to IO.
> 
> You're making a promising start!
> 
> (snip)
> > I'm getting close, but it's still asking for input after I enter
> > "done".  I can't seem to get mapM to be lazy in the way I want.
> 
> You're doing the three IO actions in sequence then selecting from their
> results. Broadly, with IO you don't get to skip stuff after the >>= just
> by not consuming the result. So, you need to move the check of the input
> value amid the sequence itself, only do further IO actions if necessary.
> You could try rolling that yourself (do another >>= only if necessary)
> or use the help of something like unfoldM, we'd be happy to help you
> with either.
> 
> -- Mark

I think I was going down the wrong road here.

I think the following is a much better way of doing while-loop-like constructs:

easyUntilDone = do
  putStrLn "Please enter words until 'done'."
  words <- let loop words = do putStr $ "Input a word: "
                               l <- getLine
                               if l == "done"
                                 then return $ reverse words
                                 else loop (l:words)
           in loop []
  putStrLn $ "Here are the words: " ++ show words

[toc] | [prev] | [next] | [standalone]


#513

FromPaul Rubin <no.email@nospam.invalid>
Date2018-12-12 19:31 -0800
Message-ID<87sgz2p0fv.fsf@nightsong.com>
In reply to#511
Kaylen Wheeler <kfjwheeler@gmail.com> writes:
> I think the following is a much better way of doing while-loop-like
> constructs:

Yes, that's more direct.  You can also look at Control.Monad.Loops for
some useful combinators.

[toc] | [prev] | [next] | [standalone]


#514

FromBarry Fishman <barry@ecubist.org>
Date2018-12-13 09:52 -0500
Message-ID<7nmup95via.fsf@ecube.ecubist.org>
In reply to#508
On 2018-12-11 05:19:18 -08, kfjwheeler@gmail.com wrote:
> I'm kind of a beginner when it comes to Haskell.  Specifically with regards to IO.
>
> I'm tying to write a very simple program that asks the user to input a list of words.  It gets words until the user enters "done".
>
> I'm getting close, but it's still asking for input after I enter "done".  I can't seem to get mapM to be lazy in the way I want.

IO can be lazy, but the order of actions needs to preserved, so that
each putStrLn prompt precedes it's getLine.

The following is how I might write it.  I used the 'hFlush' call so I
could put out the prompt without forcing a newline, and then read the
word on the same line.  You can leave it out.

--8<---------------cut here---------------start------------->8---
#! /usr/bin/env runghc

import System.IO (hFlush, stdout)

main :: IO ()
main = do
  putStrLn "Hello.  Please enter words until 'done'."
  xs <- getWordList 1
  mapM_ putStrLn xs
  where getWordList :: Int -> IO [String]
        getWordList n = do
        putStr $ "Input number " ++ show n ++ ": "
        hFlush stdout
        x <- getLine
        if x == "done"
          then return []
          else do
            xs <- getWordList (n + 1)
            return (x : xs)
--8<---------------cut here---------------end--------------->8---

I would probably shorted the last 'else' to:
          else (x:) `fmap` getWordList (n + 1)

-- 
Barry Fishman

[toc] | [prev] | [next] | [standalone]


#515

FromBarry Fishman <barry@ecubist.org>
Date2018-12-13 10:21 -0500
Message-ID<7nimzx5u6q.fsf@ecube.ecubist.org>
In reply to#514
On 2018-12-13 09:52:45 -05, Barry Fishman wrote:
> On 2018-12-11 05:19:18 -08, kfjwheeler@gmail.com wrote:
>> I'm kind of a beginner when it comes to Haskell.  Specifically with
>> regards to IO.
>>
>> I'm tying to write a very simple program that asks the user to input
>> a list of words.  It gets words until the user enters "done".
>>
>> I'm getting close, but it's still asking for input after I enter
>> "done".  I can't seem to get mapM to be lazy in the way I want.
>
> IO can be lazy, but the order of actions needs to preserved, so that
> each putStrLn prompt precedes it's getLine.

Oops, I was wrong.  If I replace the:

   xs <- getWordList 1

with:

  xs <- take 3 `fmap` getWordList 1

the values continue to be read.  So the IO can't be read lazily as far
as I can tell.

--
Barry Fishman

[toc] | [prev] | [next] | [standalone]


#516

FromBarry Fishman <barry@ecubist.org>
Date2018-12-13 10:46 -0500
Message-ID<7nefal5t0g.fsf@ecube.ecubist.org>
In reply to#515
On 2018-12-13 10:21:17 -05, Barry Fishman wrote:
> Oops, I was wrong.  If I replace the:
>
>    xs <- getWordList 1
>
> with:
>
>   xs <- take 3 `fmap` getWordList 1
>
> the values continue to be read.  So the IO can't be read lazily as far
> as I can tell.

All I can seem to do is produce a infinite lazy list of IO actions,
prompting for and reading the words:

--8<---------------cut here---------------start------------->8---
#! /usr/bin/env runghc

import System.IO (hFlush, stdout)

main :: IO ()
main = do
  putStrLn "Hello.  Please enter each word."
  xs <- sequence . take 3 $ map getWordIO [1..]
  mapM_ putStrLn xs
  where getWordIO :: Int -> IO String
        getWordIO n = do
          putStr $ "Input number " ++ show n ++ ": "
          hFlush stdout
          getLine
--8<---------------cut here---------------end--------------->8---

So the list that I 'take 3' from is a '[IO String]' and I sequence though
it to produce an 'IO [String]'.

--
Barry Fishman

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.haskell


csiph-web