Haskell-последовательность обработки IO-действий с фильтрацией их результатов в реальном времени+выполнение некоторых IO-действий в определенные моменты

Я хочу сделать какую-то бесконечную последовательность обработки IO-действий с фильтрацией их результатов в реальном времени + выполнение некоторых IO-действий в определенные моменты: У нас есть некоторая функция для сокращения последовательностей (см. мой вопрос элегантный способ haskell для фильтрации (уменьшения) последовательностей дубликатов из бесконечного списка чисел):

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

и выражение

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

если мы запустим это, пользователь может сгенерировать любую последовательность чисел, например:

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

Это означает, что сначала выполняются все действия getLine (в примере 6 раз, а в конце выполняются все действия ввода-вывода для отфильтрованного списка, но я хочу выполнять действия ввода-вывода именно в моменты, когда последовательность сокращается для некоторых подпоследовательностей одинаковых чисел .

Как я могу заархивировать этот вывод:

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

Итак, я хочу, чтобы это выражение не зависало:

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

Как я могу архивировать вывод в реальном времени, как описано выше, не блокируя его в бесконечных списках?

Пожалуйста, опишите, что вы хотите сгенерировать, а не предполагайте, что это очевидно, глядя на ваш существующий код.

chepner 24.12.2020 16:43

Вы предполагаете, что ввод осуществляется в порядке возрастания, и хотите выводить каждое уникальное число, когда вводится следующее большее число?

chepner 24.12.2020 16:45

Нет, это может быть последовательность любых чисел, повторяющиеся подпоследовательности одних и тех же чисел должны быть сведены к одному числу.

Evg 24.12.2020 16:47

Итак, что именно заставляет вас выводить, например, "1", когда вы это делаете? Что заставило вас ждать, пока не будет введено 2, и что произойдет, если 1 снова появится в выводе позже? Выходные данные, похоже, не производятся ни как можно раньше, ни как можно позже.

chepner 24.12.2020 17:01

@chepner, если следующий ввод отличается от предыдущего, это означает, что seq «сломан», и мы можем выполнить действие печати предыдущего числа.

Evg 24.12.2020 17:04

Ах, хорошо, дублируйте подпоследовательности. Я упустил это различие.

chepner 24.12.2020 17:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
131
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Это похоже на работу для потоковой библиотеки, например, потоковой передачи.

{-# 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

«потоковая передача» имеет API, напоминающий списки, но работает с эффективными последовательностями.

Преимущество потоковой версии group заключается в том, что она не заставляет вас хранить всю группу в памяти, если она не нужна.


Наименее интуитивная функция в этом ответе — отображается , потому что она очень общая. Не очевидно, что потоковая версия head подходит в качестве его параметра. Ключевая идея заключается в том, что тип Stream может представлять как обычные эффективные последовательности, так и последовательности элементов, на которых разграничены группы. Это управляется изменением параметра типа функтора ( Of в первом случае, вложенного Stream (Of a) m в случае сгруппированных потоков).

mapped давайте вы преобразуете этот параметр функтора, оказав некоторое влияние на базовую монаду (здесь IO). head обрабатывает внутренние Stream (Of a) m группы, возвращая нас к параметру Of (Maybe a) функтора.

Без сторонней библиотеки вы можете лениво читать содержимое стандартного ввода, добавляя фиктивную строку в конец ожидаемого ввода для принудительного вывода. (Вероятно, есть лучшее решение, которое я тупо упускаю из виду.)

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 производит кортежи, состоящие из i-й и i+1-й строк без блокировки. print_unique затем немедленно выводит строку, если следующая строка отличается.

По сути, вы упорядочиваете действия вывода по мере выполнения ввода, а не действия ввода.

Ответ принят как подходящий

Я нашел хорошее решение с iterateUntilM

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

мне не нравится многословие с

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

если вы знаете, как уменьшить это, пожалуйста, напишите)

пс. Примечательно, что я снял видео об этой функции :) И не смогла сразу применить :(

Другие вопросы по теме