# Глава 10. Диапазон
Список — тип, который повсеместно встречается в программах на Haskell. Неудивительно, что в языке есть способы лаконично создавать список из диапазона (англ. range) значений.
## Мотивация
Допустим, понадобился нам список целых чисел от одного до десяти. Пишем:
```haskell {.example_for_playground}
main :: IO ()
main = print tenNumbers
where tenNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```
Неплохо, но избыточно, ведь чисел могло быть и сто, и тысяча. Есть лучший путь:
```haskell {.example_for_playground}
main :: IO ()
main = print tenNumbers
where tenNumbers = [1..10]
```
Красиво, не правда ли? Выражение в квадратных скобках называется диапазоном. Иногда его именуют также интервалом или арифметической последовательностью. Идея предельно проста: зачем указывать содержимое списка целиком в той ситуации, когда можно указать лишь интервал значений?
## Использование
Значение слева от `..` — это начало диапазона, а значение справа — его конец. Компилятор сам догадается, что шаг между числами в данной последовательности равен 1:
```haskell
[1..10] = [1,2,3,4,5,6,7,8,9,10]
```
Вот ещё пример:
```haskell
[3..17] = [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]
```
Мы можем задать шаг и явно:
```haskell
[2,4..10] = [2,4,6,8,10]
```
Получили только чётные значения. Схема проста: указываем первый элемент, второй элемент и последний. **Разница между первым и вторым даёт шаг последовательности.** Многие начинающие хаскеллисты путаются и принимают второй элемент за шаг. Это не так: шаг последовательности рассчитывается компилятором как разность второго и первого элементов!
И ещё пример:
```haskell
[3,9..28] = [3,9,15,21,27]
```
Что выведет этот код? В случае ошибки напишите `error`. {.task_text}
```haskell {.example_for_playground}
module Main where
main :: IO ()
main = print ([5,8..100] !! 2)
```
```consoleoutput {.task_source #haskell_chapter_0100_task_0010}
```
Здесь формируется список, нулевой элемент которого 5, первый — 8. Получается, что шаг между элементами равен 8 - 5, то есть 3. Поэтому элемент с индексом 2, который выводится в консоль, равен 8 + 3, то есть 11. {.task_hint}
```haskell {.task_answer}
11
```
Можно задать и нисходящий диапазон:
```haskell
[9,8..1] = [9,8,7,6,5,4,3,2,1]
```
Или так:
```haskell
[-9, -8.. -1] = [-9,-8,-7,-6,-5,-4,-3,-2,-1]
```
Напишите функцию `progressionSum`, которая принимает три целых числа: первый и последний элементы арифметической прогрессии и ее шаг. {.task_text}
Функция должна вернуть сумму членов арифметической прогрессии. {.task_text}
```haskell {.task_source #haskell_chapter_0100_task_0020}
module Main where
-- Your code here
main :: IO ()
main = do
print (progressionSum 0 3 1)
print (progressionSum 1 10 2)
print (progressionSum (-1) (-10) (-5))
```
Создайте диапазон чисел и примените к нему встроенную функцию `sum`. {.task_hint}
```haskell {.task_answer}
module Main where
progressionSum :: Int -> Int -> Int -> Int
progressionSum firstEl lastEl step = sum [firstEl, firstEl + step.. lastEl]
main :: IO ()
main = do
print (progressionSum 0 3 1)
print (progressionSum 1 10 2)
print (progressionSum (-1) (-10) (-5))
```
Можно взять также и числа с плавающей точкой:
```haskell
[1.02,1.04..1.16] = [1.02,1.04,1.06,1.08,1.1,1.12,1.14,1.16]
```
В общем, идея ясна. Но что это мы всё с числами да с числами! Возьмём символы:
```haskell
['a'..'z'] = "abcdefghijklmnopqrstuvwxyz"
```
Диапазон от `'a'` до `'z'` — это английский алфавит в виде `[Char]` или, как мы уже знаем, просто `String`. При большом желании явно задать шаг можно и здесь:
```haskell
['a','c'..'z'] = "acegikmoqsuwy"
```
Вот такая красота.
Теперь, после знакомства со списками и диапазонами, мы будем использовать их постоянно.
## Бесконечные последовательности
Благодаря ленивым вычислениям в Haskell запросто можно оперировать бесконечными диапазонами. Они выглядят так, как будто при определении диапазона мы забыли указать его верхнюю границу: {#infinite-range}
```haskell
[1 ..]
```
Вот мы и получили бесконечный набор чисел: `[1, 2, 3, 4, 5, 6, ...`. Мы можем использовать его внутри функции:
```haskell
infiniteRange :: Int -> Int -> [Int]
infiniteRange start step = [start, start+step ..]
```
Такая функция нормально скомпилируется. Проблемы начнутся, если мы попробуем ее использовать:
```haskell
main :: IO ()
main = print (infiniteRange 10 5)
```
В рантайме действительно будет предпринята попытка генерации все новых и новых элементов списка для их вывода в консоль. Очевидно, ничем хорошим это не закончится.
Однако, возвращаясь к концепции ленивости, бесконечные списки все же можно создавать и использовать без риска исчерпания всех возможных ресурсов. Например, так мы посчитаем 17-ый элемент арифметической прогрессии с первым элементом 10 и шагом 5:
```haskell
main :: IO ()
main = print (infiniteRange 10 5 !! 17)
```
```
95
```
Про ленивость мы поговорим более подробно в [посвященной ей главе.](/courses/haskell/chapters/haskell_chapter_0170/)
Создайте функцию `f`, которая считает пятый по индексу член убывающей прогрессии `0, -2, -4, -6, ...`. Воспользуйтесь для этого бесконечным диапазоном. {.task_text}
```haskell {.task_source #haskell_chapter_0100_task_0030}
module Main where
-- Your code here
main :: IO ()
main = print f
```
Создайте бесконечный диапазон чисел и примените к нему оператор взятия элемента по индексу `!!`. {.task_hint}
```haskell {.task_answer}
module Main where
f :: Int
f = [0, -2..] !! 5
main :: IO ()
main = print f
```
Что выведет этот код? В случае ошибки или нежелательного поведения программы напишите `error`. {.task_text}
```haskell {.example_for_playground}
module Main where
main :: IO ()
main = print (sum [1,2..])
```
```consoleoutput {.task_source #haskell_chapter_0100_task_0040}
```
При попытке посчитать сумму членов бесконечной арифметической прогрессии воспользоваться ленивостью не получится: требуется знать значения всех элементов диапазона. Поэтому так или иначе, но процесс израсходует всю доступную ему память. {.task_hint}
```haskell {.task_answer}
error
```
## Для любопытных
В разделе про диапазоны для списка мы оперировали значениями типа `Int`, `Double` и `Char`. Возникает вопрос: а можно ли использовать значения каких-нибудь других типов? Отвечаю: можно, но с оговоркой. Попробуем проделать это со строкой:
```haskell
main :: IO ()
main = print ["a","aa".."aaaaaa"] -- Ну-ну...
```
При попытке скомпилировать такой код увидим ошибку:
```
No instance for (Enum [Char])
arising from the arithmetic sequence ‘"a", "aa" .. "aaaaaa"’
```
И удивляться тут нечему: шаг между строками абсурден, и компилятор в замешательстве. Не все типы подходят для перечислений в силу своей природы, однако в будущем, когда мы научимся создавать наши собственные типы, мы узнаем, что их вполне можно использовать в диапазонах. Наберитесь терпения.
Приоткрою секрет: этот странный пример с шагом между строками теоретически можно-таки заставить работать, но о том, как это сделать, мы узнаем во время знакомства с Третьим Китом Haskell.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!