ТОП 10:

Использование ключевого слова let внутри блока do



 
 

Помните связывания при помощи ключевого слова let? Если уже подзабыли, освежите свои знания. Связывания должны быть тако- го вида: let <определения> in <выражение>, где <определения> – это име- на, даваемые выражениям, а <выражение> использует имена из <опре- делений>. Также мы говорили, что в списковых выражениях часть in не нужна. Так вот, в блоках do можно использовать выражение let таким же образом, как и в списковых выражениях. Смотрите:

import Data.Char

 

main = do

putStrLn "Ваше имя?" firstName <– getLine putStrLn "Ваша фамилия?" lastName <– getLine

let bigFirstName = map toUpper firstName bigLastName = map toUpper lastName

putStrLn $ "Привет, " ++ bigFirstName ++ " "

++ bigLastName

 
 

++ ", как дела?"

Видите, как выровнены операторы действий ввода-вывода в блоке do? Обратите внимание и на то, как выровнено выраже- ние let по отношению к действиям ввода-вывода и как выровнены образцы внутри выражения let. Это хороший пример, потому что выравнивание текста очень важно в языке Haskell. Далее мы запи- сали вызов map toUpper firstName, что превратит, например, "Иван" в намного более солидное "ИВАН". Мы связали эту строку в верхнем регистре с именем, которое использовали в дальнейшем при выво- де на терминал.


 

Вам может быть непонятно, когда использовать символ <–, а ког- да выражение let. Запомните: символ <– (в случае действий ввода- вывода) используется для выполнения действий ввода-вывода и свя- зывания результатов с именами. Выражение map toUpper firstName не является действием ввода-вывода – это чистое выражение. Со- ответственно, используйте символ <– для связывания результатов действий ввода-вывода с именами, а выражение let – для связыва- ния имён с чистыми значениями. Если бы мы выполнили что-то вроде let firstName = getLine, то просто создали бы синоним функ- ции getLine, для которого значение всё равно должно получаться с помощью символа <–.

 

Обращение строк

 
 

Теперь напишем программу, которая будет считывать строки, пере- ставлять в обратном порядке буквы в словах и распечатывать их. Вы- полнение программы прекращается при вводе пустой строки. Итак:

main = do

line <– getLine if null line

then return () else do

putStrLn $ reverseWords line main

 

 
 

reverseWords :: String –> String reverseWords = unwords . map reverse . words

Чтобы лучше понять, как работает программа, сохраните её в файле reverse.hs, скомпилируйте и запустите:

$ ghc reverse.hs

[1 of 1] Compiling Main ( reverse.hs, reverse.o ) Linking reverse ...

$ ./reverse

уберитесь в проходе номер 9 ьсетиребу в едохорп ремон 9 козёл ошибки осветит твою жизнь лёзок икбишо титевсо юовт ьнзиж но это всё мечты

 
 

он отэ ёсв ытчем


 

Для начала посмотрим на функцию reverseWords. Это обыч- ная функция, которая принимает строку, например "эй ты мужик", и вызывает функцию words, чтобы получить список слов ["эй", "ты","мужик"]. Затем мы применяем функцию reverse к каждому элементу списка, получаем ["йэ","ыт","кижум"] и помещаем ре- зультат обратно в строку, используя функцию unwords. Конечным результатом будет "йэ ыт кижум".

Теперь посмотрим на функцию main. Сначала мы получаем стро- ку с терминала с помощью функции getLine. Далее у нас имеется условное выражение. Запомните, что в языке Haskell каждое клю- чевое слово if должно сопровождаться секцией else, так как каж- дое выражение должно иметь некоторое значение. Наш оператор записан так, что если условие истинно (в нашем случае – когда вве- дут пустую строку), мы выполним одно действие ввода-вывода; если оно ложно – выполним действие ввода-вывода из секции else. По той же причине в блоке do условные операторы if должны иметь вид if <условие> then <действие ввода-вывода> else <действие ввода- вывода>.

 
 

Вначале посмотрим, что делается в секции else. Поскольку можно поместить только одно действие ввода-вывода после клю- чевого слова else, мы используем блок do для того, чтобы «склеить» несколько операторов в один. Эту часть можно было бы написать так:

else (do

 
 

putStrLn $ reverseWords line main)

Подобная запись явно показывает, что блок do может рассмат- риваться как одно действие ввода-вывода, но и выглядит она не очень красиво. В любом случае внутри блока do мы можем вызвать функцию reverseWords со строкой – результатом действия getLine и распечатать результат. После этого мы выполняем функцию main. Получается, что функция main вызывается рекурсивно, и в этом нет ничего необычного, так как сама по себе функция main – тоже действие ввода-вывода. Таким образом, мы возвращаемся к началу программы в следующей рекурсивной итерации.

Ну а что случится, если мы получим на вход пустую строку? В этом случае выполнится часть после ключевого слова then. То есть выпол- нится выражение return (). Если вам приходилось писать на импера-


 

тивных языках вроде C, Java или на Python, вы наверняка уверены, что знаете, как работает функция return – и, возможно, у вас воз- никнет искушение пропустить эту часть текста. Но не стоит спе- шить: функция return в языке Haskell работает совершенно не так, как в большинстве других языков! Её название сбивает с толку, но на самом деле она довольно сильно отличается от своих «тёзок». В императивных языках ключевое слово return обычно прекращает выполнение метода или процедуры и возвращает некоторое зна- чение вызывающему коду. В языке Haskell (и особенно в действиях ввода-вывода) одноимённая функция создаёт действие ввода-выво- да из чистого значения. Если продолжать аналогию с коробками, она берёт значение и помещает его в «коробочку». Получившееся в результате действие ввода-вывода на самом деле не выполняет ни- каких действий – оно просто инкапсулирует некоторое значение. Таким образом, в контексте системы ввода-вывода return "ха-ха" будет иметь тип IO String. Какой смысл преобразовывать чистое значение в действие ввода-вывода, которое ничего не делает? За- чем «пачкать» нашу программу больше необходимого? Нам нужно некоторое действие ввода-вывода для второй части условного опе- ратора, чтобы обработать случай пустой строки. Вот для чего мы создали фиктивное действие ввода-вывода, которое ничего не де- лает, записав return ().

 
 

Вызов функции return не прекращает выполнение блока do – ничего подобного! Например, следующая программа успешно вы- полнится вся до последней строчки:

main = do

return ()

return "ХА-ХА-ХА" line <– getLine return "ЛЯ-ЛЯ-ЛЯ" return 4

 
 

putStrLn line

Всё, что делает функция return, – создаёт действия ввода-выво- да, которые не делают ничего, кроме как содержат значения, и все они отбрасываются, поскольку не привязаны к образцам. Мы мо- жем использовать функцию return вместе с символом <– для того, чтобы связывать значения с образцами.


 

main = do

let a = "ад"

b = "да!"

 
 

putStrLn $ a ++ " " ++ b

Как вы можете видеть, функция return выполняет обратную операцию по отношению к операции <–. В то время как функция return принимает значение и помещает его в «коробку», операция <– принимает (и исполняет) «коробку», а затем привязывает получен- ное из неё значение к имени. Но всё это выглядит лишним, так как в блоках do можно использовать выражение let для привязки к име- нам, например так:

main = do

let a = "hell" b = "yeah"

 
 

putStrLn $ a ++ " " ++ b

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

 







Последнее изменение этой страницы: 2017-02-17; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 34.201.9.19 (0.009 с.)