Новичок. Получил модуль под названием HHtml, выводящий это:
setDoc = "<!DOCTYPE = <html><head>"
setTitle = "<title>" ++ htmlTitle generator ++ "</title>"
setHeader = "<header>" ++ htmlHeader generator ++ "</header>"
setMeta = "<meta>" ++ htmlMeta generator ++ "</meta></head>"
setBody = "<body>" ++ htmlBody generator ++ "</body>"
setFooter = "<footer>" ++ htmlFooter generator ++ "</footer>"
setEOF = "</html>"
setHTML = [setDoc, setTitle, setHeader, setMeta, setBody, setFooter, setEOF]
главный файл:
import HHtml
import System.IO
main = do
let content = mapM_ putStrLn setHTML
writeFile "index.html" content
Теперь, как бы я ни смотрел на это, я продолжаю получать Couldn't match type IO() with [Char]
или любой другой вариант в этом отношении. Я понимаю сообщение об ошибке, но не понимаю, как ее исправить. Спасибо за указатели!
вывод (writeFile) html, созданный с помощью mapM_
, поверх списка setHTML
. SetDoc, setTitle и т. д. Являются просто заполнителями
Для чего вы используете mapM_
?
Потому что в конечном итоге значения в списке будут вводом-выводом.
просто чтобы убедиться - если вы используете это для производства, убедитесь, что вы либо контролируете ввод, либо выполняете экранирование, чтобы избежать JS-инъекций
@epsilonhalbe: Я новичок в Haskell, но если я начну писать предварительный код, это поможет мне использовать информацию из всех книг (LYAH, RWH, Graham Hutton и т. д.). Я планирую сделать его более гибким и использовать шаблоны HTML5 / JS и Haskell для вставки кода, поэтому ваш комментарий очень важен для меня. Спасибо!
@chi: спасибо за участие. Я читал это, но для меня это не имеет прямого отношения. Я знаю о readFile / writeFile и о том, как работать. Джон Парди пришел с идеальным ответом, просто используя unlines, вместо того, чтобы пропустить его через монадическую функцию.
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
, также известный как [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
Исправление зависит от того, чего вы пытаетесь достичь. Что должен делать этот код?