Haskel: как заставить оценку функций и записать в файл последовательно? - PullRequest
3 голосов
/ 19 апреля 2019

У меня проблема с ленивым вводом-выводом в Haskell. Несмотря на чтение других вопросов в этой области, я не мог понять, как решить мой конкретный случай.

Я использую пакет скальпеля для разбора html. Вариант использования прост: один сайт содержит ссылки на другие сайты, которые описывают какое-то событие. Таким образом, я написал следующие структуры (я пропустил некоторые реализации здесь):

type Url = String

-- function that parses all urls
allUrls :: Url -> IO (Maybe [Url])

data Event = Event { ... }

-- function that parses an event
parseEvent :: Url -> IO (Maybe Event)

-- function that writes the event to a file
doThings :: Url -> IO ()
doThings url = return url >>= parseEvent >>= (appendFile "/tmp/foo.txt" . show)

-- function that should take all urls and write their events to a file
allEvents :: IO (Maybe [Url]) -> IO (Maybe (IO [()]))
allEvents urls = urls >>= return . liftM (mapM doThings)

-- or alternatively:

-- function that takes all urls and returns all events
allEvents :: IO (Maybe [Url]) -> IO (Maybe (IO [Maybe Event]))
allEvents urls = urls >>= return . liftM (mapM parseEvent)

-- some function that writes all events to a file
allEventsToFile :: IO (Maybe (IO [Maybe Event])) -> IO()
??? 

Функция doThings работает как положено. Получив URL, он анализирует соответствующее событие и записывает его в файл. Но все события абсолютно ничего не делают из-за лени. Как я могу форсировать оценку внутри allEvents?

1 Ответ

4 голосов
/ 19 апреля 2019

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

Проблема заключается в том, чтона самом деле вы не делаете IO в allEvents - вы просто перемещаетесь вокруг значений в IO функторе .Этими значениями являются IO сами действия, но это не имеет значения.В частности, a >>= return . f всегда совпадает с fmap f a, согласно законам монады.И fmapping в IO не связывает действия.

Эта проблема уже наблюдается в сигнатуре типа: -> IO (Maybe (IO [()])) говорит, что функция выдает действия IO, которые вы затем могли бы выполнить позже.Но в этом случае вы хотите выполнить все, когда вы выполняете allEvents.Таким образом, подпись может быть

allEvents :: IO (Maybe [Url]) -> IO ()

(или, возможно, -> IO (Either EventExecError ()), если вы хотите правильно обработать ошибку).

Это, вероятно, все еще не то, что вы хотите: почему вы принять IO действие в качестве аргумента?Это означает, что allEvents сам должен будет выполнить это действие, чтобы сначала получить URL-адреса, прежде чем выполнять какую-либо собственную работу.Это может иметь свои побочные эффекты и давать разные результаты для разных звонков, вы хотите это?

Наверное, нет, так что на самом деле это должно быть

allEvents :: Maybe [Url] -> IO ()

Теперь вы начинаете с простого значения Maybe, которое вы можете легко сопоставить с шаблоном:

allEvents Nothing = ?  -- perhaps simply `return ()`
allEvents (Just urls) = mapM_ doThings urls

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

main :: IO ()
main = do
  urlq <- allUrls
  allEvents urlq

... или короткое allUrls >>= allEvents.

...