2014-05-06 14:58:45 +04:00
|
|
|
|
---
|
2014-10-13 01:24:42 +04:00
|
|
|
|
language: Haskell
|
2017-08-25 11:20:27 +03:00
|
|
|
|
filename: haskell-ru.hs
|
2014-05-06 14:58:45 +04:00
|
|
|
|
contributors:
|
|
|
|
|
- ["Adit Bhargava", "http://adit.io"]
|
2014-05-06 20:48:13 +04:00
|
|
|
|
translators:
|
2014-05-06 14:58:45 +04:00
|
|
|
|
- ["Aleksey Pirogov", "http://astynax.github.io"]
|
2014-05-06 20:48:13 +04:00
|
|
|
|
lang: ru-ru
|
2014-05-06 14:58:45 +04:00
|
|
|
|
---
|
|
|
|
|
|
2022-06-15 12:17:20 +03:00
|
|
|
|
Haskell разрабатывался, как чистый функциональный язык программирования, применимый на практике. Язык известен благодаря своей системе типов, и "знаменит" благодаря монадам. [Меня][author] же Haskell заставляет возвращаться к себе снова и снова именно своей элегантностью и [я][author] получаю истинное удовольствие, программируя на Haskell.
|
2014-05-06 14:58:45 +04:00
|
|
|
|
|
|
|
|
|
```haskell
|
|
|
|
|
-- Однострочные комментарии начинаются с двух дефисов
|
|
|
|
|
{- Многострочный комментарий
|
|
|
|
|
заключается в пару фигурных скобок с дефисами с внутренней стороны.
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-------------------------------------------------------
|
|
|
|
|
-- 1. Примитивные типы и простейшие операции над ними
|
|
|
|
|
-------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
-- Числа объявляются просто
|
|
|
|
|
3 -- 3
|
|
|
|
|
|
|
|
|
|
-- Арифметика тоже выглядит вполне ожидаемо
|
|
|
|
|
1 + 1 -- 2
|
|
|
|
|
8 - 1 -- 7
|
|
|
|
|
10 * 2 -- 20
|
|
|
|
|
35 / 5 -- 7.0
|
|
|
|
|
|
|
|
|
|
-- Операция деления всегда возвращает действительное число
|
|
|
|
|
35 / 4 -- 8.75
|
|
|
|
|
|
|
|
|
|
-- Делим нацело так
|
|
|
|
|
35 `div` 4 -- 8
|
|
|
|
|
|
|
|
|
|
-- Булевы значения - тоже примитивные значения
|
|
|
|
|
True
|
|
|
|
|
False
|
|
|
|
|
|
|
|
|
|
-- Булева алгебра
|
|
|
|
|
not True -- False
|
|
|
|
|
not False -- True
|
|
|
|
|
1 == 1 -- True
|
|
|
|
|
1 /= 1 -- False
|
|
|
|
|
1 < 10 -- True
|
|
|
|
|
|
|
|
|
|
-- В примере выше `not`, это функция, принимающая один аргумент.
|
|
|
|
|
-- При вызове функции в Haskell список аргументов
|
|
|
|
|
-- не нужно заключать в скобки - аргументы просто
|
|
|
|
|
-- перечисляются через пробелы сразу после имени функции.
|
|
|
|
|
-- Т.о. типичный вызов выглядит так:
|
|
|
|
|
-- func arg1 arg2 arg3...
|
|
|
|
|
-- Ниже же будет показано, как определять свои функции.
|
|
|
|
|
|
|
|
|
|
-- Строки и символы
|
|
|
|
|
"Это строка."
|
|
|
|
|
'ы' -- а это символ
|
|
|
|
|
'Нельзя заключать длинные строки в одинарные кавычки.' -- ошибка!
|
|
|
|
|
|
|
|
|
|
-- Строки можно конкатенировать
|
|
|
|
|
"Привет" ++ ", Мир!" -- "Привет, Мир!"
|
|
|
|
|
|
|
|
|
|
-- При этом строки - это просто списки символов!
|
|
|
|
|
"Я - строка!" !! 0 -- 'Я'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
2017-04-01 23:19:58 +03:00
|
|
|
|
-- 2. Списки и Кортежи
|
2014-05-06 14:58:45 +04:00
|
|
|
|
----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
-- Все элементы списка в Haskell
|
|
|
|
|
-- должны иметь один и тот же тип.
|
|
|
|
|
|
|
|
|
|
-- Эти два списка - эквивалентны:
|
|
|
|
|
[1, 2, 3, 4, 5]
|
|
|
|
|
[1..5]
|
|
|
|
|
|
|
|
|
|
-- Haskell позволяет определять даже бесконечные списки!
|
|
|
|
|
[1..] -- список всех натуральных чисел!
|
|
|
|
|
|
|
|
|
|
-- Бесконечные списки возможно в Haskell потому, что он "ленив".
|
|
|
|
|
-- В Haskell все вычисления производятся тогда и только тогда,
|
|
|
|
|
-- когда их результат потребуется.
|
|
|
|
|
-- Эта стратегия так и называется - "lazy evaluation".
|
|
|
|
|
-- Скажем, если вам нужен тысячный элемент из
|
|
|
|
|
-- списка натуральных чисел (бесконечного) и вы напишете так:
|
|
|
|
|
|
|
|
|
|
[1..] !! 999 -- 1000
|
|
|
|
|
|
|
|
|
|
-- То Haskell вычислит элементы этого списка от 1 до 1000...
|
|
|
|
|
-- ... и остановится, ведь последующие элементы пока не нужны.
|
|
|
|
|
-- Это значит, что остальные элементы нашего
|
|
|
|
|
-- "бесконечного" списка не будут вычисляться! По крайней мере,
|
|
|
|
|
-- пока не понадобятся и они.
|
|
|
|
|
|
|
|
|
|
-- Списки можно объединять
|
|
|
|
|
[1..5] ++ [6..10]
|
|
|
|
|
|
|
|
|
|
-- И добавлять значения в начало
|
|
|
|
|
0:[1..5] -- [0, 1, 2, 3, 4, 5]
|
|
|
|
|
|
|
|
|
|
-- А можно обратиться по индексу
|
|
|
|
|
[0..] !! 5 -- 5
|
|
|
|
|
|
|
|
|
|
-- Вот ещё несколько функций, часто используемых со списками
|
|
|
|
|
head [1..5] -- 1
|
|
|
|
|
tail [1..5] -- [2, 3, 4, 5]
|
|
|
|
|
init [1..5] -- [1, 2, 3, 4]
|
|
|
|
|
last [1..5] -- 5
|
|
|
|
|
|
|
|
|
|
-- list comprehensions - "формулы" для описания списков
|
|
|
|
|
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]
|
|
|
|
|
|
|
|
|
|
-- можно указать условие попадания элементов в список
|
|
|
|
|
[x*2 | x <- [1..5], x*2 > 4] -- [6, 8, 10]
|
|
|
|
|
|
|
|
|
|
-- Списки могут даже состоять из других списков
|
|
|
|
|
[[1,2,3],[4,5,6]] !! 1 !! 2 -- 6 (вторая строка, третий столбец)
|
|
|
|
|
|
|
|
|
|
-- Кортежи позволяют своим элементам иметь различные типы,
|
|
|
|
|
-- но при этом кортежи имеют фиксированную длину.
|
|
|
|
|
-- Кортеж:
|
|
|
|
|
("haskell", 1)
|
|
|
|
|
|
|
|
|
|
-- Часто кортежи из двух элементов называются "парами".
|
|
|
|
|
-- Элементы пары можно получать так:
|
|
|
|
|
fst ("haskell", 1) -- "haskell"
|
|
|
|
|
snd ("haskell", 1) -- 1
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- 3. Функции
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- Простая функция, принимающая два аргумента
|
|
|
|
|
add a b = a + b
|
|
|
|
|
|
|
|
|
|
-- Внимание!
|
|
|
|
|
-- Если вы используете ghci (интерактивный интерпретатор Haskell),
|
|
|
|
|
-- вам нужно использовать ключевое слово `let`, примерно так:
|
|
|
|
|
-- let add a b = a + b
|
|
|
|
|
|
|
|
|
|
-- Вызовем нашу функцию
|
|
|
|
|
add 1 2 -- 3
|
|
|
|
|
|
|
|
|
|
-- Функцию можно поместить между первым и вторым аргументами,
|
|
|
|
|
-- если заключить её имя в обратные кавычки
|
|
|
|
|
1 `add` 2 -- 3
|
|
|
|
|
|
|
|
|
|
{- Вы можете также определять функции, имя которых
|
|
|
|
|
вообще не содержит букв! Таки функции и называются "операторами",
|
|
|
|
|
и, да, вы можете определять свои операторы!
|
|
|
|
|
Скажем, оператор целочисленного деления можно определить так -}
|
|
|
|
|
(//) a b = a `div` b
|
|
|
|
|
35 // 4 -- 8
|
|
|
|
|
{- Здесь оператор заключен в скобки - как говорят,
|
|
|
|
|
поставлен в префиксную позицию.
|
|
|
|
|
В префиксной позиции оператор можно не только определять,
|
|
|
|
|
но и вызывать -}
|
|
|
|
|
(+) 1 2 -- 3
|
|
|
|
|
|
|
|
|
|
-- Охранные выражения (guards) порой удобны,
|
|
|
|
|
-- если наша функция ветвится
|
|
|
|
|
fib x
|
|
|
|
|
| x < 2 = x
|
|
|
|
|
| otherwise = fib (x - 1) + fib (x - 2)
|
|
|
|
|
|
|
|
|
|
{- Сопоставление с образцом (pattern matching)
|
|
|
|
|
чем-то напоминает охранные выражения.
|
|
|
|
|
Здесь мы видим три определения функции fib.
|
|
|
|
|
При вызове функции по имени Haskell использует
|
|
|
|
|
первое определение, к образцу которого
|
|
|
|
|
"подойдет" набор аргументов -}
|
|
|
|
|
fib 1 = 1
|
2024-03-11 14:42:55 +03:00
|
|
|
|
fib 2 = 1
|
2014-05-06 14:58:45 +04:00
|
|
|
|
fib x = fib (x - 1) + fib (x - 2)
|
|
|
|
|
|
|
|
|
|
-- Pattern matching для кортежей выглядит так
|
|
|
|
|
foo (x, y) = (x + 1, y + 2)
|
|
|
|
|
|
|
|
|
|
{- Pattern matching для списков устроен чуть сложнее.
|
|
|
|
|
Пусть `x` - первый элемент списка, а `xs` - остальные элементы.
|
|
|
|
|
Тогда операции `head` и `tail` могут быть определены так -}
|
|
|
|
|
myHead (x:xs) = x
|
|
|
|
|
myTail (x:xs) = xs
|
|
|
|
|
|
|
|
|
|
-- Функцию отображения мы можем написать так
|
|
|
|
|
myMap func [] = []
|
|
|
|
|
myMap func (x:xs) = func x:(myMap func xs)
|
|
|
|
|
|
|
|
|
|
-- При сопоставлении происходит привязка
|
|
|
|
|
-- элементов значения с именами в образце
|
|
|
|
|
fstPlusThird (a : _ : b : _) = a + b
|
|
|
|
|
fstPlusThird [1,2,3,4,5] -- 4
|
|
|
|
|
-- Значения, для которых вместо имени указано `_`,
|
|
|
|
|
-- игнорируются. Это удобно, когда важен сам факт
|
|
|
|
|
-- совпадения образца
|
|
|
|
|
oneElem [_] = True
|
|
|
|
|
oneElem _ = False
|
|
|
|
|
|
|
|
|
|
startsWith x (y:_) = x == y
|
|
|
|
|
startsWith _ _ = False
|
|
|
|
|
|
|
|
|
|
startsWith 'H' "Hello!" -- True
|
|
|
|
|
startsWith 'H' "hello!" -- False
|
|
|
|
|
|
|
|
|
|
{- Обратите внимание на тот факт,
|
|
|
|
|
что первый аргумент нашей функции `myMap` - тоже функция!
|
|
|
|
|
Функции, подобно `myMap`, принимающие другие функции
|
|
|
|
|
в качестве параметров, или, скажем, возвращающие функции
|
|
|
|
|
в качестве результата, называются
|
|
|
|
|
Функциями Высших Порядков (ФВП, High Order Functions, HOF)
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-- Вместе с ФВП часто используются анонимные функции
|
|
|
|
|
myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]
|
|
|
|
|
-- Такие функции описываются в виде
|
|
|
|
|
-- \arg1 arg1 .. -> expression
|
|
|
|
|
|
|
|
|
|
-- Популярные в других языках ФВП присутствуют и в Haskell
|
|
|
|
|
map (\x -> x * 10) [1..5] -- [10, 20, 30, 40, 50]
|
|
|
|
|
filter (\x -> x > 2) [1..5] -- [3, 4, 5]
|
|
|
|
|
|
|
|
|
|
{- Функция свертки
|
|
|
|
|
(она же `reduce` или `inject` в других языках)
|
|
|
|
|
в Haskell представлены функциями `foldr` и `foldl`.
|
|
|
|
|
Суть свертки можно представить так:
|
|
|
|
|
|
|
|
|
|
foldl f x0 [x1,x2,x3] -> (f (f (f x0 x1) x2) x3)
|
|
|
|
|
foldr f x0 [x1,x2,x3] -> (f x1 (f x2 (f x3 x0)))
|
|
|
|
|
|
|
|
|
|
Здесь x0 - начальное значения так называемого "аккумулятора"
|
|
|
|
|
-}
|
|
|
|
|
-- Эти два вызова дают одинаковый результат
|
|
|
|
|
foldr (\x acc -> acc + x) 0 [1..5] -- 15
|
|
|
|
|
foldl (\acc x -> acc + x) 0 [1..5] -- 15
|
|
|
|
|
-- Тут можно даже заменить анонимную функцию на оператор
|
|
|
|
|
foldr (+) 0 [1..5] -- 15
|
|
|
|
|
foldl (+) 0 [1..5] -- 15
|
|
|
|
|
|
|
|
|
|
-- Зато здесь разница видна
|
|
|
|
|
foldr (\x acc -> (x + 10) : acc) [] [1..3] -- [11, 12, 13]
|
|
|
|
|
foldl (\acc x -> (x + 10) : acc) [] [1..3] -- [13, 12, 11]
|
|
|
|
|
|
|
|
|
|
{- Часто в качестве начального значения
|
|
|
|
|
удобно брать крайнее значение списка (крайнее слева или справа).
|
|
|
|
|
Для этого есть пара функций - `foldr1` и `foldl1` -}
|
|
|
|
|
foldr1 (+) [1..5] -- 15
|
|
|
|
|
foldl1 (+) [1..5] -- 15
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- 4. Больше о функциях
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
{- Каррирование (currying)
|
|
|
|
|
Если в Haskell при вызове функции передать не все аргументы,
|
|
|
|
|
Функция становится "каррированой" - результатом вызова станет
|
|
|
|
|
новая функция, которая при вызове и примет оставшиеся аргументы -}
|
|
|
|
|
|
|
|
|
|
add a b = a + b
|
|
|
|
|
foo = add 10 -- теперь foo будет принимать число
|
|
|
|
|
-- и добавлять к нему 10
|
|
|
|
|
foo 5 -- 15
|
|
|
|
|
|
|
|
|
|
-- Для операторов можно "опустить" любой из двух аргументов
|
|
|
|
|
-- Используя этот факт можно определить
|
|
|
|
|
-- функцию `foo` из кода выше несколько иначе
|
|
|
|
|
foo = (+10)
|
|
|
|
|
foo 5 -- 15
|
|
|
|
|
|
|
|
|
|
-- Поупражняемся
|
|
|
|
|
map (10-) [1..3] -- [9, 8, 7]
|
|
|
|
|
filter (<5) [1..10] -- [1, 2, 3, 4]
|
|
|
|
|
|
|
|
|
|
{- Композиция функций
|
|
|
|
|
Функция (.) соединяет пару функций в цепочку.
|
|
|
|
|
К примеру, можно соединить функцию, добавляющую 10,
|
|
|
|
|
с функцией, умножающей на 5 -}
|
|
|
|
|
foo = (*5) . (+10)
|
|
|
|
|
|
|
|
|
|
-- (5 + 10) * 5 = 75
|
|
|
|
|
foo 5 -- 75
|
|
|
|
|
|
|
|
|
|
{- Управление приоритетом вычисления
|
|
|
|
|
В Haskell есть функция `$`, которая применяет
|
|
|
|
|
свой первый аргумент ко второму с наименьшим приоритетом
|
|
|
|
|
(обычное применение функций имеет наивысший приоритет)
|
|
|
|
|
Эта функция часто позволяет избежать использования
|
|
|
|
|
"лишних" скобок -}
|
|
|
|
|
head (tail (tail "abcd")) -- 'c'
|
|
|
|
|
head $ tail $ tail "abcd" -- 'c'
|
|
|
|
|
-- того же эффекта иногда можно достичь использованием композиции
|
|
|
|
|
(head . tail . tail) "abcd" -- 'c'
|
|
|
|
|
head . tail . tail $ "abcd" -- 'c'
|
|
|
|
|
{- Тут стоит сразу запомнить, что композиция функций
|
|
|
|
|
возвращает именно новую функцию, как в последнем примере.
|
|
|
|
|
Т.е. можно делать так -}
|
|
|
|
|
third = head . tail . tail
|
|
|
|
|
-- но не так
|
|
|
|
|
third = head $ tail $ tail -- (head (tail (tail))) - ошибка!
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- 5. Сигнатуры типов
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
{- Haskell обладает очень сильной системой типов.
|
|
|
|
|
И типизация в Haskell - строгая. Каждое выражение имеет тип,
|
|
|
|
|
который может быть описан сигнатурой.
|
|
|
|
|
Сигнатура записывается в форме
|
|
|
|
|
expression :: type signature
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-- Типы примитивов
|
|
|
|
|
5 :: Integer
|
|
|
|
|
"hello" :: String
|
|
|
|
|
True :: Bool
|
|
|
|
|
|
|
|
|
|
{- Функции тоже имеют тип
|
|
|
|
|
`not` принимает булево значение и возвращает булев результат
|
|
|
|
|
not :: Bool -> Bool
|
|
|
|
|
|
|
|
|
|
Вот функция двух аргументов
|
|
|
|
|
add :: Integer -> Integer -> Integer
|
|
|
|
|
|
|
|
|
|
Тут то мы и видим предпосылки к каррированию: тип
|
|
|
|
|
на самом деле выглядит так (скобки просто обычно опускаются)
|
|
|
|
|
add :: (Integer -> Integer) -> Integer
|
|
|
|
|
т.е. функция принимает аргумент,
|
|
|
|
|
и возвращает функцию от второго аргумента! -}
|
|
|
|
|
|
|
|
|
|
-- Считается хорошим тоном указывать сигнатуру определений,
|
|
|
|
|
-- которые доступны другим разработчикам (публичны). Пример:
|
|
|
|
|
double :: Integer -> Integer
|
|
|
|
|
double x = x * 2
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- 6. Управление потоком исполнения
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
-- Выражение `if`
|
|
|
|
|
haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"
|
|
|
|
|
|
|
|
|
|
-- Выражение `if` можно записать и в несколько строк.
|
|
|
|
|
-- Соблюдайте отступы!
|
|
|
|
|
haskell = if 1 == 1
|
|
|
|
|
then "awesome"
|
|
|
|
|
else "awful"
|
|
|
|
|
|
|
|
|
|
-- Так как `if` - выражение, ветка `else` обязательна!
|
|
|
|
|
-- И более того, результаты выражений в ветках `then` и `else`
|
|
|
|
|
-- должны иметь одинаковый тип!
|
|
|
|
|
|
|
|
|
|
-- `case`-выражение выглядит так
|
|
|
|
|
case args of -- парсим аргументы командной строки
|
|
|
|
|
"help" -> printHelp
|
|
|
|
|
"start" -> startProgram
|
|
|
|
|
_ -> putStrLn "bad args"
|
|
|
|
|
|
|
|
|
|
-- При вычислении результата `case`-выражения производится
|
|
|
|
|
-- сопоставление с образцом:
|
|
|
|
|
fib x = case x of
|
|
|
|
|
1 -> 1
|
|
|
|
|
2 -> 1
|
|
|
|
|
_ -> fib (x - 1) + fib (x - 2)
|
|
|
|
|
|
|
|
|
|
-- В Haskell нет циклов - вместо них используются рекурсия,
|
|
|
|
|
-- отображение, фильтрация и свертка (map/filter/fold)
|
|
|
|
|
map (*2) [1..5] -- [2, 4, 6, 8, 10]
|
|
|
|
|
|
|
|
|
|
for array func = map func array
|
|
|
|
|
for [0..3] $ \i -> show i -- ["0", "1", "2", "3"]
|
|
|
|
|
for [0..3] show -- ["0", "1", "2", "3"]
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- 7. Пользовательские типы данных
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
-- Создадим свой Haskell-тип данных
|
|
|
|
|
|
|
|
|
|
data Color = Red | Blue | Green
|
|
|
|
|
|
|
|
|
|
-- Попробуем использовать
|
|
|
|
|
|
|
|
|
|
say :: Color -> String
|
|
|
|
|
say Red = "You are Red!"
|
|
|
|
|
say Blue = "You are Blue!"
|
|
|
|
|
say Green = "You are Green!"
|
|
|
|
|
|
|
|
|
|
-- Типы могут иметь параметры (параметры типов)
|
|
|
|
|
|
|
|
|
|
data Maybe a = Nothing | Just a
|
|
|
|
|
|
|
|
|
|
-- Все эти выражения имеют тип `Maybe`
|
|
|
|
|
Just "hello" -- :: `Maybe String`
|
|
|
|
|
Just 1 -- :: `Maybe Int`
|
|
|
|
|
Nothing -- :: `Maybe a` для любого `a`
|
|
|
|
|
|
|
|
|
|
-- Типы могут быть достаточно сложными
|
|
|
|
|
data Figure = Rectangle (Int, Int) Int Int
|
|
|
|
|
| Square (Int, Int) Int
|
|
|
|
|
| Point (Int, Int)
|
|
|
|
|
|
|
|
|
|
area :: Figure -> Int
|
|
|
|
|
area (Point _) = 0
|
|
|
|
|
area (Square _ s) = s * s
|
|
|
|
|
area (Rectangle _ w h) = w * h
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- 8. Ввод-вывод в Haskell
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
-- Полноценно объяснить тему ввода-вывода невозможно
|
|
|
|
|
-- без объяснения монад, но для использования в простых случаях
|
|
|
|
|
-- вводного описания будет достаточно.
|
|
|
|
|
|
|
|
|
|
-- Когда программа на Haskell выполняется,
|
|
|
|
|
-- вызывается функция с именем `main`.
|
|
|
|
|
-- Эта функция должна вернуть значение типа `IO ()`
|
|
|
|
|
-- Например
|
|
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
|
main = putStrLn $ "Hello, sky! " ++ (say Blue)
|
|
|
|
|
-- `putStrLn` имеет тип `String -> IO ()`
|
|
|
|
|
|
|
|
|
|
-- Проще всего реализовать программу с вводом-выводом (IO),
|
|
|
|
|
-- если вы реализуете функцию с типом `String -> String`.
|
|
|
|
|
-- Далее ФВП
|
|
|
|
|
-- interact :: (String -> String) -> IO ()
|
|
|
|
|
-- сделает всё за нас!
|
|
|
|
|
|
|
|
|
|
countLines :: String -> String
|
|
|
|
|
countLines = show . length . lines
|
|
|
|
|
-- здесь `lines` разделяет строку на список строк
|
|
|
|
|
-- по символу перевода строки
|
|
|
|
|
|
|
|
|
|
main' :: IO ()
|
|
|
|
|
main' = interact countLines
|
|
|
|
|
|
|
|
|
|
{- Вы можете думать о типе `IO ()`,
|
|
|
|
|
как о некотором представлении последовательности
|
|
|
|
|
действий, которые должен совершить компьютер.
|
|
|
|
|
Такое представление напоминает программу
|
|
|
|
|
на императивном языке программирования. Для описания
|
|
|
|
|
такой последовательности используется `do`-нотация -}
|
|
|
|
|
|
|
|
|
|
sayHello :: IO ()
|
|
|
|
|
sayHello = do
|
|
|
|
|
putStrLn "What is your name?"
|
|
|
|
|
name <- getLine -- запрашиваем строку и связываем с "name"
|
|
|
|
|
putStrLn $ "Hello, " ++ name
|
|
|
|
|
|
|
|
|
|
-- Упражнение:
|
|
|
|
|
-- напишите свою реализацию функции `interact`,
|
|
|
|
|
-- которая запрашивает и обрабатывает только одну строку
|
|
|
|
|
|
|
|
|
|
{- Код функции `sayHello` не будет исполняться
|
|
|
|
|
при её определении. Единственное место, где IO-действия
|
|
|
|
|
могут быть произведены - функция `main`!
|
|
|
|
|
Чтобы эта программа выполнила действия в функции `sayHello`,
|
|
|
|
|
закомментируйте предыдущее определение функции `main`
|
|
|
|
|
и добавьте новое определение:
|
|
|
|
|
|
|
|
|
|
main = sayHello -}
|
|
|
|
|
|
|
|
|
|
{- Давайте подробнее рассмотрим, как работает функция `getLine`
|
|
|
|
|
Её тип:
|
|
|
|
|
getLine :: IO String
|
|
|
|
|
Вы можете думать, что значение типа `IO a` представляет
|
|
|
|
|
собой компьютерную программу, в результате выполнения которой
|
|
|
|
|
генерируется значение типа `a`, в дополнение
|
|
|
|
|
к остальным эффектам, производимым при выполнении - таким как
|
|
|
|
|
печать текста на экран. Это значение типа `a` мы можем
|
|
|
|
|
сохранить с помощью оператора `<-`. Мы даже можем реализовать
|
|
|
|
|
свое действие, возвращающее значение: -}
|
|
|
|
|
|
|
|
|
|
action :: IO String
|
|
|
|
|
action = do
|
|
|
|
|
putStrLn "This is a line. Duh"
|
|
|
|
|
input1 <- getLine
|
|
|
|
|
input2 <- getLine
|
|
|
|
|
-- Тип блока `do` будет соответствовать типу последнего
|
|
|
|
|
-- выполненного в блоке выражения.
|
|
|
|
|
-- Заметим, что `return` - не ключевое слово, а функция
|
|
|
|
|
-- типа `a -> IO a`
|
|
|
|
|
return (input1 ++ "\n" ++ input2) -- return :: String -> IO String
|
|
|
|
|
|
|
|
|
|
-- Теперь это действие можно использовать вместо `getLine`:
|
|
|
|
|
|
|
|
|
|
main'' = do
|
|
|
|
|
putStrLn "I will echo two lines!"
|
|
|
|
|
result <- action
|
|
|
|
|
putStrLn result
|
|
|
|
|
putStrLn "This was all, folks!"
|
|
|
|
|
|
|
|
|
|
{- Тип `IO` - пример "монады". Языку Haskell нужны монады,
|
|
|
|
|
чтобы оставаться преимущественно чистым функциональным языком.
|
|
|
|
|
Любые функции, взаимодействующие с внешним миром
|
|
|
|
|
(производящие ввод-вывод) имеют `IO` в своих сигнатурах.
|
|
|
|
|
Это позволяет судить о функции как о "чистой" - такая не будет
|
|
|
|
|
производить ввод-вывод. В ином случая функция - не "чистая".
|
|
|
|
|
|
|
|
|
|
Такой подход позволяет очень просто разрабатывать многопоточные
|
|
|
|
|
программы - чистые функции, запущенные параллельно
|
|
|
|
|
не будут конфликтовать между собой в борьбе за ресурсы. -}
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
-- 9. Haskell REPL
|
|
|
|
|
----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
{- Интерактивная консоль Haskell запускается командой `ghci`.
|
|
|
|
|
Теперь можно вводить строки кода на Haskell.
|
|
|
|
|
Связывание значений с именами производится
|
|
|
|
|
с помощью выражения `let`: -}
|
|
|
|
|
|
|
|
|
|
let foo = 5
|
|
|
|
|
|
|
|
|
|
-- Тип значения или выражения можно узнать
|
|
|
|
|
-- с помощью команды `:t`:
|
|
|
|
|
|
|
|
|
|
>:t foo
|
|
|
|
|
foo :: Integer
|
|
|
|
|
|
|
|
|
|
-- Также можно выполнять действия с типом `IO ()`
|
|
|
|
|
|
|
|
|
|
> sayHello
|
|
|
|
|
What is your name?
|
|
|
|
|
Friend!
|
|
|
|
|
Hello, Friend!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Многое о Haskell, например классы типов и монады невозможно уместить в столь короткую статью. Огромное количество очень интересных идей лежит в основе языка, и именно благодаря этому фундаменту на языке так приятно писать код. Позволю себе привести ещё один маленький пример кода на Haskell - реализацию быстрой сортировки:
|
|
|
|
|
|
|
|
|
|
```haskell
|
|
|
|
|
qsort [] = []
|
|
|
|
|
qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater
|
|
|
|
|
where lesser = filter (< p) xs
|
|
|
|
|
greater = filter (>= p) xs
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Haskell прост в установке, забирайте [здесь](http://www.haskell.org/platform/) и пробуйте! Это же так интересно!.
|
|
|
|
|
|
|
|
|
|
Более глубокое погрузиться в язык позволят прекрасные книги
|
|
|
|
|
[Learn you a Haskell](http://learnyouahaskell.com/) и
|
|
|
|
|
[Real World Haskell](http://book.realworldhaskell.org/).
|
|
|
|
|
|
2022-06-15 12:17:20 +03:00
|
|
|
|
[author]: http://adit.io имеется в виду автор оригинального текста Adit Bhargava *(примечание переводчика)*
|