Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MakeevGA-Haskell-a4-shortcode_2014_05_31.doc
Скачиваний:
15
Добавлен:
19.01.2023
Размер:
1.79 Mб
Скачать

Двумерный синтаксис

Вы уже заметили, наверное, что в языке Haskell нет ни точек с запятыми, разделяющих операторы, ни begin ... end или фигурных скобок, группирующих операторы. Хотя на самом деле потребность в группировке имеется.

Посмотрите еще раз на определение функции solve в версии с where. Откуда следует, что d = b*b - 4*a*c есть локальное определение, а не просто следующая за solve функция? Компилятор понимает это благодаря такой вещи, как двумерный синтаксис.

То, с какой колонки начинается первое определение за словом where, определяет все, что будет к этому where относиться. Как только вы вернетесь обратно к тому столбцу, с которого начинается сама функция solve, компилятор поймет, что локальные определения кончились.

Это очень удобный подход, который избавляет от необходимости брать все локальные определения в фигурные скобки {} и разделять их точками с запятой ";". Хотя это тоже возможно, конечно, если по каким-то очень странным причинам вам захочется в одной строчке дать несколько определений функций.

Арифметические последовательности

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

[1..10] → [1,2,3,4,5,6,7,8,9,10]

[1,3..10] → [1,3,5,7,9]

[10,9..1] → [10,9,8,7,6,5,4,3,2,1]

[1..] → [1,2,3,4,5,6,7,8,9,10,…

В последнем случае мы случайно создали бесконечный список натуральных чисел. Если мы попробуем его вывести на экран, то так и не дождемся конца. Как с такими списками работать, мы рассмотрим чуть позже. А пока нужно сказать, что арифметические последовательности на самом деле не ограничены целыми числами. Можно использовать многие другие типы (на самом деле только те, что входят в класс Enum), правда, иногда с неожиданными последствиями:

['a'..'f'] → "abcdef"

[0.0,3.5..10.0] → [0.0,3.5,7.0,10.5]

Замыкания списков

Еще одна очень удобная особенность языка, связанная с генерацией и обработкой списков – это так называемые замыкания списков. Появилась она в языке из-за мощного лобби математиков, которые вообще сначала хотели, чтобы все их математические определения без переписывания оказались работающими программами (шутка, конечно, хотя…). Оцените сами:

{x2 | x ← N, x < 10}

[x^2 | x <- [1..10]]

Первое на языке математиков означает множество квадратов натуральных чисел, меньших 10. Второе на языке Haskell означает список квадратов натуральных чисел, меньших или равных 10. В общем случае, замыкание списков имеет вид:

[выражение | образец <- список, образец <- список, … , условие, условие, … ]

Замыкание создает список по следующей схеме: из каждого списка берется по элементу, и если все условия выполняются, то из этих элементов выражение генерирует новый элемент выходного списка.

[x+1 | x <- [1..5]] →

[2,3,4,5,6]

[(x,x+1) | x <- [1..5]] →

[(1,2),(2,3),(3,4),(4,5),(5,6)]

Как видим, выражение, генерирующее список, может быть довольно нетривиальным. Если списков задано несколько, то выражение генерирует новый элемент списка по всевозможным комбинациям значений из списков-источников:

[(x,y) | x <- [1..4], y <- [1..4], x < y] →

[(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)]

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