Evg
Evg

Reputation: 3080

Haskell sequence of IO actions processing with filtration their results in realtime+perfoming some IO actions in certain moments

I want to do some infinite sequence of IO actions processing with filtration their results in realtime+perfoming some IO actions in certain moments: We have some function for reducing sequences (see my question haskell elegant way to filter (reduce) sequences of duplicates from infinte list of numbers):

f :: Eq a => [a] -> [a]
f = map head . group

and expression

join $ sequence <$> ((\l -> (print <$> l)) <$> (f <$> (sequence $ replicate 6 getLine)))

if we run this, user can generate any seq of numbers, for ex:

1
2
2
3
3
"1"
"2"
"3"
[(),(),()]

This means that at first all getLine actions performed (6 times in the example and at the end of this all IO actions for filtered list performed, but I want to do IO actions exactly in the moments then sequencing reduces done for some subsequences of same numbers.

How can I archive this output:

1     
2
"1"
2     
3
"2"        
3
3
"3"
[(),(),()]

So I Want this expression not hangs:

 join $ sequence <$> ((\l -> (print <$> l)) <$> (f <$> (sequence $ repeat getLine)))

How can I archive real-time output as described above without not blocking it on infinite lists?

Upvotes: 2

Views: 174

Answers (3)

Evg
Evg

Reputation: 3080

I found a nice solution with iterateUntilM

iterateUntilM (\_->False) (\pn -> getLine >>= (\n -> if n==pn then return n else (if pn/="" then print pn else return ()) >> return n) ) ""

I don't like some verbose with

(if pn/="" then print pn else return ())

if you know how to reduce this please comment)

ps. It is noteworthy that I made a video about this function :) And could not immediately apply it :(

Upvotes: 0

danidiaz
danidiaz

Reputation: 27766

This seems like a job for a streaming library, like streaming.

{-# LANGUAGE ImportQualifiedPost #-}
module Main where

import Streaming
import Streaming.Prelude qualified as S

main :: IO ()
main =
      S.mapM_ print
    . S.catMaybes
    . S.mapped S.head
    . S.group
    $ S.replicateM 6 getLine

"streaming" has an API reminiscent to that of lists, but works with effectful sequences.

The nice thing about streaming's version of group is that it doesn't force you to keep the whole group in memory if it isn't needed.


The least intuitive function in this answer is mapped, because it's very general. It's not obvious that streaming's version of head fits as its parameter. The key idea is that the Stream type can represent both normal effectful sequences, and sequences of elements on which groups have been demarcated. This is controlled by changing a functor type parameter (Of in the first case, a nested Stream (Of a) m in the case of grouped Streams).

mapped let's you transform that functor parameter while having some effect in the underlying monad (here IO). head processes the inner Stream (Of a) m groups, getting us back to an Of (Maybe a) functor parameter.

Upvotes: 1

chepner
chepner

Reputation: 531748

Without a 3rd-party library, you can lazily read the contents of standard input, appending a dummy string to the end of the expected input to force output. (There's probably a better solution that I'm stupidly overlooking.)

import System.IO

print_unique :: (String, String) -> IO ()
print_unique (last, current) | last == current = return ()
                             | otherwise = print last

main = do
  contents <- take 6 <$> lines <$> hGetContents stdin
  traverse print_unique (zip <*> tail $ (contents ++ [""]))

zip <*> tail produces tuples consisting of the ith and i+1st lines without blocking. print_unique then immediately outputs a line if the following line is different.

Essentially, you are sequencing the output actions as the input is executed, rather than sequencing the input actions.

Upvotes: 1

Related Questions