This was actually something I wanted to understand with my previous question, but I misworded it by giving for granted the solution would somehow have to build on sequence and repeat, so I got an intersting answer, which is definitely useful to solve the problem at hand, but I didn't actually solve my curiosity as well, so I'm rephrasing the quesition as in the title.
As you can see from the linked question, I initially wrote
main :: IO ()
main = do
x <- takeWhile (/= 'q') <$> sequence (repeat getChar)
print "you pressed q"
thinking x would be a finite-length [Char].
But I'm now under the impression that I've not "just" used the wrong tool above (takeWhile, a pure function), but that there's actually no (orthodox) tool at all to bail out of that IO [Char] that is sequence (repeat getChar).
Am I correct?
If not, then how do you bail out of that?
Beware, I'm not asking now the same question as before. I'm looking for a solution that does make use of sequence (repeat getChar) and still manages to get to the print "you pressed q" action, or the explanatin of why such a solution doesn't exist.
If you
sequencean infinite list ofIOoperations, you can't bail out via "pure" program logic. So, you are correct that there is no "orthodox" tool to bail out ofsequence (repeat getChar).The only way to bail out is to invoke an
IOeffect to do "impure" bailing, for example by raising an exception, killing the thread, exiting the program, or similar. In these cases, you can't easily return a value, so this wouldn't be very useful for your use case. Alternatively, instead of bailing out, you can "cheat" and use unsafe lazy I/O, which allows you to execute an initial prefix of the infinite list of actions and inspect the initial prefix of the returned list. You can either continue processing the list indefinitely or "abandon" the list after processing some finite number of elements.In general, though, either of these approaches are going to be bad practice, at least for your problem.
If you want to write a proper monadic action that reads characters until the first
'q', then the right way to do it with elementary Haskell code is something like this:The technical reason you can't use
sequenceto implement this logic is that this is a fundamentally monadic algorithm butsequenceis "only" an applicative combinator. Applicative combinators can't use the results of applicative actions to influence the "structure" of the computation. When yousequencean infinite list ofIOactions, you have committed to executing an infinite loop, and the results of the actions cannot change this into a finite loop, except as a side effect (exception, etc.). In particularsequencecan't implement the part ofgetToQwhere we inspect the return valuecand decide whether the rest of the computation is going to bepure []or a recursive call togetToQto do moregetChars. You can see this from the following (simplified) definition ofsequence:See how the returned
avalue from thexaction cannot be used to influence whether or not thesequence xsaction in the next line executes? That's howsequencediffers structurally fromgetToQ.Now, there are some packages, like
extraandmonad-loopsthat provide monadic combinators that can be used to succinctly write the equivalent ofgetToQ. For example, frommonad-loops, theunfoldWhileMwill do exactly what you want:There's nothing magical about these combinators. A simplified implementation of this combinator would be:
See how this combinator, in contrast to
sequence, is fundamental a non-applicative, monadic combinator. It uses the return valueafrom the actionxto determine the structure of the rest of the computation: (i) recurse to execute morexactions; or (ii) end the computation.