Nemerle — гибридный язык высокого уровня со статической типизацией, сочетающий в себе возможности функционального и объектно-ориентированного программирования, для платформ .NET и Mono (язык компилируется в CIL и является CLS-совместимым). Главная особенность языка — развитая система метапрограммирования.
История Разработка языка Nemerle началась в 2003 году в университете Вроцлава (Польша). Команда разработчиков состояла всего из трёх человек, все они на момент начала разработки были аспирантами Вроцлавского университета. Михал Москаль — лидер команды и автор системы вывода типов, Камиль Скальски — разработчик системы макросов и расширяемого парсера, и Павел Ольшта — автор кодогенератора и реализации механизма сопоставления с образцом.
Название языка происходит от имени мага Nemmerle из «Волшебника Земноморья» Урсулы Ле Гуин.
Язык изначально проектировался под платформу .NET. 12 марта 2010 года была выпущена первая бета-версия компилятора языка, поддерживающая работу приложений под управлением .NET 3.5. Язык, его реализация и документация выпускаются под свободной BSD-подобной лицензией, допускающей свободное использование их в любых целях.
Первый релиз (версия 1.0) комплекта ПО для программирования на Nemerle выпущен 13 мая 2011 года, на текущий момент самой свежей является версия 1.2, работающая на платформе .NET 4.0. В комплект, доступный для свободного скачивания, входит инсталлируемый компилятор языка и набор библиотек для встраивания в Microsoft Visual Studio 2008, отдельный компилятор (для работы без Visual Studio), экспериментальная версия ПО, интегрируемого с Visual Studio 2010, а также исходные коды. Текущая версия компилятора поддерживает включение в проект кода на C# 6.0, при условии неиспользования небезопасного кода.
С июня 2012 года команда разработчиков Nemerle стала частью компании JetBrains, которая займётся дальнейшей разработкой и поддержкой языка.
Характеристика языка Nemerle позиционируется как язык общего назначения. Он сочетает в себе несколько парадигм программирования: объектно-ориентированное программирование, императивное программирование, функциональное программирование и метапрограммирование. Из-за такого сочетания концепций, написание программ на Nemerle возможно используя разные стили: в императивном подходе код будет аналогичен коду программ C# (за исключением некоторых нюансов напр. указания типа), в функциональном подходе исходный код будет родственен языкам семейства ML (ML, OCaml, F#, Haskell), включая их особенности: функции высшего порядка сопоставление с образцом (pattern matching) алгебраические типы локальные функции кортежи и анонимные типы частичное применение
Вдобавок к императивным и функциональным парадигмам, Nemerle обладает мощной системой макросов, которые предоставляют пользователю возможность добавлять новые конструкции в язык и описывать решение задач в декларативном стиле с помощью создания собственных предметно-ориентированных языков программирования (DSL).
Особенности Характерной особенностью Nemerle, как и всех языков, типизированных по Хиндли — Милнеру является мощная система вывода типов.
Вывод типов def x = 1; // int def myList = List(); // generic List[T], T должен выводиться из дальнейшего использования myList.Add(x); // благодаря этой строке компилятор определяет тип myList как List[int]
Все является выражением def x = { // эквивалент x = 3 def y = 1; def z = 2; y + z // последнее выражение в блоке является значением блока
def x = if (DateTime.Now.DayOfWeek == DayOfWeek.Monday) // if, using, try - тоже являются выражениями "Monday" else "other day";
def x = try { Int32.Parse(someString) } catch { | FormatException() => 0; }
Кортежи def k = (1, "one"); // k : (int * string) def (a, b) = k; // a = 1, b = "one"
Сопоставление с образцом def result = match (number) { | 0 => "zero" | 1 => "one" | x when x < 0 => "negative" | _ => "more than one" }
Функциональные типы и локальные функции def next(x) { x + 1 };
def mult(x, y) { x * y }
def fibbonacci(_) { | 0 => 0 | 1 => 1 | i => fibbonacci(i - 1) + fibbonacci(i - 2) }
Console.WriteLine(next(9)); // 10 Console.WriteLine(mult(2, 2)); // 4 Console.WriteLine(fibbonacci(10)); // 55
Метапрограммирование Nemerle позволяет создавать, анализировать и модифицировать код программы во время компиляции с помощью макросов. Макросы могут быть использованы в виде вызова метода либо в виде новых конструкций языка. Большая часть конструкций в языке реализована с помощью макросов (if, for, foreach, while, using и т. д.).
Пример макроса «if»: macro @if (cond, e1, e2) syntax ("if", "(", cond, ")", e1, Optional (";"), "else", e2) { /* <[ ]> // <[ ]> определяет области квазицитирования, код внутри них преобразуется в AST Немерле, аналогично преобразованию кода в Expression компилятором C# */ <[ match ($cond : bool)
| true => $e1 | _ => $e2
]> }
// Вышеприведённый макрос вводит в язык конструкцию if, def max = if (a > b) a else b; // которая при компиляции раскрывается в def max = match (a > b) { | true => a | _ => b }
Основные концепции Типобезопасные «гигиеничные» макросы и квазицитирование c возможностью расширения синтаксиса. Наличие локальных функций (лексических замыканий). Функция является объектом первого класса. Гарантированная оптимизация хвостовой рекурсии, то есть хвостовая рекурсия всегда заменяется циклом при компиляции. Выведение типов. В частности, возможно выведение типов локальных переменных и выведение сигнатуры локальных функций. Отсутствие четкой границы между инструкцией (statement) и выражением (expression). «Everything is expression». Например, условный оператор может находиться внутри арифметического выражения. Нет необходимости в инструкции return. Блоки кода, упраздняющие необходимость в таких инструкциях, как break или continue. Алгебраические типы данных, кортежи и сопоставление с образцом. Упрощенный синтаксис работы со списками. Списочные литералы. Частичное применение операторов и функций — простая генерация обёртки некоторой функции, в которой часть параметров подставлена заранее, а часть передаётся непосредственно при вызове функции.
Особенности синтаксиса Синтаксис Nemerle очень близок к C#, но имеет ряд характерных отличий, в том числе позаимствованных из функциональных и скриптовых языков. Вот некоторые из них:
Наряду с обычным порядком заключения элементов программы (классов, составных операторов, тел методов) в фигурные скобки, поддерживается альтернативный вариант описания структуры программы, базирующийся на отступах, как в Python. В описании переменных и методов тип указывается в стиле OCaml (похоже на Pascal, но без возможности задать несколько имен переменных) в виде «переменная : тип». Имена могут, помимо букв, цифр и знака подчёркивания, содержать знак апострофа «'». Объявления локальных функций и констант начинаются с def. Наряду с унаследованным от C# объявлением главной функции программы в виде статического метода Main головного класса программы поддерживается прямое размещение кода в файле без помещения его в какой-либо класс или метод. Переменные объявляются с помощью модификатора mutable (а не def). Таким образом, синтаксис поощряет использование неизменяемых объектов, что соответствует идеологии функционального программирования. Создание экземпляра класса выполняется вызовом его конструктора — «ИмяКласса(параметры конструктора)» без необходимости указывать ключевое слово new (как это принято в C#). Команда подключения пространства имён using подключает все вложенные пространства имён, а также позволяет прямо подключить конкретный тип, что даёт возможность использования его статических членов (например, методов классов или констант перечислений) без квалификации имени. Статические классы (классы, имеющие только статические методы и поля) описываются с ключевым словом module вместо class и не требуют указания модификатора static перед каждым членом класса. Введены две отдельные конструкции для статического уточнения и динамического приведения типов (: и :> соответственно). В стандартной библиотеке имеется тип «список» (list), являющийся однонаправленным связанным списком. Списки являются неизменяемыми и ведут себя во многом аналогично строкам C#. Для этого типа в языке предусмотрен специализированный синтаксис, значительно упрощающий работу с ним (литералы, возможность использования списка в сопоставлении с образцом). Введён тип «вариант» (variant) — аналог алгебраических типов в функциональных языках. Введён тип «кортеж» (tuple) — безымянная, неизменяемая структура данных, содержащая несколько (возможно разнотипных) элементов. Для кортежей поддерживается структурное соответствие. Кортежи удобны, когда требуется вернуть несколько значений из функции, свойства или просто организовать любое выражение. Конструкцию переключения switch заменила конструкция сопоставления с образцом match, обладающая большими возможностями (позаимствовано из ML-подобных языков). Гарантировано преобразование компилятором хвостовой рекурсии в цикл. Это позволяет описывать повторяющиеся вычисления рекурсивно, не опасаясь, что применение рекурсии приведёт к исчерпанию памяти. Поддерживается только для одной функции. Поддержка взаимно-рекурсивных функций пока что не реализована. Отсутствует специальный синтаксис (используемый в C#) для финализатора (конструкция «~ИмяКласса()»), как провоцирующий ошибочное понимание у программистов, знакомых с C++. Финализатор описывается как обычный виртуальный метод Finalize(), причём компилятор не обеспечивает автоматический вызов финализатора класса-родителя. Имеется возможность объявления значений используемых по умолчанию в параметрах и синтаксис вызова с явным указанием имён параметров в произвольном порядке. В стандартной библиотеке языка есть три макроса, реализующих условные операторы: if, отличающийся тем, что в нём обязательна ветвь else, when, представляющий собой «if без else», и unless, представляющий собой отрицание when, то есть «when (! условие) выражение». Все условные операторы переписываются соответствующими макросами в оператор match. В параметрах, аргументах и именах переменных допускается использование специального подстановочного символа-заместителя «_». Будучи указанным в начале имени переменной или вместо него (то есть если указан только знак подчеркивания), этот символ подавляет предупреждение компилятора о неиспользованной переменной. Использование подчеркивания вместо имени локальной переменной (например, «_ = f(a, b)») позволяет игнорировать значение вычисления (в тех же целях можно использовать стандартный макрос ignore следующим образом: «ignore(f(a, b))»). C# игнорирует их автоматически, что иногда приводит к ошибкам. Nemerle выдаёт предупреждение, если результат вычисления не используется в дальнейшем. Приведенный прием позволяет указать компилятору, что игнорирование результата сделано не случайно. Почти все языковые конструкции (кроме using, class и т. д.) определены как имеющие значение и могут быть использованы в качестве элементов выражений. Однако, выражение может иметь тип void, что соответствует statement в C#. В языке поддерживается функциональный тип (обычный для функциональных языков). С его помощью можно по месту описать ссылку на функцию. В C# для передачи ссылок на функции используются делегаты. Nemerle также поддерживает их, но функциональный тип является более удобным, гибким и быстрым решением. Он не требует объявления отдельного типа (как делегат) и не может быть множественным (multicast). В языке поддерживается частичное применение функций и операторов, что позволяет создавать функции «на лету». Например, если в некоторую функцию требуется передать другую функцию (например, функцию сложения), то можно частично применить оператор сложения. Выражение «_ + 2» возвращает функцию с одним целочисленным аргументом, прибавляющую к нему значение 2.
Средства метапрограммирования