• Get mapM to be lazy for IO

    From kfjwheeler@gmail.com@21:1/5 to All on Tue Dec 11 05:19:18 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark Carroll@21:1/5 to kfjwheeler on Tue Dec 11 13:36:06 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaylen Wheeler@21:1/5 to Mark Carroll on Wed Dec 12 17:52:49 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaylen Wheeler@21:1/5 to Mark Carroll on Wed Dec 12 18:21:41 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Rubin@21:1/5 to Kaylen Wheeler on Wed Dec 12 19:29:50 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Rubin@21:1/5 to Kaylen Wheeler on Wed Dec 12 19:31:16 2018
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Barry Fishman@21:1/5 to Barry Fishman on Thu Dec 13 10:21:17 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Barry Fishman@21:1/5 to kfjwheeler@gmail.com on Thu Dec 13 09:52:45 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Barry Fishman@21:1/5 to Barry Fishman on Thu Dec 13 10:46:39 2018
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)