4

Haskell is challenging! What I figured out so far is that I can do the following to simulate a for-loop in Haskell to get a list of numbers from the user:

myList <- sequence [putStr "Enter an integer: " >> (
    \s -> read s :: Int) <$> getLine | t <- [1..5]]

Great! So myList contains five integers that I have entered. Great! But here's the catch. Instead of a for-loop that iterates five times (or any finite number of times) how can I convert the above to the equivalent while-loop?

So what I'm thinking of is this, but it won't work, unfortunately. Is there some "magic" way to make it work?

takeWhile (\x -> x > 0) $ sequence [putStr "Enter an integer: " >> (
    \s -> read s :: Int) <$> getLine | t <- [1..]]

The problem is that (\x -> x > 0) works with Ints. (Or any Num type.) But what's coming from that list is really a bunch of IO Ints. x > 0 returns a Bool. I need a function that returns an IO Bool? I'm a little lost. Can someone point the way to Haskell enlightenment for me?! Studying this on my own isn't exactly easy!!! Thank you so much!!!

5
  • That magic is called forever! Commented Mar 23, 2020 at 5:07
  • Also I don't know if you noticed, but your code snippets don't compile. Commented Mar 23, 2020 at 5:14
  • 1
    It's hard to see how forever could ever yield something that acts like a while loop. Commented Mar 23, 2020 at 5:14
  • @amalloy I was thinking of while(true)... :facepalm: Commented Mar 23, 2020 at 5:15
  • The first code snippet DOES compile! Unless there's a typo in there, but trust me, I tested it several times and it compiles. The second code snippet, with "takeWhile" however, does not compile, but I just wanted people to get an idea of what I was trying to do. Commented Mar 24, 2020 at 17:18

2 Answers 2

6

You cannot write this program with a sequence of an infinite list of IO actions. Any operations you perform "outside" of the sequence will be unable to inspect its contents, and any operations inside the sequence will be unable to stop it from continuing.

Instead, you must write an IO action which reads an Int, inspects it, and decides whether to continue or to stop at that time.

positiveInts :: IO [Int]
positiveInts = do
  putStr "Enter an integer: "
  i <- readLn
  if i <= 0
    then pure []
    else (i:) <$> positiveInts
Sign up to request clarification or add additional context in comments.

Comments

0

@amalloy's answer is great. It's kind of conditional sequencing.

Perhaps we may attempt generalizing it further by inventing takeWhileM (basically conditional sequencing) which sequences only an initial part of an indefinitely long list of actions while a predicate satisfies.

takeWhileM :: Monad m => (a -> Bool) -> [m a] -> m [a]
takeWhileM f (a:as) = a >>= \n -> if f n then (n:) <$> takeWhileM f as
                                         else pure []

So for this particular case i run it like

λ> takeWhileM (> 0) . repeat $ putStr "Enter an integer:" >> readLn
Enter an integer:1
Enter an integer:2
Enter an integer:3
Enter an integer:4
Enter an integer:5
Enter an integer:0
[1,2,3,4,5]

7 Comments

This isn't what I'd expect a function named takeWhileM to do. The one in monad-loops has type Monad m => (a -> m Bool) -> [a] -> m [a], which better fits with functions like filterM or mapM.
@amalloy Yes.. you are possibly right.. we may perhaps call this function sequenceWhile for a better naming convention.
Hey there @amalloy, wow! Thanks so much!!!! This is exactly what I was looking for. Is Haskell your primary language? I'm still at the dabbler stage. I think Haskell is really interesting, and I want to continue working with it, but not sure if I'll ever reach any high level or professional level of skill in the language. Maybe? Could be Nothing or Just success!!! Hahahaha. Just an FP joke. Okay, thanks again. This was super helpful, and your takeWhileM function was not hard to read. By the way, can you recommend any resources for Haskell wannabes who are not geniuses?
@amalloy I have just discovered that it was needless to rediscover sequenceWhile since it already lives in Control.Monad.Extra which fits the job like a glove. So yes.. it should be called squenceWhile. :)
@Douglas Lewitt I believe you address me... As @amalloy mentions this function should not be called takeWhileM since by convention that name fits to another type signature as mentioned in his above comment. This function should be called sequenceWhile instead. Don't let Haskell intimidate you. Once you (can) forget about what you know about programming Haskell starts making so much sense. All you need is time so never give up. Learn You a Haskell for Great Good is a good starting point.
|

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.