Haskell writeFile

Новичок. Получил модуль под названием 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] или любой другой вариант в этом отношении. Я понимаю сообщение об ошибке, но не понимаю, как ее исправить. Спасибо за указатели!

Исправление зависит от того, чего вы пытаетесь достичь. Что должен делать этот код?

melpomene 12.09.2018 11:01

вывод (writeFile) html, созданный с помощью mapM_, поверх списка setHTML. SetDoc, setTitle и т. д. Являются просто заполнителями

Madderote 12.09.2018 11:04

Для чего вы используете mapM_?

melpomene 12.09.2018 11:16

Потому что в конечном итоге значения в списке будут вводом-выводом.

Madderote 12.09.2018 11:47

просто чтобы убедиться - если вы используете это для производства, убедитесь, что вы либо контролируете ввод, либо выполняете экранирование, чтобы избежать JS-инъекций

epsilonhalbe 12.09.2018 12:32

@epsilonhalbe: Я новичок в Haskell, но если я начну писать предварительный код, это поможет мне использовать информацию из всех книг (LYAH, RWH, Graham Hutton и т. д.). Я планирую сделать его более гибким и использовать шаблоны HTML5 / JS и Haskell для вставки кода, поэтому ваш комментарий очень важен для меня. Спасибо!

Madderote 12.09.2018 16:24

@chi: спасибо за участие. Я читал это, но для меня это не имеет прямого отношения. Я знаю о readFile / writeFile и о том, как работать. Джон Парди пришел с идеальным ответом, просто используя unlines, вместо того, чтобы пропустить его через монадическую функцию.

Madderote 12.09.2018 16:30
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
7
1 037
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

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