mapM_ putStrLn setHTML
- это действие типа IO ()
, которое вы назначаете имени content
с помощью оператора let
. При выполнении этого действия печатает каждую строку setHTML
, ничего не возвращая. Вы можете выполнить это действие, написав что-то вроде этого:
main = do
let content = mapM_ putStrLn setHTML
content
Без переменной это просто:
main = mapM_ putStrLn setHTML
Но content
является непрозрачным значением - единственное, что вы можете с ним сделать, это выполнить его из main
, присоединить его к другим IO
действиям с помощью >>=
(или do
нотации) и сохраните его в структуре данных (которая здесь не нужна). В частности, он не «хранит» содержимое страницы, он просто описывает среде выполнения, как должен печатать этот контент. И в любом случае вы заметили несоответствие типов: writeFile
принимает String
, a.k.a. [Char]
, что, очевидно, не является IO ()
.
Но так как вы, очевидно, хотите использовать writeFile
для записи каждой строки setHTML
в файл, вместо стандартного вывода, вам не нужно действие, которое будет печатать строки - вам нужны сами строки, соединенные вместе с новыми строками. Есть несколько возможных способов сделать это, в зависимости от того, как вы хотите расширить этот код.
Одним из способов является использование функции unlines :: [String] -> String
для объединения строк вместе с символами новой строки, а затем использование writeFile
для записи результирующих String
в "index.html"
:
main = writeFile "index.html" (unlines setHTML)
Если вы хотите поместить составное содержимое в переменную, вы, конечно, можете сделать это:
main = do
let content = unlines setHTML
writeFile "index.html" content
(Действительно, вы можете перевести вызов unlines
в определение setHTML
, если вам не нужно setHTML
, чтобы быть [String]
.)
Теперь writeFile
примет content
, потому что это значение String
, а не IO ()
. Это хороший подход, поскольку он сохраняет логику построения страницы в чистоте и использует IO
только так, как нужно для записи страницы.
В качестве альтернативы вы можете использовать более настоятельный подход, оставаясь в IO
. Тогда хорошей функцией для использования будет withFile
(из System.IO
), которая имеет следующий тип:
FilePath -> IOMode -> (Handle -> IO r) -> IO r
Для открытия требуется FilePath
, IOMode
(например, ReadMode
или WriteMode
), чтобы указать, будете ли вы читать или писать на дескриптор, и функция который принимает дескриптор и выполняет некоторое IO
и возвращает результат некоторого типа r
; он возвращает действие IO
, которое открывает файл, запускает вашу функцию, автоматически гарантирует, что файл закрыт (даже если возникло исключение), и возвращает результат.
Тогда вы будете использовать mapM_
аналогично тому, как у вас уже есть, чтобы печатать каждую строку для этого дескриптора - для этого есть hPutStrLn :: Handle -> String -> IO ()
, который пишет в определенный дескриптор, вместо putStrLn
, который пишет в стандартный вывод. Компактная версия:
main = withFile "index.html" WriteMode $ \file -> do
mapM_ (hPutStrLn file) setHTML
Или более подробная версия, если вам не нравится внешний вид лямбды:
main = withFile "index.html" WriteMode writeContents
where writeContents file = mapM_ (hPutStrLn file) setHTML