Главная » Haskell » Образцы вида (n + k)

0

В  целях обеспечения  совместимости с  математической нотацией  в  языке  Haskell имеется возможность использования так  называемых  образцов вида (n + k). Это значит, что в образцах можно использовать символ (+), который обозначает арифметическое  сложение  чисел. Другими  словами, для числовых значений можно использовать выражение последующих вычисляемых  элементов последовательности через уже имеющиеся. Такой способ представления формул принят в математике, а потому в языке Haskell было решено внедрить эту технику.

Однако  такие  образцы не приветствуются большинством  программистов на языке Haskell, а потому их невозможно найти в стандартном модуле Prelude, чтобы привести в качестве примера. Поэтому пример будет достаточно простой:

fact 0             = 1

fact (n  + 1)  = (n  + 1)  * fact n

Это функция для вычисления факториала заданного числа. Как видно, она соответствует математической  рекуррентной формуле:

(n + 1)! = (n + 1) ? n!

Конечно, этот пример несколько надуман, но именно рекуррентные формулы в математике послужили прототипом образцов вида (n + k) в языке Haskell. Надо отметить, что такой способ записи существует не во всех функциональных языках. И в сообществе любителей языка Haskell по этому поводу даже был раскол, и часть ведущих программистов и специалистов подписала меморандум о запрете образцов вида (n + k). Но такие образцы всё равно были включены в стандарт языка. Тем не менее использование  этого вида образцов лежит на совести разработчика программного  обеспечения. Некоторым они нравятся за дополнительную выразительность и подобие математическим  формулам.

Но необходимо помнить, что в образцах можно использовать только операции конструирования данных (конструкторы).  Арифметический знак (+) — это единственный символ операции, который можно использовать в образцах.

1.2.1.       Именованные образцы

Другими  образцами специального  вида являются так  называемые  «именованные образцы». Конечно, это достаточно условное название, потому как любой образец, который не является константой или маской подстановки (_), имеет какой-то идентификатор, по которому к нему можно обратиться. Но здесь речь идёт о несколько иной вещи.

Иногда в вычислительном  процессе, описываемом некоторой функцией, необходимо обратиться к фактическому значению входного параметра как к целому, но при этом имеется надобность и в разложении этого фактического значения на составные части (если это значение сложного типа). Так, к примеру, в стандартном модуле Prelude определена функция dropWhile, которая «выкидывает» элементы входного списка, пока значение входного предиката на них равно True (подробно эта функция описана на стр. 261):

dropWhile  p []                     = []

dropWhile  p xs@(x:xs’) | p x             = dropWhile  p xs’

| otherwise = xs

Именованный образец записывается при помощи символа (@), который разделяет наименование образца как целого и запись внутренней структуры образца. В приведённом примере образец xs@(x:xs’) является именованным. Это список, который обрабатывает функция dropWhile. Если в теле функции имеется необходимость обратиться к этому списку в целом, то используется идентификатор xs, но если надо получить голову и хвост этого списка, то можно воспользоваться идентификаторами  x и xs’ соответственно.

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

Какие  же  способы позволяют сделать то же  самое?  Первый  способ —  повторная сборка объекта сложной природы. В этом случае второй клоз функции dropWhile выглядел бы так:

dropWhile  p (x:xs’) | p x             = dropWhile  p xs’

| otherwise = x:xs’

Как  видно, вариант otherwise повторно собирает объект (список)  из элемента x и списка xs’ при помощи конструктора  (:).  Здесь  именно происходит создание нового объекта, а не использование входного параметра, как в предыдущем варианте функции. Хотя это приведёт к абсолютно такому же результату, ресурсоёмкость такого решения выше.

Второй вариант  — использование  селекторов  для доступа к элементам значения сложного типа. В этом случае второй клоз функции dropWhile будет выглядеть уже так:

dropWhile  p xs  | p (head  xs)  = dropWhile  p (tail xs)

| otherwise     = xs

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

1.2.3.    Ленивые образцы

Остаётся рассмотреть так  называемые  «ленивые образцы»,  сопоставление с которыми происходит только в случае, если сам образец или какая-либо из его частей используется в теле функции. Определение таких образцов иногда позволяет выгодно уменьшить ресурсоёмкость вычислительных процессов.

Для определения ленивых образцов используется символ (~). В стандартном модуле Prelude имеется всего пара функций, в которых определены ленивые образцы. Например, функция unzip,  которая получает на вход список пар, а возвращает пару списков (подробно описывается на стр. 270):

unzip  = foldr  (\(a,b)  ~(as,bs) ->  (a:as, b:bs)) ([], [])

При рассмотрении подобных конструкций с ленивыми образцами  необходимо помнить, что транслятор языка Haskell просто раскрывает ленивые образцы следующим образом: f ~x = e трансформируется в f  p  = let x = p in e. Это и обозначает, что только при использовании идентификатора x внутри выражения e ему будет сопоставлено формальное значение.

Источник: Душкин Р. В., Справочник по языку Haskell. М.: ДМК Пресс, 2008. 544 с., ил.

По теме:

  • Комментарии