Рейтинговые книги
Читем онлайн Изучай Haskell во имя добра! - Миран Липовача

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 41 42 43 44 45 46 47 48 49 ... 96

                               [0..] todoTasks

   putStrLn "Ваши задания:"

   mapM_ putStrLn numberedTasks

   putStrLn "Что вы хотите удалить?"

   numberString <– getLine

   let number = read numberString

       newTodoItems = unlines $ delete (todoTasks !! number) todoTasks

   (tempName, tempHandle) <– openTempFile "." "temp"

   hPutStr tempHandle newTodoItems

   hClose tempHandle

   removeFile "todo.txt"

   renameFile tempName "todo.txt"

Сначала мы читаем содержимое файла todo.txt и связываем его с именем contents. Затем разбиваем всё содержимое на список строк. Список todoTasks выглядит примерно так:

["Погладить посуду", "Помыть собаку", "Вынуть салат из печи"]

Далее соединяем числа, начиная с 0, и элементы списка дел с помощью функции, которая берёт число (скажем, 3) и строку (например, "привет") и возвращает новую строку ("3 – привет"). Вот примерный вид списка numberedTasks:

["0 - Погладить посуду", "1 - Помыть собаку", "2 - Вынуть салат из печи"]

Затем с помощью вызова mapM_ putStrLn numberedTasks мы печатаем каждое задание на отдельной строке, после чего спрашиваем пользователя, что он хочет удалить, и ждём его ответа. Например, он хочет удалить задание 1 (Помыть собаку), так что мы получим число 1. Значением переменной numberString будет "1", и, поскольку вместо строки нам необходимо число, мы применяем функцию read и связываем результат с именем number.

Помните функции delete и !! из модуля Data.List? Оператор !! возвращает элемент из списка по индексу, функция delete удаляет первое вхождение элемента в список, возвращая новый список без удалённого элемента. Выражение (todoTasks !! number), где number – это 1, возвращает строку "Помыть собаку". Мы удаляем первое вхождение этой строки из списка todoTasks, собираем всё оставшееся в одну строку функцией unlines и даём результату имя newTodoItems.

Далее используем новую функцию из модуля System.IO – openTempFile. Имя функции говорит само за себя: open temp file – «открыть временный файл». Она принимает путь к временному каталогу и шаблон имени файла и открывает временный файл. Мы использовали символ . в качестве каталога для временных файлов, так как . обозначает текущий каталог практически во всех операционных системах. Строку "temp" мы указали в качестве шаблона имени для временного файла; это означает, что временный файл будет назван temp плюс несколько случайных символов. Функция возвращает действие ввода-вывода, которое создаст временный файл; результат действия – пара значений, имя временного файла и дескриптор. Мы могли бы открыть обычный файл, например с именем todo2.txt, но использовать openTempFile – хорошая практика: в этом случае не приходится опасаться, что вы случайно что-нибудь перезапишете.

Теперь, когда временный файл открыт, запишем туда строку newTodoItems. В этот момент исходный файл не изменён, а временный содержит все строки из исходного, за исключением удалённой.

Затем мы закрываем временный файл и удаляем исходный с помощью функции removeFile, которая принимает путь к файлу и удаляет его. После удаления старого файла todo.txt мы используем функцию renameFile, чтобы переименовать временный файл в todo.txt. Обратите внимание: функции removeFile и renameFile (обе они определены в модуле System.Directory) принимают в качестве параметров не дескрипторы, а пути к файлам.

Сохраните программу в файле с именем deletetodo.hs, скомпилируйте её и проверьте:

$ ./deletetodo

Ваши задания:

0 – Погладить посуду

1 – Помыть собаку

2 – Вынуть салат из печи

Что вы хотите удалить?

1

Смотрим, что осталось:

$ cat todo.txt

Погладить посуду

Вынуть салат из печи

Круто! Удалим ещё что-нибудь:

$ ./deletetodo

Ваши задания:

0 – Погладить посуду

1 – Вынуть салат из печи

Что вы хотите удалить?

0

Проверяя файл с заданиями, убеждаемся, что осталось только одно:

$ cat todo.txt

Вынуть салат из печи

Итак, всё работает. Осталась только одна вещь, которую мы в этой программе не учли. Если после открытия временного файла что-то произойдёт и программа неожиданно завершится, то временный файл не будет удалён. Давайте это исправим.

Уборка

Чтобы гарантировать удаление временного файла, воспользуемся функцией bracketOnError из модуля Control.Exception. Она очень похожа на bracket, но если последняя получает ресурс и гарантирует, что освобождение ресурса будет выполнено всегда, то функция bracketOnError выполнит завершающие действия только в случае возникновения исключения. Вот исправленный код:

import System.IO

import System.Directory

import Data.List

import Control.Exception

main = do

   contents <– readFile "todo.txt"

   let todoTasks = lines contents

       numberedTasks = zipWith (n line –> show n ++ " – " ++ line)

                               [0..] todoTasks

   putStrLn "Ваши задания:"

   mapM_ putStrLn numberedTasks

   putStrLn "Что вы хотите удалить?"

   numberString <– getLine

   let number = read numberString

       newTodoItems = unlines $ delete (todoTasks !! number) todoTasks

   bracketOnError (openTempFile "." "temp")

      ((tempName, tempHandle) –> do

            hClose tempHandle

            removeFile tempName)

      ((tempName, tempHandle) –> do

            hPutStr tempHandle newTodoItems

            hClose tempHandle

            removeFile "todo.txt"

            renameFile tempName "todo.txt")

Вместо обычного использования функции openTempFile мы заключаем её в bracketOnError. Затем пишем, что должно произойти при возникновении исключения: мы хотим закрыть и удалить временный файл. Если же всё нормально, пишем новый список заданий во временный файл; все эти строки остались без изменения. Мы выводим новые задания, удаляем исходный файл и переименовываем временный.

Аргументы командной строки

Если вы пишете консольный скрипт или приложение, то вам наверняка понадобится работать с аргументами командной строки. К счастью, в стандартную библиотеку языка Haskell входят удобные функции для работы с ними.

В предыдущей главе мы написали программы для добавления и удаления элемента в список заданий. Но у нашего подхода есть две проблемы. Во-первых, мы жёстко задали имя файла со списком заданий в тексте программы. Мы решили, что файл будет называться todo.txt, и что пользователь никогда не захочет вести несколько списков.

Эту проблему можно решить, спрашивая пользователя каждый раз, какой файл он хочет использовать как файл со списком заданий. Мы использовали такой подход, когда спрашивали пользователя, какой элемент он хочет удалить. Это, конечно, работает, но не идеально, поскольку пользователь должен запустить программу, подождать, пока она спросит что-нибудь, и затем дать ответ. Такая программа называется интерактивной, и сложность здесь заключается вот в чём: вдруг вам понадобится автоматизировать выполнение этой программы, например, с помощью скрипта? Гораздо сложнее написать скрипт, который будет взаимодействовать с программой, чем обычный скрипт, который просто вызовет её один или несколько раз!

Вот почему иногда лучше сделать так, чтобы пользователь сообщал, чего он хочет, при запуске программы, вместо того чтобы она сама спрашивала его после запуска. И что может послужить этой цели лучше командной строки!..

В модуле System.Environment есть два полезных действия ввода-вывода. Первое – это функция getArgs; её тип – getArgs :: IO [String]. Она получает аргументы, с которыми была вызвана программа, и возвращает их в виде списка. Второе – функция getProgName, тип которой – getProgName :: IO String. Это действие ввода-вывода, возвращающее имя программы.

Вот простенькая программа, которая показывает, как работают эти два действия:

import System.Environment

import Data.List

main = do

   args <– getArgs

   progName <– getProgName

   putStrLn "Аргументы командной строки:"

   mapM putStrLn args

   putStrLn "Имя программы:"

   putStrLn progName

Мы связываем значения, возвращаемые функциями getArgs и progName, с именами args и progName. Выводим строку "Аргументы командной строки:" и затем для каждого аргумента из списка args выполняем функцию putStrLn. После этого печатаем имя программы. Скомпилируем программу с именем arg-test и проверим, как она работает:

1 ... 41 42 43 44 45 46 47 48 49 ... 96
На этой странице вы можете бесплатно читать книгу Изучай Haskell во имя добра! - Миран Липовача бесплатно.
Похожие на Изучай Haskell во имя добра! - Миран Липовача книги

Оставить комментарий