Помощь в написании студенческих работ
Антистрессовый сервис

Ввод-вывод. 
Компиляция программ на Haskell

РефератПомощь в написанииУзнать стоимостьмоей работы

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

Ввод-вывод. Компиляция программ на Haskell (реферат, курсовая, диплом, контрольная)

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

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

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

" putStr «Hello, world!» .

Hello, world!

Каков же тип результата этой функции? Что можно сделать с этим результатом? Запрос о типе функции к интерпретатору дает следующий результат:

>> :t putStr.

putStr: String -> 10 ().

Результатом является значение монадического типа 10, которое содержит в себе пустой кортеж, т. е. «пустое» значение. В целом, разумно, что результат у функции пустой, само по себе действие вывода результата не вычисляет, смысл вывода строки заключается в побочном эффекте работы функции. Однако это пустое значение еще и заключено в монаду 10. Поскольку в языке нет возможностей извлечь значение, заключенное в 10, то все, что в эту монаду попало, навсегда остается в ней. Тип данных 10 не имеет конструкторов значений, так что сопоставление с образцом, которое прекрасно работало в случае таких монад, как списки и значения типа Maybe, не работает в случае монады 10.

Если обратиться к решениям задачи 5.2 или задачи 6.2, приведенным в книге ранее, то можно увидеть, что в них для анализа значения типа Maybe использовались выражения вида case of

Just value -> …

Nothing -> …

Однако использовать ту же схему для анализа «результата» вычисления функции putStr не получится:

case putStr «Hello, world!» of

? ->

На месте вопросительного знака должен бы быть образец, содержащий конструктор типа данных 10, однако такого конструктора нет вообще. Нет и никаких специальных функций, подобных функции from Just для монады Maybe, которые могли бы «вытащить» значение из-под монады 10 в мир функционального программирования.

Еще ярче это свойство монады 10 держать в себе значение, не выпуская «в свободное плавание», проявляется при рассмотрении функций ввода.

Функция getLine вводит строку из консоли и выдает результат типа 10 String. Таким образом, введенная строка становится недоступной для непосредственной обработки как результат работы функции getLine, она будет «спрятана» в монаду 10 навсегда. Единственный способ использовать введенную строку — это преобразовывать получившуюся монаду. Например, если мы хотим добавить к введенной строке восклицательный знак, а потом вывести строку на консоль, то неправильным будет записать выражение putStr $ getLine ++ «!». Такое выражение синтаксически неверно из-за того, что аргументом функции putStr должна быть строка (String), а не монада (10 String). Но функция (s -> putStr (s ++ " ! «)) применима к строке и в качестве результата выдает монаду, так что для получения ожидаемого результата подойдет конструкция getLine «= (s -> putStr (s ++ «!»)).

Теперь попробуем написать чуть более сложную конструкцию. Мы хотим ввести с консоли изображения двух вещественных чисел и вывести на консоль их сумму. Ясно, что нужно дважды применить функцию getLine, результаты ее работы перевести в вещественные числа с помощью функции read, а затем вывести результат в виде строки. В результате последовательного применения функции связывания это будет выглядеть следующим образом: getLine >>=.

  • (х -> (getLine «=
  • (у -> putStr (show (read х + read у: Double)))))

Получилась не очень наглядная запись. Фактически нам нужно «временно» взять строки х и у из-под монады 10, выполнить с ними какие-то операции, но, поскольку «навсегда» выйти из мира монад 10 невозможно, то результаты обработки обязательно нужно «спрятать» обратно в эту монаду. В нашем примере такое «упрятывание» делает функция PutStr. Специально для этих случаев в Haskell предусмотрено удобное сокращение для последовательной обработки значений, извлеченных временно из монад при условии, что в результате опять получается монада того же типа. Эго конструкция do, которая не является чем-то вроде цикла, как можно было подумать тем, кто знаком с циклами do по другим языкам программирования. Приведенная выше запись может быть переформулирована с помощью do следующим образом: do х <- getLine у <- getLine.

putStr (show (read x + read у:: Double)).

Конструкция do — это просто функция, выдающая монаду. Часто она заканчивается применением функции return. Напомним, что функция return определена в классе Monad, и ее смысл состоит в том, чтобы «спрятать» заданное в качестве аргумента значение в монаду. Например, конструкция do х <- Just 5 у <- Just 10 return (х + у) выдает в качестве результата Just 15, а do х <- [1, 2].

У <- [11/ 12] return (х+у) выдает [12,13/13/14].

Возможно, такая запись напомнила вам еще одну похожую конструкцию с тем же результатом — генератор списков [ X + у I х <- [1,.

2], у <- [11, 12] ] — и это, конечно, не случайно. Генератор списков — это частный случай конструкции do для списковой монады.

В последовательности выражений, содержащихся внутри do, могут присутствовать выражения трех видов:

  • 1) конструкции вида образец <- монада;
  • 2) просто монады (результат ее вычисления игнорируется, если это не последнее выражение в списке выражений конструкции do);
  • 3) конструкции вида let образец = выражение.

Последним выражением в do должна быть монада, результат вычисления которой и принимается за результат всей конструкции. Конструкции вида let образец = выражение позволяют ввести локальные обозначения, не обязательно связанные с монадами. Например, имеет смысл следующее выражение:

do.

let х = 12 у <- [ 3.5].

return (х + у) Результатом его вычисления является значение [15,16,17]. Но конструкция do.

let х = 12 у <- [3.5].

putStr (show (х+у)).

неправильна, так как в двух последних предложениях в ней используются разные монады: значение, связываемое с идентификатором у, извлекается из монады-списка, а результат упаковывается в монаду 10.

В генераторах списков также можно использовать конструкцию let, например:

" [х + у | let х = 12, у <- [ 1.. 5] ].

[13,14,15,16,17).

Но в генераторах списков мы применяли еще и условия, так что, например, конструкция [(х, у) | х <- [10.15], у <- [7.9], х*у <= 100] выдавала список пар чисел из заданных списков [10. .15] и [7.. 9] при условии, что произведения чисел в паре не превосходят 100. Можно ли выразить то же самое с помощью конструкции do?

Перепишем наш генератор списка в виде конструкции do, поставив вместо условия пока неопределенную конструкцию, содержащую заданное логическое выражение: do.

х <- [10.15].

У <- [7.9].

?? (х*у <= 100).

return (х, у) Вспомним, что связывание в монаде, представленной списком, происходит путем конкатенации списков — результатов применения связывающей функции. Именно поэтому в нашем примере образующиеся пары чисел объединяются в единый список. Каким образом можно исключить те или иные пары из этого списка? Нужно сделать так, чтобы для значений пар, не удовлетворяющих условию х*у <= 100, результатом связывания оказывался пустой список (в терминах монад — значение, выдаваемое функцией fail). Если же условие удовлетворяется, то результатом может быть список, содержащий одно любое значение, это значение все равно не будет использоваться в дальнейшем, если только не извлекать его из монады с помощью конструкции (<-).

Итак, напишем следующую простую функцию-сторож, которая, получив логическое значение, будет выдавать в зависимости от этого значения либо пустой, либо непустой список: guard: Bool -> [Int] guard True = [0] guard False = [].

Теперь попробуем использовать написанную функцию в программе: do.

х <- [10.15].

У <" [7.9].

guard (х*у <= 100) return (х, у) Можете убедиться, что эта конструкция работает и выдает ровно тот же результат, что и приведенный выше генератор списка. Стоит отметить, что совсем не важно, каков тип элементов списка, используемый функцией guard. Это может быть, например, тип пустого кортежа (единственным значением этого типа является пустой кортеж), тогда определение функции guard примет вид guard: Bool -> [()] guard True = [ ()] guard False = [].

Похожая функция определена в модуле Control. Monad, она полиморфная, и ее можно использовать не только для списковых, но и для некоторых других монад. Например, для монады Maybe функция guard в случае ложного условия выдаст значение Nothing, что приведет к результату Nothing всей конструкции do.

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

Сначала займемся вводом строк. Это будет рекурсивная функция, в основе которой лежит стандартная функция ввода одной строки getLine. Поскольку результат работы getLine «спрятан» в монаду ввода/вывода, то и наша функция должна выдавать в качестве результата такую же монаду:

getLines:: IO [String] getLines = do х <- getLine.

if null x then return [] else getLines «= return, (x:).

В последнем выражении в определении этой функции имеется рекурсивный вызов getLines, который вернет монаду, содержащую список строк, начиная со второй. Операция связывания (>>=) образует новую монаду, добавляя первую строку в начало полученного списка строк.

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

putLines: [String] -> 10 ().

putLines [] = return ().

putLines (line:lines) = do

putStrLn line putLines lines.

Функцию приписывания номеров к списку строк можно описать во вполне функциональном стиле; никакие монады ввода/вывода здесь не нужны. Тогда останется только связать все определенные нами функции в единую последовательную программу, полный текст которой приведен в листинге 8.1.

Листинг 8.1. Программа нумерации введенных строк.

— Ввод последовательности строк.

getLines: 10 [String].

getLines = do

x <- getLine.

if null x then return [] else getLines «= return, (x:).

— Вывод последовательности строк putLines: [String] -> 10 0.

putLines [] = return ().

putLines (line:lines) = do putStrLn line putLines lines.

— Нумерация строк.

numLines: [String] -> [String].

numLines = zipWith (line -> show n ++ «» ++ line) [1.].

main = do

lines <- getLines putLines $ numLines lines.

Приведенная программа очень напоминает программы, написанные в «привычном» императивном стиле. Действительно, конструкция do вместе с применением монад реализует последовательное исполнение конструкций, почти как в традиционных языках программирования. Такую программу можно даже скомпилировать и исполнить как обычное приложение. Предполагается, что в программе, подготовленной для компиляции в приложении, имеется модуль, содержащий функцию main, которая имеет тип 10 (). Например, программа из одного модуля, приведенная на рис. 8.1, может быть откомпилирована и затем исполнена из командной строки как обычное приложение. В составе Haskell Platform имеется компилятор ghc, с помощью которого можно не только интерпретировать программы, как мы это делали до сих пор, но и получить объектный код модулей и затем собрать получившийся код в исполняемую программу.

Пусть наша программа ввода, нумерации и последующего вывода строк записана в текстовый файл GetLines.hs. Скомпилируем нашу программу. В операционной системе Windows окно командной строки может выглядеть при этом так, как показано на рис. 8.1.

Компиляция программы.

Рис. 8.1. Компиляция программы.

Теперь скомпилированную программу можно исполнить как обычное приложение. Например, можно ввести три строки и посмотреть, как они будут занумерованы. На рис. 8.2 представлен пример запуска нашей программы.

Запуск программы нумерации строк.

Рис. 8.2. Запуск программы нумерации строк.

Показать весь текст
Заполнить форму текущей работой