Kniga-Online.club
» » » » Миран Липовача - Изучай Haskell во имя добра!

Миран Липовача - Изучай Haskell во имя добра!

Читать бесплатно Миран Липовача - Изучай Haskell во имя добра!. Жанр: Программирование издательство -, год 2004. Так же читаем полные версии (весь текст) онлайн без регистрации и SMS на сайте kniga-online.club или прочесть краткое содержание, предисловие (аннотацию), описание и ознакомиться с отзывами (комментариями) о произведении.
Перейти на страницу:

Я короткая

И я

Коротенькая

Короткая

Видно, что на терминал выведены только короткие строки.

Преобразование входного потока

Подобная последовательность действий – считывание строки из потока ввода, преобразование её функцией и вывод результата – настолько часто встречается, что существует функция, которая делает эту задачу ещё легче; она называется 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 будет содержать саму главу.

Перейти на страницу:

Миран Липовача читать все книги автора по порядку

Миран Липовача - все книги автора в одном месте читать по порядку полные версии на сайте онлайн библиотеки kniga-online.club.


Изучай Haskell во имя добра! отзывы

Отзывы читателей о книге Изучай Haskell во имя добра!, автор: Миран Липовача. Читайте комментарии и мнения людей о произведении.


Уважаемые читатели и просто посетители нашей библиотеки! Просим Вас придерживаться определенных правил при комментировании литературных произведений.

  • 1. Просьба отказаться от дискриминационных высказываний. Мы защищаем право наших читателей свободно выражать свою точку зрения. Вместе с тем мы не терпим агрессии. На сайте запрещено оставлять комментарий, который содержит унизительные высказывания или призывы к насилию по отношению к отдельным лицам или группам людей на основании их расы, этнического происхождения, вероисповедания, недееспособности, пола, возраста, статуса ветерана, касты или сексуальной ориентации.
  • 2. Просьба отказаться от оскорблений, угроз и запугиваний.
  • 3. Просьба отказаться от нецензурной лексики.
  • 4. Просьба вести себя максимально корректно как по отношению к авторам, так и по отношению к другим читателям и их комментариям.

Надеемся на Ваше понимание и благоразумие. С уважением, администратор kniga-online.


Прокомментировать
Подтвердите что вы не робот:*
Подтвердите что вы не робот:*