Курсовая работа на тему: "Расширение конструкции сопоставления с образцом в языке OCaml"

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

Но если вдруг:

Вам нужна качественная учебная работа (контрольная, реферат, курсовая, дипломная, отчет по практике, перевод, эссе, РГР, ВКР, диссертация, шпоры...) с проверкой на плагиат (с высоким % оригинальности) выполненная в самые короткие сроки, с гарантией и бесплатными доработками до самой сдачи/защиты - ОБРАЩАЙТЕСЬ!

Курсовая работа на тему:

"Расширение конструкции сопоставления с образцом в языкеOCaml"

Оглавление

Введение                                                                                                                                      3

1.     Обзор                                                                                                                                       5

1.1.      Views............................................................................................................................... 5

1.2.      Pattern guards and Transformational Patterns....................................................... 6

1.3.      Scala Objects patterns................................................................................................... 7

1.4.      F# Active Patterns..................................................................................................... 10

1.5.      Haskell Pattern Synonyms........................................................................................ 11

2.     Оператор  возврата к сопоставлению                                                                       13

3.     Выбор  конструкции для реализации                                                                    17

3.1.      Введение объективных критериев для сравнения........................................... 17

3.2.      Сравнение существующих решений................................................................... 18

3.2.1.       Максимальность........................................................................................... 19

3.2.2.       Выразительность........................................................................................... 20

3.2.3.       Образцы первого класса............................................................................. 21

3.2.4.       Проверка на полноту.................................................................................. 21

3.2.5.       Эффективность............................................................................................. 22

3.3.      Заключительный выбор.......................................................................................... 23

4.     Реализация активных образцов в OCaml                                                           24

4.1.      Расширение синтаксиса.......................................................................................... 24

4.2.      Правила типизации.................................................................................................. 25

4.3.      Модификация схемы компиляции...................................................................... 25

5.     Заключение                                                                                                                        28

6.     Благодарности                                                                                                                  29

Список литературы                                                                                                             30

 

Введение

В контексте языков программирования сопоставление с образцом служит весьма эф- фективным и выразительным средством анализа данных. Появившееся в первых функ- циональных языках вкупе с алгебраическими типами данных эта конструкция доволь- но быстро завоевала популярность и в практически неизменном виде реализовывалась в большинстве последующих функциональных языков. Были разработаны эффектив- ные схемы компиляции [10, 12, 22],проверки на полноту (pattern exhaustiveness) [11] и хрупкость (pattern fragility). Более того, в настоящее время сходные конструкции разрабатываются и добавляются и в некоторые лидирующие императивные языки, такие как C# [13], Kotlin [9] и Java [4].

Однако в своем наиболее распространённом варианте этот приём имеет довольно серьёзные ограничения:

•    конструкторы данных должны быть открытыми (public), что не допускает аб- стракции определения типа данных;

•    для каждого типа данных возможно лишь одно представление для использова- ния в конструкции сопоставления с образцом;

•    охранные выражения (pattern guards) либо не допускаются языком (как в языке Standard ML [14]), либо могут содержать ровно одно булево выражение (как в языке OCaml на текущий момент [24]);

•    семантика сопоставления с образцом жёстко зафиксирована на уровне языка и не допускает определяемых пользователем вычислений1.

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

1.     Со стороны определения образца: лист может быть только открытым конструк- тором типа или константой; заметим также, что при этом, как правило, он не является значением первого порядка (first class value).

2.     Со стороны определения сопоставления: сопоставление с образцом может яв- ляться только списком правил, а его семантика жёстко зафиксирована науровне языка.

В этой работе мы предъявим два независимых расширения языка для устранения каждого типа ограничений. Однако отметим, что в этой работе способы модификации семантики сопоставления с образцом [23, 25] рассматриваться не будут.

1Изменение семантики конструкции сопоставления с образцом путем параметризации образцов ти- пами специального класса типов представлено в секции 6.3 работы [23].

Основным подходом к расширению образцов является возможность проведения некоторых вычислений при сопоставлении, благодаря чему становятся возможными абстракция и задействование нескольких представлений при в сущности столь же выразительном синтаксисе. Многие языки программирования уже содержат подобные конструкции в том или ином виде: active patterns в F# [23], pattern synonyms в Haskell

[19]    и extractors в Scala [5]. В данной же статье мы предложим расширение для языка программирования общего назначения OCaml.

 

Постановка задачи

Целью работы является разработка расширение языка OCaml, которое бы:

• удовлетворяло требованию консервативности (или обратной совместимости) – синтаксис и семантика любой программы, не использующей средства расшире- ния, должны оставаться неизменными;

• являлось полностью статически типизируемым;

• не требовало модификации системы типов языка;

• допускало абстракцию образцов;

• сохраняло возможность проверки сопоставления на полноту;

• позволяло оперировать образцами как значениями первого порядка;

• не требовало модификации существующих модулей.

Были поставлены следующие задачи:

1. Провести анализ существующих решений и выполнить сравнение по ряду объ- ективных критериев. По результатам обосновать выбор того или иного суще- ствующего решения или представить собственное.

2. Формализовать изменения, которые необходимо внести в компилятор языка OCaml, для поддержки нового расширения:

• расширение синтаксиса

• правила типизации новых конструкций

• компиляция расширения в промежуточное представление

3. Представить прототип реализации выбранного расширения.

1.      Обзор

1.1.     Views

Ф. Вадлер (Philip Wadler) [26] одним из первых указал на недостатки традицион- ной модели образцов: сопоставление с образцом является мощным выразительным средством и ясно (внекоторых случаях почти досимвольно) выражает индуктивные математические определения, но требует открытого представления типа. Абстракция данных поддерживает эффективность2 и инкапсуляцию, но требует скрыть представ- ление типа – таким образом программист оказывается перед дилеммой выразитель- ного или эффективного кода.

Для решения этой проблемы Ф. Вадлер предложил концепцию представлений (views): определяемое представление состоит из целевого типа данных, по которому проводится сопоставление собразцом, и двух функции in и out, которые определяют преобразование в целевой тип данных и обратно соответственно. Можно определить несколько представлений с соответствующими преобразованиями и экспортировать их вместе с настоящим устройством типа или без него, при этом для пользователя они будут равноправны. Интересно заметить, что в терминах представлений могутбыть выражены такие особенности сопоставления с образцом как as-образцы и охранные булевы выражения.

2В качестве примера можно рассмотреть представление натуральных чисел в виде эффективного машинного типа целого числа и совершенно неэффективное, но формальное и удобное представление в виде алгебраического типа данных согласно математическому определению Пеано. Подробнее см. в [26].

1.2.     Pattern guards and Transformational Patterns

Примерно в это же время был предложен альтернативный подход к данной задаче: М. Эрвиг (Martin Erwig) и С. П. Джонс (Simon Peyton Jones) представили два рас- ширения сопоставления с образцом: охранные выражения и преобразующие образцы (transformational patterns) [6]. Охранные выражения позволяют в одной ветке разбо- ра проводить несколько последовательныхсопоставлений. Ключевой здесь является возможность вычисления функции от привязанных переменных, результат которой также проходит сопоставление. Такое простое и по синтаксису, и посемантике, и по реализации правило автоматически позволяет абстрагироваться по любому образцу, причем зачастую с возможностью использования уже существующих функций.

К примеру, рассмотрим класс типов для простейшего списка:

class  AbsList c where

nil    :: c a

cons  :: a c a c a

head  :: c a Maybe (a, c a)

Тогда сопоставление с образцом можно записать как

instance  AbsList c  ⇒  Functor c where

fmap f l

| Just (x, xs)  ←  head l = f x `cons` fmap f xs

| Nothing          ←  head l = nil

В последовательно стоящих охранных выражениях можно использовать привязки из предыдущих:

filterMap :: AbsList c  ⇒  c a  →  (a  →  Maybe b)  →  c b filterMap f l

| Just (x, xs) head l,

Just v    ←  f x                = v `cons` filterMap f xs

| Just (x, xs) head l,

Nothing  ←  f x                = filterMap f xs

| Nothing          ←  head l   = nil

Таким образом любой одиночный образец для выражения типа τ может быть пред- ставлен как функция pat :: τ → Maybe ζ, где ζ являет собой некоторый кортеж, размерность которогоопределяет количество привязываемых при сопоставлении пе-

ременных, а его составляющие – типы этих привязок. Отметим, что в этом смысле Maybe ()эквивалентен Bool, а неопровержимые (irrefutable) образцы могут представ- лены просто как irrPat :: τ → ζ. Используется pat как

f x

| Just (...) pat x = ...

Заметим, однако, небольшую синтаксическую особенность – при таком использо- вании первым зачастую идет неопровержимый образец, именующий само инспекти- руемое выражение(scrutinee) – например, x в выражении f x выше. А уже образец, непосредственно разбирающий выражение, идет после, в охранном выражении. При

этом, как в примере с filterMap, если мы хотим применить функцию к одной из по- лученных привязок, необходимо записать это в следующем охранном выражении, что отличается оттрадиционной вложенной формы записи, при которой подобразцы запи- сываются прямо на месте привязок. К тому же это вызывает дублирование отдельных охранных выражений, как с Just (x, xs) head l в примере с filterMap.

Для устранения этой особенности в пользу более привычной формы записи в ра-

боте был предложен синтаксический сахар – преобразующие образцы. Они позволяли записывать сопоставление с результатом функции непосредственно внутри образца. Правда, насколько нам известно, в первоначальном варианте, предложенном в ра- боте, они так и не были реализованы, поскольку вместо них довольно быстро были добавлены так называемыепредставляющие образцы (view patterns), реализующие ту же самую идею. Используя эту нотацию filterMap можно переписать как

{# LANGUAGE ViewPatterns   #}

filterMap f (head  →  Just (f  →  Just v,    xs))

= v `cons` filterMap f xs

filterMap f (head  →  Just (f  →  Nothing, xs)) = filterMap f xs filterMap f (head  →  Nothing)  = nil

 

1.3.     Scala Objects patterns

В работе [5] было предложено развитие идеи, что любой одиночный образец может быть представлен функцией типа pat :: τ → Maybe ζ. Образец в языке Scala опре- делятся как функция unapply(obj: τ ):Option[ζ]3, реализованная в некотором объ-

3Более полное и формальное определение вы можете найти в спецификации языка Scala: https://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html# extractor-patterns

екте, именуемом экстрактором.

Однако, по сравнению с представляющими образцами в Haskell при сопоставлении с образцом никаких дополнительных синтаксических элементов вроде или провер- ки с конструктором Just не требуется, что позволяет писать более краткий и выра-

зительный код. К примеру, аналогичное определение AbsList  и реализация функции

filterMap на языке Scala:

trait  AbsList[+A] {

def  nil[B]: AbsList[B]

def  cons[B, U >: B](hd:  U, tl: AbsList[B]): AbsList[U]

def  head: Option[(A, AbsList[A])]

// Extractor signatures:

trait  ConsExSig {

def  unapply[A](l: AbsList[A]): Option[(A, AbsList[A])] = l.head

}

trait  NilExSig {

def  unapply[A](l: AbsList[A]): Boolean = l.head.isEmpty

}

// Extractors

val  Cons = new  ConsExSig {}

val  Nil   = new  NilExSig   {}

}

 

def  filterMap[A, B](f: A Option[B], l: AbsList[A])

: AbsList[B] = {

import  l._ l match  {

case  Cons(x, xs) if  f(x).isDefined

case  Cons(_, xs)

case  Nil()

}

}

⇒  cons(f(x).get, filterMap(f, xs))

⇒  filterMap(f, xs)

⇒  nil

В то же время охранные выражения в Scala могут содержать только булево выра- жение и не позволяют определять дополнительных привязок. При этом иногда, как и в примере выше, возникает ситуация, когда в охранном выражении нужно про- извести некоторое вычисление и проверить его результат, а затем в случае успеха воспользоваться этим результатом внутри выражения – вместо этого сейчас выра- жение f(x) дублируется и более того вычисляется повторно. Чтобы избежать этой

ситуации можно определить объект-обертку, реализующий unapply для f:

1.4.     F# Active Patterns

Отметим, что приведённая реализация позволяет переопределять экстракторы в классах, реализующих свойство (trait) AbsList.

Решение, предложенное в работе [23] обладает схожей выразительностью и в то же время позволяет объединять отдельные образцы в один многозначный, сопоставление с которым может быть проверено на полноту.

Например, для того же класса типов AbsList (в ML-языках представляемого мо- дулем) можно объединить образцы Cons и Nil в один неопровержимый двузначный образец:

let  (|Cons|Nil|) l =

match  head l with

| Some(hd, tl)  Cons(hd, tl)

| None              Nil

В качестве примера использования рассмотрим функцию destutter, удаляющую последовательные дубликаты элементов:

let  rec  destutter = function

| Cons(hd, Cons(hd', _) as  tl) when  hd = hd'  destutter tl

| Cons(hd, tl)  cons hd (destutter tl)

| Nil                nil

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

входного значения.

В такой реализации используются “анонимные” типы-суммы, имена конструкторов которых не зависят от предметной области и состоят лишь из имени типа и порядко- вого номера. Такиетипы предопределяются языком F#, к примеру

type  ('a, 'b) choice =

| Choice2_1 of  'a

| Choice2_2 of  'b

Такое решение представляется также крайне удачным для реализации в языке OCaml, благодаря поддержке языком полиморфных вариантов, что любезно заметили авторы работы [23]. Полиморфные варианты позволяют заменить анонимные типы на более информативные и гибкие конструкторы-метки, к примеру

val  (|Cons|Nil|) :

'a AbsList.t  [`Cons of  ('a ∗ 'a AbsList.t) |  `Nil]

Вместо

val  (|Cons|Nil|) :

'a AbsList.t  ('a ∗ 'a AbsList.t) choice

 

1.5.     Haskell Pattern Synonyms

Помимо полных (total) образцов, предложенное расширение также позволяет опре- делять и обычные частичные образцы в терминах все тех же функций с результиру- ющим типом ζ option. При этом образцы также могут быть параметризованы раз- личными значениями, в частности, другими образцами.

С технической же точки зрения, авторы работы предложили модификацию схемы компиляции [22], отличную от используемой компилятором OCaml [10]. Представ- ляется интереснымразработать модификацию схемы [10] для внедрения подобного расширения.

Пожалуй, единственным недостатком активных образцов, предложенных для F#, по сравнению с обычными образцами конструкторов типа, является тот факт, что первые можно использоватьтолько в сопоставлении с образцом, но не для конструирования данных. Так в примере выше для разбора значения списка используется активный образец Cons, а для конструированияотдельная функция cons. При этом даже самое первое решение в виде представлений [26] позволяет использовать один и тот же терм образца в обоих случаях.

Предложенное в работе [19] решение в виде синонимов образцов позволяло опре- делять неявные (implicit) или явные двунаправленные (explicit bidirectional) образцы, термы которых можно использовать как для конструирования, так и для разбора значений. К примеру, для класса типов AbsList можно определить

Тогда эквивалентное определение функции destutter с использованием синони- мов образцов может быть записано следующим образом:

pattern  Nil :: AbsList c  ⇒  c a

pattern  Nil  ←  (head  →  Nothing) where

Nil = nil

pattern  Cons :: AbsList c  ⇒  →  c a  →  c a

pattern Cons x xs (head Just (x, xs)) where

Cons x xs = cons x xs

destutter Cons(hd, tl@Cons(hd', _)) | hd == hd' = destutter tl destutter Cons(hd, tl) = Cons hd (destutter tl)

desturrer Nil                = Nil

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

pattern Q :: Ord a a a (a, a)

pattern Q x y (x, y) where

Q x y | x  �  y      = (x, y)

| otherwise = (y, x)

Здесь для конструирования требуется ограничение Ord, в то время как для сопо- ставления нет. Сигнатура образца выводится только из определения сопоставляющей части. Поэтому вданном случае потребуется явно указать сигнатуру, поскольку вы- веденная окажется слишком общей: Q :: a b (a, b).

2.      Оператор возврата к сопоставлению

    требует введения в язык всего одного ключевого слова;

    типизируется элементарно как ∀a a;

    тривиально и достаточно эффективно встраивается в любую схему компиляции;

    проверка на полноту также легко расширяется для учета новой особенности.

 

Вдохновением для такого дизайна послужила схема компиляции сопоставления с образцом в автомат поиска с возвратами (backtracking automata) [10, 1], оптимизиро- ванный вариант которой используется сейчас в OCaml.

В простейшем варианте такой схеме компиляции на вход поступает матрица образ- цов и вектор значений. Первым шагом происходит применение разделяющего правила (mixture rule [10]),которое некоторым образом делит матрицу на группы последова- тельных строк, рекурсивно компилируемых другими правилами. При компиляции группы для случая, когда сопоставление внутри этойгруппы не удалось, генериру- ется бросание статического4 исключения. Разделяющее же правило ответственно за вставку статических обработчиков и линейное упорядочивание групп.

Рассмотрим пример компиляции слияния списков из [10] в промежуточное пред- ставление компилятора OCaml (также подробно описанного в [10]):

 

let  merge lx ly =

match  lx, ly with

| []     ,   _      1

| _      , []       2

| x::xs, y::ys →  3

catch

(catch

(switch  lx with  case  []: 1

default: exit)

with  (catch

(switch  ly with  case  []: 2

default: exit)

with  (catch (switch  lx with

case (::): (switch  ly with

case (::) : 3

default: exit)

default: exit))))

with  (failwith "Partial match")

 

Рис. 1: Простейшая компиляция слияния списков

4для которого на уровне компиляции известна точка обработки, а значит оно может быть скомпи- лировано в единственную инструкцию перехода

В данном разделе мы представим собственное расширение сопоставления с образцом. Его особенностью является минимально возможное вмешательство в язык:

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

Данное расширение предлагает позволить пользователю инициировать такие воз- враты, причем не только внутри образца, но и в теле ветви, т.е. уже после успешного разбора образцов.Для этого требуется добавить в язык оператор возврата к сопо- ставлению (далее для краткости именуемый как оператор возврата), представляемый ключевым словом backtrack.Идеалогически такая конструкция близка широко ис-

пользуемым в императивных языках операторам возврата из цикла break и continue,

позволяющих ограниченное ручное управление потоком управления.

3.      Выбор конструкции для реализации

3.1.     Введение объективных критериев для сравнения

Для выбора конкретного дизайна необходимо научиться объективно сравнивать суще- ствующие решения, для чего в свою очередь необходимо ввести некоторые критерии. К настоящему моменту таких критериев не было сформулировано: существующие ре- шения сравнивались на некоторых отдельных группах примерах, оперируя такими субъективным понятиями как ”краткость синтаксиса”, ”расширяемость”, ”легковес- ность”, ”более общее” и т.п.

Для введения подходящих критериев стоит обратиться к теоретической части за- дачи. Что такое ”образец”? Что такое ”сопоставление с образцом”? Что такое ”кон- струкция сопоставления собразцом”? Какие теоретические задачи они призваны ре- шать? Опять-таки в существующих работах подобные понятия определяются специ- фично для конкретного языка: вводятся некоторые синтаксические конструкции, на- деляются определенной семантикой, и их называют уже перечисленными выше тер- минами. Поскольку сами определения как правило привязаны к конкретному языку, сравнивать конструкции между языками становится достаточно сложно.

М. Туллсен [25] первым указал на данную проблему и сосредоточился на теорети- ческой части. Введенные им определения не зависят от конкретного языка и приме- нимы к любому статическитипизированному языку с функциями высших порядков и алгебраическими типами данных.

Так       образец       определяется       как       произвольная       функция       типа  τ → Maybe ζ, сопоставление с образцом определяется как применение данной функ- ции к значению идекомпозиции результата (который ограничен типом Maybe ζ).

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

type  ('a, 'b) pat = 'a  'b option

(∗ Комбинатор ИЛИ ∗)

val  ($|): ('a, 'b) pat  ('a, 'b) pat  ('a, 'b) pat

(∗ Комбинатор И ∗)

val  ($&): ('a, 'b) pat  ('a, 'b) pat  ('a, 'b ∗ 'b) pat

(∗ Комбинатор композиции ∗)

val  ($:): ('a, 'b) pat  ('b, 'c) pat  ('a, 'c) pat

(∗ Комбинатор функтор-отображения ∗)

val  ($>): ('a, 'b) pat  ('b  'c)     ('a, 'c) pat

(∗ Комбинатор параллельного разбора ∗)

val  ($∗): ('a, 'c) pat  ('b, 'd) pat  ('a ∗ 'c, 'b ∗ 'd) pat

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

практически случаи использования образцов.

Наконец, представляется естественным определить  конструкцию сопоставления  с образцом, как встроенный язык описания предметной области (Embedded DSL), предоставляемыйхост-языком для определения образцов. Заметим, что существую- щие языки используют данную конструкцию одновременно и для применения сопо- ставления с определяемым образцом.

Благодаря такому определению мы естественным образом получаем критерий для сравнения ”мощности” различных дизайнов конструкций сопоставления:

Определение 1 (Максимальность).

Назовем конструкцию сопоставления с образцом максимальной, если она позво- ляет выразить произвольную композицию комбинаторов образцов.

Также интересным представляется формализовать свойство синтаксической крат- кости или выразительности:

Определение 2 (Выразительность).

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

Заметим, что для такого критерия его невыполнимость проверяется простым при- ведением контрпримера, а вот его выполнимость это формально технически сложное доказательство.

Крайне важно отметить, что полнота сопоставления еще формально не опреде- лена. Формально ”полнота” это свойство именно образца, однако по исторически при- чинам прижился термин ”полнота сопоставления”. Кажется естественным определить образец полным, если при сопоставлении с ним могут быть получены только ”непу- стые” значения, т.е. по М. Туллсену, все результирующие значения имеют тег Just. Несложно показать, что такое свойство неразрешимо. Тотальные активные образцы F# предлагают некоторое решение этой проблемы, позволяя в качествеобразцов ис-

 

3.2.     Сравнение существующих решений

Произведем сравнение различных дизайнов по следующему ряду критериев:

пользовать также значения типа α → ξ, где ξ произвольный анонимный тип-сумма. В такой трактовке сопоставление с такими образцами всегда будет выдавать только

”полезное” значение. Резюмируя, при достаточно мощной конструкции сопоставления в языке проверка на полноту в базовом определении неразрешима. Однако, ослабив определение образцов, можно выделить разрешимую группу полных по определению образцов, добившись некоторой достаточной для по крайней мере некоторых практи- ческих задач аппроксимацииполноты.

    максимальность, введенная в предыдущем разделе, отражает практическую способность расширения решать поставленный круг задач;

    выразительность, введенная в предыдущем разделе, отражает синтаксические затраты на реализацию задачи;

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

    проверка на полноту, как уже было сказана, неразрешима, тем не менее нас интересует по крайней мере наличие у расширения формальныхаппроксимаций;

    эффективность — поскольку конструкция сопоставления с образцом является центральной для функциональных языков к ним предписываются жесткиетре- бования по эффективной компиляции. Здесь нас интересуют наличие в литера- туре модификаций для эффективных схем компиляции, таких как [10,12, 22].

          3.2.1.    Максимальность

Все отрицательные значения в столбцах возникают по причине, что соответствую- щие расширения не предоставляют возможности параметризовать часть образца зна- чением(представления, представления SML, образцы представленные объектами и синонимы образцов), в частности значение, полученным привязкой другого образца (активные образцы). Можнорассмотреть следующий синтетический пример из [7]:

Здесь значение f, привязанное в одной части образца, используется в другой (в данном случае само значение представляет собой view образец). Такое достаточно важное свойство (которое мы будет называть параметризацией слева-направо (left-to- right parameterization)) поддерживается только дизайнами представляющих образцов (view patterns) и обобщенных охранных выражений (pattern guards). Однако отметим,

что активные образцы также очевидным образом могут поддержать данную семан- тику, несмотря на то, что реализация языка F# не предлагает такой возможности. Такая дополнительнаявозможность никоим образом не влияет на проверку на пол- ноту и мемоизацию [17]. Единственный объективный недостаток такой возможности заключается в том, что для схем компиляции в деревья решений мы получим оче- видное ограничение на порядок столбцов, что впрочем с практической точки зрения вряд ли можно назвать существенным.

example :: ((String  →  Integer,Integer), String)  →  Bool example ((f,_), f  →  4) = True

 

          3.2.2.    Выразительность

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

Для всех расширений можно привести следующий синтетический пример дубли- рования, возникающий при использования комбинатора композиции и комбинатора ИЛИ.

type  t = C1

| C2

| C3

let _ =

match[@label outer]  a, b with

| _, b

begin  match  b with

| C1 1

| C2 2

| C3 backtrack%outer

end

| C2, C3  3

| _          4

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

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

Несмотря на некоторую кажущуюся искусственность ситуации такой шаблон в действительности возникал при написании промышленного ПО. В качестве примера, мы можем привести код на языке Scala [8], преобразующий язык из одного пред- ставления в другое. При этом существует некоторое ограниченное множество исклю- чительных ситуаций, которые по тем или иным причинамнеобходимо обработать особым образом. Множество ситуаций представлено одним большим сопоставлением,

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

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

let rec destutter2 l =

match  head l with

| Some(hd, tl) 

begin  match  head tl with

| Some(hd', _) when  hd = hd'  destutter tl

| _ cons hd (destutter tl)

end

| None  nil

Сравните данную реализацию с предложенной в разделе 1.4 на основе активных образцов.

Таким образом решение данной проблемы состоит в том, чтобы совместить дизай- ны предоставляющие возможность возвратов (такие как pattern guards или оператор возврата) и дизайны предоставляющие абстракции на уровне структурного вложения (такие как view patterns и активные образцы). Можно заключить, что текущая экоси- стема Haskell, обладающая как pattern guards, так иview patterns наиболее близка к оптимальности по выразительности и не достигает ее лишь потому, что, как уже упо- миналось в разделе 2 pattern guards не позволяют разбирать несколько альтернатив внутри ветви.

 

          3.2.3.    Образцы первого класса

Выполнение или невыполнение данного свойства напрямую следует из определения каждого дизайна. Особенный случай здесь представляют лишь синонимы образцов, для которых кажетсяочевидным возможность использования их в качестве значений первого класса, но конкретной реализации пока не предоставлено — данный вопрос оставлен открытым и в самой работе [19].

 

          3.2.4.    Проверка на полноту

Как уже было отмечено, проверка на полноту в строгом смысле неразрешима, од- нако для каждого из расширений представлены достаточно точные аппроксимации. Впрочем точность,сложность реализации и производительность этих проверок до- вольно сильно варьируются от дизайна к дизайну. Скажем, для активных образцов и представлений SML такая проверка можетбыть реализована достаточно просто и эффективно [11], но вот для множества расширений языка Haskell проверка весьма

затруднительна и лишь недавно [20] был предложен сложный комплекс алгоритмов, использующих к примеру SMT-решатели для повышения точности выдаваемых пре- дупреждений.

Напомним, что для образцов первого порядка М. Туллсена, как уже упоминалось в 3.1, разумной аппроксимации можно добиться, только расширив их определение.

Для образцов представляемых объектами [5] ситуация с проверкой на полноту, как упоминалось ранее в 1.3, довольно сложна. Более того семантика языка Scala предписывает вызов функции-экстрактора на каждое сопоставление, не используя мемоизацию, и дозволяет использование эффектов, что, как описано в [17], кратно усложняет определения полноты и избыточности ветвей. Вопрос, какой аппроксима- ции можно добиться при таком дизайне остается открытым.

 

          3.2.5.    Эффективность

Эффективная компиляция сопоставлений над абстрактными типами данных это ма- лоизученная и открытая область для исследований. Для активных образцов (а равно и для представлений SML,выразимых через них) в оригинальной работе [23] представ- лена компиляция в деревья решений [22], и авторы утверждают, что можно добиться лучших результатов, используя дополнительные возможности платформы, такие как кортежи, передаваемые на стеке (unboxed tuples). Также открытым остается вопрос, какие из расширенного ряда оптимизаций, представленных в [12],применимы для активных образцов. Для комплекса расширений языка Haskell неизвестно публика- ций, раскрывающих вопросы оптимальной компиляции. Необходимо также учиты- вать различия стратегий вычисления и виды разрешенных алгебраических эффектов, используемых в языках, что еще более усложняет объективные сравнения.

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

Для решения этих проблем можно предложить два подхода:

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

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

и позволит представлять сопоставления более декларативно для пользователей. Тем не менее при активном использовании абстракций и раздельной компиля- ции, разумеется, компилятор не сможет применять большинство оптимизаций,  и машинный код выродится в аналогичный порождаемому при использовании образцов первого порядка. Поэтому по-прежнему для генерации максимально эффективного кода необходимо использовать либо встраивание времени лин- ковки (LTO), либо более общее динамическое встраивание времени исполнения (динамическая компиляция).

 

3.3.     Заключительный выбор

Согласно данным таблицы 1 наиболее привлекательным для реализации представ- ляется оператор возврата. Просто удивительно, насколько самое тривиальнейшее ре- шение оказываетсясамым технически мощным! Вполне в духе языка программиро- вания C.

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

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

4.      Реализация активных образцов в OCaml

 

4.1.     Расширение синтаксиса

Для представления синтаксиса мы воспользуемся грамматикой из [24]. Необходимые модификации для внедрения структурированных имен следующие:

structuredname  ::=

# Однозначный полный образец '(|' capitalizedident '|)'

# Многозначный полный образец

| '(|' capitalizedident '|' '_' '|)'

valuename  ::=  ...

| structuredname

Необходимые модификации синтаксиса образцов следующие:

pattern ::= ...

# Параметризованный частичный образец

| '<' capitalizedident expr {expr} '>' pattern

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

Таким образом, необходимо лишь ввести дополнительный синтаксис для пара- метризованных частичных образцов. Заметьте, что здесь мы отходим от синтаксиса, представленного воригинальной работе: образец вместе с параметрами должен быть заключен в угловые скобки. Причина этому следующая: синтаксически категории вы- ражений и образцов значительно пересекаютсяи LALR(1)-парсер не сможет понять, стоит ли перед ним еще один параметр-выражение, либо уже образец: к примеру, является ли Pat 42 частичным образцом с параметром 42, после которогопоследует что-то еще, либо образцом-конструктором, тело которого сопоставляется с константой 42? У этой проблемы есть 2 возможных решения:

Наконец, можно произвести окончательный, объективный насколько возможно выбор расширения для реализации.

В данном разделе мы представим формализацию выбранного к реализации расшире- ния активных образцов в контексте языка OCaml.

| '(|' capitalizedident {'|' capitalizedident} '|)'} # Однозначный частичный образец

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

2.     Либо мы синтаксически ограничиваем возможные выражения до уровня образ- цов, и все разбираем как образцы, а затем, на последующих фазахкомпиляции, преобразовываем часть результатов в категории выражений. Это подход при- нятый в F# и позволяющий избежать необходимостииспользования обрамляю- щих символов. Однако такой код тяжело читать и человеку, поскольку образцы и параметры вне контекста неотличимы. Иощущается довольно странным, что в качестве параметров частичного образца нельзя передавать то, что можно пе- редавать в качестве аргументов вызова функции, например лямбды или элемент массива по индексу.

 

4.2.     Правила типизации

К сожалению, для языка OCaml статическая семантика в литературе не представле- на. Поэтому добиться математической строгости в рамках данной работы априори не получится, ввиду того, чтострогость требует введения большого количества инстру- ментов и определений. По этим, а также по другим причинам, описанным в исход- ной работе [23], единственным способом для презентации правил типизации является представление эталонного типизирующего анализатора, производящего типизирован- ное абстрактное синтаксическое дерево для исходного кода.

Такой анализатор является частью прототипа, реализованного в рамках данной работы и представлен в [3].

 

4.3.     Модификация схемы компиляции

Напомним, что для поддержания необходимых теоретических свойств активным образцам предписывается мемоизирующая семантика [17]: для каждого вхождения активного образца,соответствующая ему трансформирующая функция вызывается не более одного раза. Таким образом, необходимо для начала выписать все вхождения активных образцов, а затем для каждого из нихвыделить переменные для сохранения результатов вызова.

Вхождения (occurences) мы определяем формально в соответствии с работой [12]: вхождение o есть либо пустой вектор Λ, либо число k, за которым следует вхождение ol: k · ol. Вхождения задают пути к подтермам терма v в следующем смысле:

В данном разделе мы представим необходимые модификации для оптимизированной схемы компиляции в автоматы поиска с возвратами [10].

v/Λ = v c(v1, . . . , vn)/k · o = vk/o

Теперь необходимо дополнить схему C двумя параметрами — вхождением o, кото- рое призвано отслеживать, с какой частью входного выражения сейчас производится сопоставление и кэш-картой amap. Обновленная кэш-карта также будет возвращать- ся схемой в качестве третьего элемента, в дополнение к сгенерированному коду и таблице контекстов.

Вхождение изменяется естественным образом:

    Для правил схемы компиляцией, выполняющих специализацию (таких как пра- вило конструктора (constructor-rule)), выполняемые рекурсивные вызовыдолж- ны получать специализацию вхождения по конструктору (такая операция фор- мально описана в работе [12]).

    Для рекурсивного вызова правила переменных (variable-rule), вхождение инкре- ментируется.

    Для компиляции матрицы P ll → Lll правила или-образцов (orpat-rule) [10] вхож- дение также инкрементируется.

    В разделяющем правиле вхождение передается рекурсивным вызовам неизмен- ным.

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

let  (|M1|M2|) x = (∗ ... ∗)

let  (|C|_|)     x = (∗ ... ∗)

let  (|D|_|)     x = (∗ ... ∗)

let _ =

match  (1, 2, 3) with

| (M1 _, C  _, 42)  1

| (M2 _, D  _,   x)  2

| (C   _, _   ,   _)  3

5.      Заключение

В данной работе были достигнуты следующие результаты:

 

1.     Произведён подробный разбор аналогов в большинстве наиболее передовых функ- циональных языков, выделены решения, которые могут служить опорой для дизайна расширений для языка OCaml:

    Представлено расширение оператора возврата к сопоставлению.

    Введены объективные критерии для сравнения существующих решений и произведен анализ каждого из расширений, представленных как кандида- ты к реализации, обоснован выбор активных образцов как расширения для реализации.

    Инициирован RFC [2] по реализации активных образцов в качестве расши- рения языка OCaml; текущие наработки по реализации должны послужить в качестве образца синтаксиса и семантики для RFC.

2.     Формализованы следующие изменения, необходимые к внесению в компилятор языках OCaml для поддержки активных образцов:

    расширение синтаксиса

    правила типизации новых конструкций

    модификация схемы компиляции [10] для активных образцов

3.     Реализованы следующие элементы прототипа расширения:

    синтаксический анализ

    типизация

Список литературы

[1]    Augustsson Lennart. Compiling pattern matching // Functional Programming Languages and Computer Architecture / Под ред. Jean-Pierre Jouannaud. –– Berlin, Heidelberg : Springer Berlin Heidelberg, 1985. –– С. 368–381.

[2]    Bashkirov Alexander. Active patterns for OCaml. –– 2020. –– Режим доступа: https:

//github.com/ocaml/RFCs/pull/12 (дата обращения: 01.06.2020).

[3]    Bashkirov Alexander. Active patterns for OCaml implementation repository. –– 2020. –– Режим доступа: https://github.com/bash-spbu/ocaml/tree/active_ patterns_docker_image (дата обращения: 01.06.2020).

[4]    Brian Goetz. Data Classes and Sealed  Types  for  Java. ––  2019. ––  Режим  досту- па: https://cr.openjdk.java.net/~briangoetz/amber/datum.html (дата обра- щения: 20.05.2020).

[5]    Emir Burak, Odersky Martin, Williams John. Matching Objects with Patterns // Proceedings of the 21st European Conference on Object-Oriented Programming. –– ECOOP’07. –– Berlin, Heidelberg : Springer-Verlag, 2007. –– С. 273–298. –– Режим до- ступа: http://dl.acm.org/citation.cfm?id=2394758.2394779.

[6]    Erwig Martin, Peyton Jones Simon. Pattern Guards  and  Transformational  Patterns  //  Haskell  Workshop  2000.  ––  2000.  ––  September.   ––   Режим   доступа: https://www.microsoft.com/en-us/research/publication/ pattern-guards-and-transformational-patterns/.

[7]    GHC Wiki. Haskell view patterns. –– Режим доступа: https://gitlab.haskell. org/ghc/ghc/wikis/view-patterns (дата обращения: 20.05.2020).

[8]    IntelliJ           Scala.           Демонстрация         невыразительности             сопоставле- ния    на    примере    конвертера    из    языка    Scala     в     UAST.   ––     Ре-     жим доступа:       https://github.com/JetBrains/intellij-scala/blob/ 878ef964b1bb9dc535ed0bb42ad7ea0731427065/scala/uast/src/org/jetbrains/plugins/scala/lang/psi/uast/converter/Scala2UastConverter.scala#L366

[9]    Kotlin Language Documentation. Control Flow: When Expression. –– 2019. –– Режим доступа: https://kotlinlang.org/docs/reference/control-flow.html# when-expression (дата обращения: 20.05.2020).

[10]    Le Fessant Fabrice, Maranget Luc. Optimizing Pattern Matching // ACM SIGPLAN Notices. –– 2001. –– 08. –– Т. 36.

[11]    Maranget Luc. Warnings for pattern matching // Journal of Functional Programming. –– 2007. –– 05. –– Т. 17. –– С. 387–421.

[12]    Maranget Luc. Compiling Pattern Matching to Good Decision Trees // Proceedings of the 2008 ACM SIGPLAN Workshop  on  ML. ––  ML  ’08. ––  New  York,   NY,  USA : ACM, 2008. –– С. 35–46. –– Режим доступа: http://doi.acm.org/10.1145/ 1411304.1411311.

[13]    Microsoft Docs. C# Pattern Matching. –– 2019. –– Режим доступа: https:// docs.microsoft.com/en-us/dotnet/csharp/pattern-matching (дата обращения: 20.05.2020).

[14]    Milner Robin, Tofte Mads, Macqueen David. The Definition of Standard ML. –– Cambridge, MA, USA : MIT Press, 1997. –– ISBN: 0262631814.

[15]    OCaml Community. if-let for OCaml. –– Режим доступа: https://github.com/ ocaml/ocaml/pull/194 (дата обращения: 20.05.2020).

[16]    OCaml   Discuss.   Musings    on    extended    pattern-matching    syntaxes.   ––  2019.         ––               Режим                                  доступа:               https://discuss.ocaml.org/t/ musings-on-extended-pattern-matching-syntaxes/3600 (дата обращения: 20.05.2020).

[17]    Okasaki Chris. Views for Standard ML // In SIGPLAN Workshop on ML. –– 1998. –– С. 14–23.

[18]    Oleg Kiselyov. How OCaml type checker works – or what polymorphism and garbage collection have in common. –– Режим доступа: http://okmij.org/ftp/ML/ generalization.html (дата обращения: 20.05.2020).

[19]    Pattern Synonyms / Matthew Pickering, Gergo Érdi, Simon  Peyton  Jones,  Richard A. Eisenberg // Haskell’16. –– 2016. –– September. –– Режим доступа: https:

(дата обращения: 20.05.2020).

[20]    Peyton Jones Simon, Graf Sebastian, Scott Ryan. Lower your guards: a compositional pattern-match coverage checker. –– 2020. –– March. –– In submission. Режим доступа: https://www.microsoft.com/en-us/research/publication/ lower-your-guards-a-compositional-pattern-match-coverage-checker/.

[21]    The Rust reference : Intern report / Inria ; исполн.: Community Rust : 2020. –– Режим доступа: https://doc.rust-lang.org/reference/index.html.

[22]    Scott Kevin D, Ramsey Norman. When Do Match-compilation Heuristics Matter? // Technical Report CS-2000-13. –– 2000.

[23]    Syme   Don,   Neverov   Gregory,   Margetson   James.   Extensible   pattern matching via a lightweight  language  extension  //  Proceedings  of  the  12th  ACM SIGPLAN international conference on Functional programming. –– Association  for  Computing  Machinery,  Inc.,  2007.  ––  October.  ––  Режим доступа: https://www.microsoft.com/en-us/research/publication/ extensible-pattern-matching-via-a-lightweight-language-extension/.

[24]    The OCaml system release 4.09: Documentation and user’s manual : Intern report / Inria ; исполн.: Xavier Leroy, Damien Doligez, Alain Frisch и др. : 2019. –– Сент. –– С. 1–789. Режим доступа: https://hal.inria.fr/hal-00930213.

[25]    Tullsen Mark. First Class Patterns // In 2nd International Workshop on Practial Aspects of Declarative Languages, volume 1753 of LNCS. –– Springer-Verlag,2000. –– С. 1–15.

[26]    Wadler P. Views: A Way for Pattern Matching to Cohabit with Data Abstraction // Proceedings of the 14th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages. –– POPL ’87. –– New  York,  NY,  USA  :  ACM,  1987. ––  С. 307–313. –– Режим доступа: http://doi.acm.org/10.1145/41625.41653.