Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программирование » Изучай Haskell во имя добра! - Миран Липовача

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

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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 42 43 44 45 46 47 48 49 50 ... 96
Перейти на страницу:

$ ./arg-test first second w00t "multi word arg"

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

first

second

w00t

multi word arg

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

arg-test

Ещё больше шалостей со списком дел

В предыдущих примерах мы писали отдельные программы для добавления и удаления заданий в списке дел. Теперь мы собираемся объединить их в новое приложение, а что ему делать, будем указывать в командной строке. Кроме того, позаботимся о том, чтобы программа смогла работать с разными файлами – не только todo.txt.

Назовём программу просто todo, она сможет делать три разные вещи:

• просматривать задания;

• добавлять задания;

• удалять задания.

Для добавления нового задания в список дел в файле todo.txt мы будем писать:

$ ./todo add todo.txt "Найти магический меч силы"

Просмотреть текущие задания можно будет командой view:

$ ./todo view todo.txt

Для удаления задания потребуется дополнительно указать его индекс:

$ ./todo remove todo.txt 2

Многозадачный список задач

Начнём с реализации функции, которая принимает команду в виде строки (например, "add" или "view") и возвращает функцию, которая в свою очередь принимает список аргументов и возвращает действие ввода-вывода, выполняющее в точности то, что необходимо:

import System.Environment

import System.Directory

import System.IO

import Data.List

import Control.Exception

dispatch :: String -> [String] –> IO ()

dispatch "add" = add

dispatch "view" = view

dispatch "remove" = remove

Функция main будет выглядеть так:

main = do

   (command:argList) <- getArgs

   dispatch command argList

Первым делом мы получаем аргументы и связываем их со списком (command:argsList). Таким образом, первый аргумент будет связан с именем command, а все остальные – со списком argList. В следующей строке к переменной commands применяется функция dispatch, результатом которой может быть одна из функций add, view или remove. Затем результирующая функция применяется к списку аргументов argList.

Предположим, программа запущена со следующими параметрами:

$ ./todo add todo.txt "Найти магический меч силы"

Тогда значением command будет "add", а значением argList – список ["todo.txt", "Найти магический меч силы"]. Поэтому сработает первый вариант определения функции dispatch и будет возвращена функция add. Применяем её к argList, результатом оказывается действие ввода-вывода, добавляющее новое задание в список.

Теперь давайте реализуем функции add, view и remove. Начнём с первой из них:

add :: [String] –> IO ()

add [fileName, todoItem] = appendFile fileName (todoItem ++ "n")

При вызове

$ ./todo add todo.txt "Найти магический меч силы"

функции add будет передан список ["todo.txt", "Найти магический меч силы"]. Поскольку пока мы не обрабатываем некорректный ввод, достаточно будет сопоставить аргумент функции add с двухэлементным списком. Результатом функции будет действие ввода-вывода, добавляющее строку вместе с символом конца строки в конец файла.

Далее реализуем функциональность просмотра списка. Если мы хотим просмотреть элементы списка, то вызываем программу так: todo view todo.txt. В первом сопоставлении с образцом идентификатор command будет связан со строкой view, а идентификатор argList будет равен ["todo.txt"].

Вот код функции view:

view :: [String] –> IO ()

view [fileName] = do

   contents <– readFile fileName

   let todoTasks = lines contents

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

                       [0..] todoTasks

   putStr $ unlines numberedTasks

Программа, которая удаляла задачу из списка, производила практически те же самые действия: мы отображали список задач, чтобы пользователь мог выбрать, какую из них удалить. Но в этой функции мы просто отображаем список.

Ну и наконец реализуем функцию remove. Функция будет очень похожа на программу для удаления элемента, так что если вы не понимаете, как работает функция удаления, прочитайте пояснения к её определению. Основное отличие – мы не задаём жёстко имя файла, а получаем его как аргумент. Также мы не спрашиваем у пользователя номер задачи для удаления – его мы также получаем в виде аргумента.

remove :: [String] -> IO ()

remove [fileName, numberString] = do

   contents <- readFile fileName

   let todoTasks = lines contents

       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 fileName

            renameFile tempName fileName)

Мы открываем файл, полное имя которого задаётся в идентификаторе fileName, открываем временный файл, удаляем строку по индексу, записываем во временный файл, удаляем исходный файл и переименовываем временный в fileName. Приведём полный листинг программы во всей её красе:

import System.Environment

import System.Directory

import System.IO

import Control.Exception

import Data.List

dispatch :: String -> [String] -> IO ()

dispatch "add" = add

dispatch "view" = view

dispatch "remove" = remove

main = do

   (command:argList) <- getArgs

   dispatch command argList

add :: [String] -> IO ()

add [fileName, todoItem] = appendFile fileName (todoItem ++ "n")

view :: [String] -> IO ()

view [fileName] = do

   contents <- readFile fileName

   let todoTasks = lines contents

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

                       [0..] todoTasks

   putStr $ unlines numberedTasks

remove :: [String] -> IO ()

remove [fileName, numberString] = do

   contents <- readFile fileName

   let todoTasks = lines contents

       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 fileName

            renameFile tempName fileName)

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

Давайте проверим, как наша программа работает:

$ ./todo view todo.txt

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

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

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

$ ./todo add todo.txt "Забрать детей из химчистки"

$ ./todo view todo.txt

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

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

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

3 – Забрать детей из химчистки

$ ./todo remove todo.txt 2

$ ./todo view todo.txt

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

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

2 – Забрать детей из химчистки

Большой плюс такого подхода – легко добавлять новую функциональность. Добавить вариант определения функции dispatch, реализовать соответствующую функцию – и готово! В качестве упражнения можете реализовать функцию bump, которая примет файл и номер задачи и вернёт действие ввода-вывода, которое поднимет указанную задачу на вершину списка задач.

Работаем с некорректным вводом

Можно было бы дописать эту программу, улучшив сообщения об ошибках, возникающих при некорректных исходных данных. Начать можно с добавления варианта функции dispatch, который срабатывает при любой несуществующей команде:

dispatch :: String -> [String] -> IO ()

dispatch "add" = add

dispatch "view" = view

dispatch "remove" = remove

dispatch command = doesntExist command

doesntExist :: String -> [String] -> IO ()

doesntExist command _ =

   putStrLn $ "Команда " ++ command ++ " не определена"

Также можно добавить варианты определения функций add, view и remove для случаев, когда программе передано неправильное количество аргументов. Например:

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