Вот пример дорожной карты.
Строки и текст
Как вы, вероятно, знаете, тип Haskell String
- это просто синоним типа для [Char]
, где Char
- это тип данных, который может представлять одну кодовую точку Unicode. Это делает String
идеальным типом данных для представления текстовых данных, за исключением незначительной проблемы, которая - в виде связанного списка в штучной упаковке Char
значений - потенциально может быть крайне неэффективной.
Тип данных Text
из пакета text
решает эту проблему. Text
также, как и String
, представляет собой список значений Char
, но вместо использования фактического списка на Haskell он использует представление с эффективным использованием времени и пространства. Это должна быть ваша замена String
, когда вам нужно эффективно работать с текстовыми (Unicode) данными.
Как и многие другие типы данных в стандартных библиотеках Haskell, он поставляется в ленивых и строгих вариантах. Оба варианта имеют одинаковое имя Text
, но они содержатся в отдельных модулях, поэтому вы можете сделать следующее:
import qualified Data.Text as TS
import qualified Data.Text.Lazy as TL
, если вам нужно было использовать оба варианта TS.Text
и TL.Text
в одной и той же программе.
Точная разница между вариантами описана в документации для Data.Text . В двух словах, вы должны по умолчанию использовать строгую версию. Вы используете ленивую версию только в двух случаях. Во-первых, если вы планируете работать с большим значением Text
постепенно, рассматривая его скорее как текстовый «поток», чем «строку», тогда ленивая версия - хороший выбор. (Например, программа для чтения огромного числового CSV-файла может считывать файл в виде длинного ленивого потока Text
и сохранять результаты в эффективном числовом типе, таком как Vector
из неупакованных значений Double
, чтобы избежать сохранения весь вводимый текст в памяти.) Во-вторых, если вы строите большую строку Text
из множества маленьких кусочков, то вы не хотите использовать строгие версии, потому что их неизменность означает, что их нужно копировать всякий раз, когда вы Добавьте что-нибудь. Вместо этого вы хотели бы использовать ленивый вариант с функциями из Data.Text.Lazy.Builder
.
1038 * байтовые строки *
Тип данных ByteString
из пакета bytestring
, с другой стороны, является эффективным представлением списка байтов. Точно так же, как Text
является эффективной версией [Char]
, вы должны рассматривать ByteString
как эффективную версию [Word8]
, где Word8
- это тип Haskell, представляющий один беззнаковый байт данных со значением 0-255 , Эквивалентно, вы можете думать о ByteString
как о представлении фрагмента памяти или фрагмента данных, которые должны быть считаны или записаны в файл, точно как байты для байтов. Это также входит в ленивые и строгие ароматы:
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
, и соображения по использованию вариантов аналогичны тем, что указаны для Text
.
Чтение и запись в файлы
В программе на Haskell обычно строки Unicode обычно представляются в виде значений String
или Text
. Однако, чтобы считать их из файлов или записать их в файлы, их необходимо кодировать и декодировать из последовательностей байтов.
Самый простой способ справиться с этим - использовать функции Haskell, которые автоматически обрабатывают кодирование и декодирование. Как вы, наверное, знаете, в Prelude
уже есть две функции, которые читают и пишут строки напрямую:
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
Кроме того, в text
есть функции readFile
и writeFile
, которые делают это. Вы можете найти версии как в Data.Text.IO
, так и в Data.Text.Lazy.IO
. Кажется, они имеют одинаковые подписи, но одна работает на строгом типе Text
, а другая - на ленивом Text
типе:
readFile :: FilePath -> IO Text
writeFile :: FilePath -> Text -> IO ()
Вы можете сказать, что эти функции выполняют кодирование и декодирование автоматически, потому что они возвращают и принимают значения Text
, а не ByteString
. Используемая кодировка по умолчанию будет зависеть от операционной системы и ее конфигурации. В типичном современном дистрибутиве Linux это будет UTF-8.
Кроме того, вы можете читать или записывать необработанные байты из файла, используя функции из пакета bytestring
(опять же, либо ленивые, либо строгие версии, в зависимости от модуля):
readFile :: FilePath -> IO ByteString
writeFile :: FilePath -> ByteString -> IO ()
Они имеют те же имена, что и версии text
, но вы можете видеть, что они имеют дело с необработанными байтами, потому что они возвращают и принимают ByteString
аргументы. В этом случае, если вы хотите использовать эти ByteString
в качестве текстовых данных, вам нужно будет их самостоятельно декодировать или кодировать. Если, например, ByteString
представляет версию текста в кодировке UTF-8, то вам нужны следующие функции: Data.Text.Encoding
(для строгих версий) или Data.Text.Lazy.Encoding
(для ленивых версий):
decodeUtf8 :: ByteString -> Text
encodeUtf8 :: Text -> ByteString
Модули Char8
Теперь модули в Data.ByteString.Char8
и Data.ByteString.Lazy.Char8
являются особым случаем. Когда простой текст ASCII был закодирован с использованием одной из нескольких схем кодирования, сохраняющих ASCII (включая сам код ASCII, кодирование Latin-1 и другие символы Latin-x и UTF-8), оказывается, что кодированный ByteString
является просто простое однобайтовое кодирование кодовых точек Unicode от 0 до 127. Чуть более широко, когда текст был закодирован в Latin-1, тогда кодированный ByteString
представляет собой простое однобайтовое кодирование символов кодовых точек Unicode от 0 до 255. В этих случаях и только в этих случаях функции в этих модулях можно безопасно использовать для обхода явных шагов кодирования и декодирования и просто обрабатывать строку байтов как текст ASCII и / или Latin-1 непосредственно путем автоматического преобразования отдельных байтов в значения Unicode Char
и обратно.
Поскольку эти функции работают только в этом особом случае, обычно следует избегать их использования, кроме как в специализированных приложениях.
Кроме того, как было отмечено в комментарии, варианты ByteString
в этих Char8
модулях ничем не отличаются от простых строгих и ленивых ByteString
вариантов; просто обрабатываются так, как если бы они представляли собой строки Char
значений вместо Word8
значений функций в этих модулях - типы данных одинаковы, только интерфейс функции отличается.
Общая стратегия
Итак, если вы работаете с обычным текстом и кодировкой вашей операционной системы по умолчанию, просто используйте строгий тип данных Text
из Data.Text
и (высокоэффективные) функции ввода-вывода из Data.Text.IO
. Вы можете использовать ленивые варианты для потоковой обработки или построения больших строк из крошечных кусочков, и вы можете использовать Data.Text.Read
для простого анализа.
Вы должны быть в состоянии вообще избегать использования String
в большинстве ситуаций, но если вы обнаружите, что вам нужно конвертировать туда и обратно, тогда эти функции преобразования в Data.Text
(или Data.Text.Lazy
) будут полезны:
pack :: String -> Text
unpack :: Text -> String
Если вам нужен больший контроль над кодировкой, вы все еще хотите использовать Text
во всей вашей программе, за исключением "краев", где вы читаете или пишете файлы. На этих границах используйте функции ввода / вывода из Data.ByteString
(или Data.ByteString.Lazy
) и функции кодирования / декодирования из Data.Text.Encoding
или Data.Text.Lazy.Encoding
.
Если вам нужно смешать строгие и ленивые варианты, обратите внимание, что Data.Text.Lazy
содержит:
toStrict :: TL.Text -> TS.Text -- convert lazy to strict
fromStrict :: TS.Text -> TL.Text -- vice versa
и Data.ByteString.Lazy
содержат соответствующие функции для значений ByteString
:
toStrict :: BL.ByteString -> BS.ByteString
fromStrict :: BS.ByteString -> BL.ByteString