Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Получение строк из входного потокаДавайте посмотрим на действие ввода-вывода getContents, упроща- ющее обработку входного потока за счёт того, что оно позволяет рассматривать весь поток как обычную строку. Действие getContents читает всё содержимое стандартного потока ввода вплоть до обна- ружения символа конца файла. Его тип: getContents:: IO String. Са- мое приятное в этом действии то, что ввод-вывод в его исполнении является ленивым. Это означает, что выполнение foo <- getContents не приводит к загрузке в память всего содержимого потока и связы- ванию его с именем foo. Нет, действие getContents для этого слиш- ком лениво.Оно скажет: «Да, да, я прочту входные данные с тер- минала как-нибудь потом, когда это действительно понадобится!». В примере capslocker.hs для чтения ввода строка за строкой и пе- чати их в верхнем регистре использовалась функция forever. Если мы перейдём на getContents, то она возьмёт на себя все заботы о деталях ввода-вывода– о том, когда и какую часть входных дан- ных нужно прочитать. Поскольку наша программа просто берёт входные данные, преобразует их и выводит результат, пользуясь getContents, её можно написать короче: import Data.Char
main = do contents <- getContents putStr $ map toUpper contents Мы выполняем действие getContents и даём имя contents стро- ке, которую она прочтёт. Затем проходим функцией toUpper по всем символам этой строки и выводим результат на терминал. Имейте в виду: поскольку строки являются списками, а списки ленивы, как и действие getContents, программа не будет пытаться прочесть и со- хранить в памяти всё содержимое входного потока. Вместо этого
она будет читать данные порциями, переводить каждую порцию в верхний регистр и печатать результат. Давайте проверим: $./capslocker < haiku.txt Я МАЛЕНЬКИЙ ЧАЙНИК ОХ УЖ ЭТОТ ОБЕД В САМОЛЁТЕ ОН СТОЛЬ МАЛ И НЕВКУСЕН Работает. А что если мы просто запустим capslocker и будем пе- чатать строки вручную (для выхода из программы нужно нажать Ctrl + D)? $./capslocker хей хо ХЕЙ ХО идём ИДЁМ Чудесно! Как видите, программа печатает строки в верхнем ре- гистре по мере ввода строк. Когда результат действия getContents связывается с идентификатором сontents, он представляется в па- мяти не в виде настоящей строки, но в виде обещания, что рано или поздно он вернёт строку. Также есть обещание применить фун- кцию toUpper ко всем символам строки сontents. Когда выполняет- ся функция putStr, она говорит предыдущему обещанию: «Эй, мне нужна строка в верхнем регистре!». Поскольку никакой строки ещё нет, она говорит идентификатору сontents: «Аллё, а не считать ли строку с терминала?». Вот тогда функция getContents в самом деле считывает с терминала и передаёт строку коду, который её запра- шивал, чтобы сделать что-нибудь осязаемое. Затем этот код при- меняет функцию toUpper к символам строки и отдаёт результат в функцию putStr, которая его печатает. После чего функция putStr говорит, «Ау, мне нужна следующая строка, шевелись!» – и так про- должается до тех пор, пока не закончатся строки на входе, что мы обозначаем символом конца файла. Теперь давайте напишем программу, которая будет принимать некоторый вход и печатать только те строки, длина которых мень- ше 15 символов. Смотрим:
main = do contents <- getContents putStr $ shortLinesOnly contents
shortLinesOnly:: String -> String shortLinesOnly = unlines. filter (\line -> length line < 15). lines Фрагмент программы, ответственный за ввод-вывод, сделан настолько малым, насколько это вообще возможно. Так как пред- полагается, что наша программа печатает результат, основываясь на входных данных, её можно реализовать согласно следующей логике: читаем содержимое входного потока, запускаем на этом содержимом некоторую функцию, печатаем результат работы этой функции. Функция shortLinesOnly принимает строку – например, такую: "коротко\nдлииииииииииинно\nкоротко". В этом примере в строке на самом деле три строки входных данных: две короткие и одна (посе- редине) длинная. В результате применения функции lines получа- ем список ["коротко", "длииииииииииинно", "коротко"]. Затем список строк фильтруется, и остаются только строки, длина которых мень- ше 15 символов: ["коротко", "коротко"]. Наконец, функция unlines соединяет элементы списка в одну строку, разделяя их символом перевода строки: "коротко\nкоротко". Попробуем проверить, что получилось. Сохраните этот текст в файле shortlines.txt: Я короткая И я А я длиииииииинная!!! А уж я-то какая длиннющая!!!!!!! Коротенькая Длиииииииииииииииииииииинная Короткая Сохраните программу в файле shortlinesonly.hs и скомпилируй- те её: $ ghc shortlinesonly.hs [1 of 1] Compiling Main (shortlinesonly.hs, shortlinesonly.o) Linking shortlinesonly...
Чтобы её протестировать, перенаправим содержимое файла shortlines.txt на её поток ввода: $./shortlinesonly < shortlines.txt Я короткая И я Коротенькая Короткая Видно, что на терминал выведены только короткие строки.
Преобразование входного потока Подобная последовательность действий – считывание строки из потока ввода, преобразование её функцией и вывод результата – на- столько часто встречается, что существует функция, которая дела- ет эту задачу ещё легче; она называется interact. Функция interact принимает функцию типа String –> String как параметр и возвраща- ет действие ввода-вывода, которое примет некоторый вход, запус- тит заданную функцию и распечатает результат. Давайте изменим нашу программу так, чтобы воспользоваться этой функцией: main = interact shortLinesOnly
shortLinesOnly:: String -> String shortLinesOnly = unlines. filter (\line -> length line < 15). lines Этой программой можно пользоваться, либо перенаправляя файл в поток ввода, либо вводя данные непосредственно с кла- виатуры, строка за строкой. Результат будет одинаковым, однако при вводе с клавиатуры входные данные будут чередоваться с вы- ходными. Давайте напишем программу, которая постоянно считывает строку и затем говорит нам, является ли введённая строка палинд- ромом. Можно было бы использовать функцию getLine, чтобы она считывала строку, затем говорить пользователю, является ли она палиндромом, и снова запускать функцию main. Но легче делать это с помощью функции interact. Когда вы её используете, всегда ду- майте, как преобразовать некий вход в желаемый выход. В нашем случае мы хотим заменить строку на входе на "палиндром" или "не палиндром".
respondPalindromes:: String -> String respondPalindromes = unlines. map (\xs -> if isPal xs then "палиндром" else "не палиндром"). lines
isPal xs = xs == reverse xs Всё вполне очевидно. Вначале преобразуем строку, например "слон\nпотоп\nчто-нибудь" в список строк ["слон", "потоп", "что-нибудь"] Затем применяем анонимную функцию к элементам списка и по- лучаем: ["не палиндром", "палиндром", "не палиндром"] Соединяем список обратно в строку функцией unlines. Теперь мы можем определить главное действие ввода-вывода: main = interact respondPalindromes Протестируем: $./palindromes ха-ха не палиндром арозаупаланалапуазора палиндром печенька не палиндром Хоть мы и написали программу, которая преобразует одну боль- шую составную строку в другую составную строку, она работает так, как будто мы обрабатываем строку за строкой. Это потому что язык Haskell ленив – он хочет распечатать первую строку результата, но не может, поскольку пока не имеет первой строки ввода. Как толь- ко мы введем первую строку на вход, он напечатает первую строку на выходе. Мы выходим из программы по символу конца файла.
Также можно запустить нашу программу, перенаправив в неё со- держимое файла. Например, у нас есть файл words.txt: кенгуру радар ротор мадам Вот что мы получим, если перенаправим его на вход нашей про- граммы: $./palindromes < words.txt не палиндром палиндром палиндром палиндром Ещё раз: результат аналогичен тому, как если бы мы запускали программу и вводили слова вручную. Здесь мы не видим входных строк, потому что вход берётся из файла, а не со стандартного ввода. К этому моменту, вероятно, вы уже усвоили, как работает лени- вый ввод-вывод и как его можно использовать с пользой для себя. Вы можете рассуждать о том, каким должен быть выход для дан- ного входа, и писать функцию для преобразования входа в выход. В ленивом вводе-выводе ничего не считывается со входа до тех пор, пока это не станет абсолютно необходимым для того, что мы собираемся напечатать.
Чтение и запись файлов До сих пор мы работали с вводом-выводом, печатая на терминале и считывая с него. Ну а как читать и записывать файлы? В неко- тором смысле мы уже работали с файлами. Чтение с терминала можно представить как чтение из специального файла. То же вер- но и для печати на терминале – это почти что запись в файл. Два файла – stdin и stdout – обозначают, соответственно, стандартный ввод и вывод. Принимая это во внимание, мы увидим, что запись и чтение из файлов очень похожи на запись в стандартный вывод и чтение со стандартного входа. Для начала напишем очень простую программу, которая откры- вает файл с именем girlfriend.txt и печатает его на терминале. В этом
файле записаны слова лучшего хита Аврил Лавин, «Girlfriend». Вот содержимое girlfriend.txt: Эй! Ты! Эй! Ты! Мне не нравится твоя подружка! Однозначно! Однозначно! Думаю, тебе нужна другая! Программа: import System.IO main = do handle <– openFile "girlfriend.txt" ReadMode contents <– hGetContents handle putStr contents hClose handle Скомпилировав и запустив её, получаем ожидаемый результат: Эй! Ты! Эй! Ты! Мне не нравится твоя подружка! Однозначно! Однозначно! Думаю, тебе нужна другая! Посмотрим, что у нас тут? Первая строка – это просто четыре восклицания: они привлекают наше внимание. Во второй строке Аврил сообщает вам, что ей не нравится ваша подружка. Третья строка подчёркивает, что неприятие это категорическое. Ну а чет- вёртая предписывает подружиться с кем-нибудь получше. А теперь пройдемся по каждой строке кода. Наша программа – это несколько действий ввода-вывода, «склеенных» с помощью бло- ка do. В первой строке блока do мы использовали новую функцию, openFile. Вот её сигнатура: openFile:: FilePath –> IOMode –> IO Handle. Если попробовать это прочитать, получится следующее: «Функция openFile принимает путь к файлу и режим открытия файла (IOMode) и возвращает действие ввода-вывода, которое откроет файл, полу- чит дескриптор файла и заключит его в результат». Тип FilePath – это просто синоним для типа String; он опре- делён так: type FilePath = String
Тип IOMode определён так: data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode Этот тип содержит перечисление режимов открытия файла, так же как наш тип содержал перечисление дней недели. Очень просто! Обратите внимание, что этот тип – IOMode; не путайте его с IO Mode. Тип IO Mode может быть типом действия ввода-вывода, ко- торое возвращает результат типа Mode, но тип IOMode – это просто перечисление. В конце концов функция вернёт действие ввода-вывода, кото- рое откроет указанный файл в указанном режиме. Если мы привя- жем это действие к имени, то по- лучим дескриптор файла (Handle). Значение типа Handle описывает, где находится наш файл. Мы будем использовать дескриптор для того, чтобы знать, из какого файла чи- тать. Было бы глупо открыть файл и не связать дескриптор файла с именем, потому что с ним потом ничего нельзя будет сделать! В на- шем случае мы связали дескриптор с идентификатором handle. На следующей строке мы видим функцию hGetContents. Она прини- мает значение типа Handle; таким образом, она знает, с каким файлом работать, и возвращает значение типа IO String – действие ввода- вывода, которое вернёт содержимое файла в результате. Функция похожа на функцию getContents. Единственное отличие – функция getContents читает со стандартного входа (то есть с терминала), в то время как функция hGetContents принимает дескриптор файла, из которого будет происходить чтение. Во всех остальных смыслах они работают одинаково. Так же как и getContents, наша функция hGetContents не пытается прочитать весь файл целиком и сохра- нить его в памяти, но читает его по мере необходимости. Это очень удобно, поскольку мы можем считать, что идентификатор contents хранит всё содержимое файла, но на самом деле содержимого фай-
ла в памяти нет. Так что даже чтение из очень больших файлов не отожрёт всю память, но будет считывать только то, что нужно, и тогда, когда нужно. Обратите внимание на разницу между дескриптором, который используется для идентификации файла, и его содержимым. В на- шей программе они привязываются к именам handle и contents. Дескриптор – это нечто, с помощью чего мы знаем, что есть наш файл. Если представить всю файловую систему в виде очень боль- шой книги, а каждый файл в виде главы, то дескриптор будет чем-то вроде закладки, которая показывает нам, где мы в данный момент читаем (или пишем), в то время как идентификатор contents будет содержать саму главу. С помощью вызова putStr contents мы распечатываем содержи- мое на стандартном выводе, а затем выполняем функцию hClose, ко- торая принимает дескриптор и возвращает действие ввода-вывода, закрывающее файл. После открытия файла с помощью функции openFile вы должны закрывать файлы самостоятельно!
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2017-02-17; просмотров: 200; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.21.231.245 (0.003 с.) |