Борьба с вводом-выводом при чтении файла конфигурации в Haskell

У меня есть входные данные, предназначенные для моих еще не написанных приложений Haskell, которые находятся в файле. Я не обновляю файл. Мне просто нужно прочитать файл и передать его в мою функцию Haskell, которая ожидает список строк. Но чтение файла, конечно, дает IO объектов данных. Я узнал, что с помощью операции <- можно каким-то образом "извлечь" строки, упакованные в структуру IO, поэтому я попробовал эту попытку:

run :: [String]
run = do
  datadef_content <- readFile "play.txt" -- yields a String
  let datadef = lines datadef_content -- should be a [String]
  return datadef

Я поместил это в файл play.hs и загрузил его из ghci с помощью

:l play

К моему удивлению, я получил сообщение об ошибке для строки readFile

 Couldn't match type ‘IO’ with ‘[]’
 Expected type: [String]
   Actual type: IO String

а для return сообщение об ошибке

 Couldn't match type ‘[Char]’ with ‘Char’
 Expected type: [String]
   Actual type: [[String]]

Первое, кажется, указывает на то, что я не могу избавиться от IO, а последнее сообщение, похоже, предполагает, что lines вернет список списка строк, что также не имеет для меня смысла.

Как мне это сделать правильно?


person user1934428    schedule 02.02.2020    source источник
comment
Вы действительно не можете избавиться от IO (или, по крайней мере, вы не должны стараться никогда этого не делать). Есть unsafeIO, но обычно это создает больше проблем, чем решает.   -  person Willem Van Onsem    schedule 02.02.2020
comment
Do-нотация объяснена яркими цветами.   -  person Will Ness    schedule 02.02.2020
comment
Не пытайтесь run :: [String]. Вместо этого возьмите функцию foo :: [String] -> blah, которая принимает содержимое run в качестве аргумента, и преобразуйте ее в wrappedFoo :: IO [String] -> blah, используя соответственно fmap или (=<<), а затем примените ее к run :: IO [String].   -  person Daniel Wagner    schedule 03.02.2020
comment
Haskell вынуждает вас объявить, что вы выполняете ввод-вывод в типе, если вы действительно выполняете ввод-вывод. Объявление run :: [String] равносильно обещанию того, что run не выполняет операций ввода-вывода и является чистым постоянным списком строк. Если вы хотите выполнить ввод-вывод, вам необходимо объявить run :: IO [String], сообщив компилятору, что это зависит от ввода-вывода и что он может выполняться несколько раз, возможно, с разными результатами (если, например, "play.txt" изменяет свое содержимое).   -  person chi    schedule 03.02.2020


Ответы (1)


Вы объявляете run значением [String]. Но return не является ключевым словом, которое обеспечивает возвращаемое значение функции; это функция типа Monad m => a -> m a. return datadef производит значение типа IO [String], которое становится возвращаемым значением функции.

Решение состоит в том, чтобы предоставить правильный тип возвращаемого значения для run:

run :: IO [String]
run = do
    ...

run можно также более кратко определить как

run = fmap lines (readFile "play.txt")

Хотя синтаксис do предполагает, что есть способ извлечь значение из действия IO; все, что вы можете сделать, это «протолкнуть» вызов lines в действие.

person chepner    schedule 02.02.2020
comment
Спасибо; Я попробую. Вы случайно не знаете источник, из которого я могу узнать значение fmap? Лучшее, что я мог найти в Google, - это это описание Data.Functor, и он довольно краток, и я не могу понять, как он объясняет ваш пример. - person user1934428; 05.02.2020
comment
Я попробовал ваш подход. Теперь объявление run в порядке, но означает ли это, что я должен выполнять все мои вычисления, которые используют содержимое файла внутри блока do? - person user1934428; 09.02.2020
comment
Я нашел эту веб-страницу, которая, кажется, отвечает на мой вопрос. - person user1934428; 09.02.2020