Аннотация:
Язык программирования Nemerle заинтересовал многих в первую очередь своей мощнейшей подсистемой макросов. Однако и без них Nemerle предоставляет ряд существенных улучшений по сравнению с традиционными, императивными языками программирования (такими как Java, C# и C++).
Nemerle, кроме традиционного императивного программирования, поддерживает функциональное программирование. Это выражается в наличии конструкций, упрощающих манипуляцию функциями, построение и анализ сложных структур данных и т.п.
К сожалению, если вы не использовали возможности, присущие функциональным языкам ранее, то вам будет трудно оценить, насколько Nemerle может оказаться вам полезным в реальной повседневной работе. Данная статья призвана в неформальной форме продемонстрировать это.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
ЧВV> с незаменяемыми полями
вероятно все же неизменяемыми, ... хотя
ЧВV> отряд не заменит потери бойца
так даже больше нравится, но вроде бы по контексту должна быть классика
ЧВV> Так, все поля Variant Option ... обязаны быть публичными, и по умолчанию являются изменяемыми
опечатка, "не" пропущено
ЧВV> но в основном их применение обычно обосновано и происходит из-за того, что программист просто пока не привык использовать неизменяемые структуры данных.
аналогично
ЧВV> Если функция начинается с выражения match, ...
все же точнее будет "Если тело функции состоит из выражения match, ..."
// Другой вариант частичного применения. В этот раз первому параметру
// назначен строковый литерал.
def f = WriteLine("Еще один вариант - '{0}'", _);
f("частичного применения функции");
компилятор у меня матерится следующим образом
in argument #1 of f, needed a array [System.Object]+, got string: string is not a subtype of array [System.Object] [simple require]
Здравствуйте, AngeL B., Вы писали:
AB>Не компилируется пример из статьи. На кусок AB>
AB>// Другой вариант частичного применения. В этот раз первому параметру
AB>// назначен строковый литерал.
AB>def f = WriteLine("Еще один вариант - '{0}'", _);
AB>f("частичного применения функции");
AB>
AB>компилятор у меня матерится следующим образом AB>in argument #1 of f, needed a array [System.Object]+, got string: string is not a subtype of array [System.Object] [simple require]
AB>версия компилятора 0.9.3. AB>Что и где не так?
Не очень хорошо вышло, в 0.9.3 баг, в trunk версии бага нет, как минимум с r7392 (самое старое что есть под рукой).
Варианты исправления:
1) забрать из svn последнюю версию
2) забрать свежий daily snapshot
3) исправить пример
def f = x : string => WriteLine("Еще один вариант - '{0}'", x);
f("частичного применения функции");
Причем в случаях 1 и 2 даже необязательно собирать компилятор, бинарники в boot там этого бага не имеют.
Все отлично, статья очень понравилась, но хотелось бы как-то раскрыть понятие "замыкания" для людей, до этого не имевших дело с ФП. Это что физически такое?
Как я понял — средство упаковки функции вместе с некоторым количеством локальных переменных (аргументов функции и т.п.) в единый объект? Или я не прав?
Здравствуйте, x-code, Вы писали:
XC>Все отлично, статья очень понравилась, но хотелось бы как-то раскрыть понятие "замыкания" для людей, до этого не имевших дело с ФП. Это что физически такое?
.
XC>Как я понял — средство упаковки функции вместе с некоторым количеством локальных переменных (аргументов функции и т.п.) в единый объект? Или я не прав?
Примерно, прав.
Help will always be given at Hogwarts to those who ask for it.
Или попробовать одну из CTP-версий интеграции (к сожалению последняя версия не досутпна, но можно попробовать апрельскую Nemerle APR 2007 CTP (вторая попытка)
Здравствуйте, x-code, Вы писали:
XC>Все отлично, статья очень понравилась, но хотелось бы как-то раскрыть понятие "замыкания" для людей, до этого не имевших дело с ФП. Это что физически такое? XC>Как я понял — средство упаковки функции вместе с некоторым количеством локальных переменных (аргументов функции и т.п.) в единый объект? Или я не прав?
Именно так. Рассматривать их лучше не как объекты, а как отдельную абстракцию, но взгляд на замыкания как на эдакие инлайн-объекты тоже имеет право на жизнь. Собственно так и делается в компиляторе Nemerle. Но все может измениться. В принципе хороший оптимизирующий компилятор может переписывать код программы устраня как такие невидимые классы, так и вызов метода по ссылки. Но это уже детали реализации компиляторов.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, x-code, Вы писали:
XC>Все отлично, статья очень понравилась, но хотелось бы как-то раскрыть понятие "замыкания" для людей, до этого не имевших дело с ФП. Это что физически такое?
По поводу интерпретатора арифметических выражений:
В C# версии реализован паттерн Visitor, а в Nemerle версии нет.
А было бы "замечательно" если бы у каждого варианта был бы метод Accept.
Вот аналог того, что в Nemerle на C#
using System;
abstract class Expression
{
public virtual double Eval()
{
Literal literal = this as Literal;
if (literal != null)
{
return literal.Value;
}
Operation operation = this as Operation;
if (operation != null)
{
if (this is Min) return Math.Min(operation.First.Eval(), operation.Second.Eval());
if (this is Max) return Math.Max(operation.First.Eval(), operation.Second.Eval());
if (this is Plus) return operation.First.Eval() + operation.Second.Eval();
if (this is Minus) return operation.First.Eval() - operation.Second.Eval();
if (this is Mul) return operation.First.Eval() * operation.Second.Eval();
if (this is Div) return operation.First.Eval() / operation.Second.Eval();
}
throw new InvalidOperationException(ToString());
}
public new virtual string ToString()
{
Literal literal = this as Literal;
if (literal != null)
{
return literal.Value.ToString();
}
Operation operation = this as Operation;
if (operation != null)
{
if (this is Min) return"Min(" + operation.First.ToString() + ", " + operation.Second.ToString() + ")";
if (this is Max) return"Max(" + operation.First.ToString() + ", " + operation.Second.ToString() + ")";
if (this is Plus) return operation.First.ToString() + " + " + operation.Second.ToString();
if (this is Minus) return operation.First.ToString() + " - " + operation.Second.ToString();
if (this is Mul) return operation.First.ToString() + " * " + operation.Second.ToString();
if (this is Div) return operation.First.ToString() + " / " + operation.Second.ToString();
}
throw new InvalidOperationException(base.ToString());
}
}
class Literal: Expression
{
private double value;
public double Value { get { return value; } }
public Literal(double value) { this.value = value; }
}
abstract class Operation: Expression
{
private Expression first, second;
public Expression First { get {return first; } }
public Expression Second { get {return second; } }
public Operation(Expression first, Expression second)
{
this.first = first;
this.second = second;
}
}
class Min: Operation { public Min (Expression first, Expression second): base(first, second) {} }
class Max: Operation { public Max (Expression first, Expression second): base(first, second) {} }
class Plus: Operation { public Plus (Expression first, Expression second): base(first, second) {} }
class Minus: Operation { public Minus(Expression first, Expression second): base(first, second) {} }
class Mul: Operation { public Mul (Expression first, Expression second): base(first, second) {} }
class Div: Operation { public Div (Expression first, Expression second): base(first, second) {} }
public class Program
{
public static void Main(string[] arguments)
{
Expression expression = new Plus(new Literal(1.23), new Min(new Literal(3.45), new Literal(1.23)));
Console.WriteLine("Expression '{0}' = {1}", expression.ToString(), expression.Eval());
}
}
Здравствуйте, <Аноним>, Вы писали:
А>Здравствуйте, Чистяков Влад (VladD2), Вы писали:
А>По поводу интерпретатора арифметических выражений:
А>В C# версии реализован паттерн Visitor, а в Nemerle версии нет.
Естественно, так как он попросту не нужен при наличии паттерн-матчинга.
А>А было бы "замечательно" если бы у каждого варианта был бы метод Accept.
Не вижу ничего замечательного в этом. Использование данного паттерна — это проектное решение. В чистом ООП оно имеет смысл, так как упрощает равитие системы, при наличии паттерн-матчинга оно попросту бессмысленно.
А>Вот аналог того, что в Nemerle на C#
Это как раз поптыка эмулировать паттерн-матчинг на C#. Только у этого решенпия есть две прблемы которые заставят отказаться от него.
1. Нет никаких гарантий, что обработаны все варианты. О том, что какой-то варинат не обработан будет известно только в рантайме. Стало быть мы получаем самые плохие стороны динамики в казалось бы статически типизированном языке. Вариант с паттерн-мачингом дает нам такие гарантии (компилятор проверяет полноту паттернов).
2. Это очень примитивный случай. В нем паттерн-матчинг действительно не так сложно эмулировать на операторах is/as. Но с разрастанием кода станет очевидным, что эмуляция хуже оригинала. Как я говорил в статье, паттерн-матчинг поддерживеет сложные вложенные паттерны, что значительно увеличивает наши возможности. Например, если нам понадобиться распознать некий паттерн в коде (ну скажем упростить мат.выражения), то с паттерн-матчингом прийдется добавить всего пару строк кода, а в императивном коду это уже красиво не выразить. Прийдется лепить кучи if-ов.
Ну, и надо заметить, что кода все равно значительно больше чем на Nemerle, хотя и не так много как в чисто ОО-решении. К тому же в нем ради "укомпакчивания" кода бвли допущены разные вольности вроде наличия Value у всех выражений хотя только оно нужно только у литералов.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Функциональное программирование в Nemerle
От:
Аноним
Дата:
14.05.07 16:58
Оценка:
Здравствуйте, VladD2, Вы писали:
VD>В чистом ООП оно имеет смысл, так как упрощает равитие системы, при наличии паттерн-матчинга оно попросту бессмысленно.
А главное отжирает по три строке в каждом классе. Я к тому, что в том примере C# и Nemerle сравнивались нечестно, за счет чего и получилась, столь впечатляющая разница в объеме кода в строках. В килобайтах, кстати не так много, не в 5 раз, а в 3.
VD>2. Это очень примитивный случай. В нем паттерн-матчинг действительно не так сложно эмулировать на операторах is/as. Но с разрастанием кода станет очевидным, что эмуляция хуже оригинала. Как я говорил в статье, паттерн-матчинг поддерживеет сложные вложенные паттерны, что значительно увеличивает наши возможности. Например, если нам понадобиться распознать некий паттерн в коде (ну скажем упростить мат.выражения), то с паттерн-матчингом прийдется добавить всего пару строк кода, а в императивном коду это уже красиво не выразить. Прийдется лепить кучи if-ов.
Ну и где такой пример, который бы с одной стороны имел достаточную сложность для показа превосходства Nemerle, а с другой не являлся бы, например, исходниками компилятора Nemerle.
VD>Ну, и надо заметить, что кода все равно значительно больше чем на Nemerle, хотя и не так много как в чисто ОО-решении.
В этом и мысль. Конечно превосходство Nemerle очевидно при сравнении 250 строк кода на С# и 50 строк кода на Nemerle, однако если аналогичный код на С# легко умещается в 80 строк, преимущество Nemerle испаряется.
VD>К тому же в нем ради "укомпакчивания" кода бвли допущены разные вольности вроде наличия Value у всех выражений хотя только оно нужно только у литералов.
Этого нет, Value только у Literal'ов.
Здравствуйте, <Аноним>, Вы писали:
VD>>2. Это очень примитивный случай. В нем паттерн-матчинг действительно не так сложно эмулировать на операторах is/as. Но с разрастанием кода станет очевидным, что эмуляция хуже оригинала. Как я говорил в статье, паттерн-матчинг поддерживеет сложные вложенные паттерны, что значительно увеличивает наши возможности. Например, если нам понадобиться распознать некий паттерн в коде (ну скажем упростить мат.выражения), то с паттерн-матчингом прийдется добавить всего пару строк кода, а в императивном коду это уже красиво не выразить. Прийдется лепить кучи if-ов. А>Ну и где такой пример, который бы с одной стороны имел достаточную сложность для показа превосходства Nemerle, а с другой не являлся бы, например, исходниками компилятора Nemerle.
См. исходники здесь. Особое внимание советую обратить на src/Common/Bnf/BnfUtils.n. И заодно рекомендую прикинуть, как будет выглядеть аналогичный код на C#, и сколько времени уйдёт на его написание.
VD>>Ну, и надо заметить, что кода все равно значительно больше чем на Nemerle, хотя и не так много как в чисто ОО-решении. А>В этом и мысль. Конечно превосходство Nemerle очевидно при сравнении 250 строк кода на С# и 50 строк кода на Nemerle, однако если аналогичный код на С# легко умещается в 80 строк, преимущество Nemerle испаряется.
Это эчень искусственно. В данном случае имеет место ужимание строк. Если честно эмулировать паттерн-матчинг, то строк должно быть около 120. Более чем в 2 раза — уже достойный результат. Причём, это C# так хорошо отделался на простом примере, дай ему что-то посложнее, и он быстро обломается.
Кстати, советую ещё обратить внимание на слова Влада про то, что компилятор Nemerle проверяет, все ли варианты отловлены и предупреждает в случае необходимости. А подчас помощь компилятора в нахождении ошибок за программиста — это гораздо больший плюс, нежели лаконичность.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[5]: Функциональное программирование в Nemerle
От:
Аноним
Дата:
14.05.07 20:55
Оценка:
Здравствуйте, konsoletyper, Вы писали:
K>Здравствуйте, <Аноним>, Вы писали:
K>См. исходники здесь. Особое внимание советую обратить на src/Common/Bnf/BnfUtils.n. И заодно рекомендую прикинуть, как будет выглядеть аналогичный код на C#, и сколько времени уйдёт на его написание.
Гляну.
K>Если честно эмулировать паттерн-матчинг, то строк должно быть около 120. Более чем в 2 раза — уже достойный результат.
Не вижу, где нечестно и чего нехватает.
K>Причём, это C# так хорошо отделался на простом примере, дай ему что-то посложнее, и он быстро обломается.
Из данного примера облом не очевиден.
K>Кстати, советую ещё обратить внимание на слова Влада про то, что компилятор Nemerle проверяет, все ли варианты отловлены и предупреждает в случае необходимости. Я обратил. Пока думаю, как реализовать это на C#.
Re[6]: Функциональное программирование в Nemerle
От:
Аноним
Дата:
14.05.07 21:13
Оценка:
Здравствуйте, Аноним, Вы писали:
А>Я обратил. Пока думаю, как реализовать это на C#.
Достаточно перенести код для ToString и Eval в методы класса, тем самым если появится новый класс — то эти методы придется реализовать
using System;
abstract class Expression
{
public abstract double Eval();
public override abstract string ToString();
}
class Literal: Expression
{
private double value;
public double Value { get { return value; } }
public Literal(double value) { this.value = value; }
public override double Eval() { return value; }
public override string ToString() { return value.ToString(); }
}
abstract class Operation: Expression
{
private Expression first, second;
public Expression First { get {return first; } }
public Expression Second { get {return second; } }
public Operation(Expression first, Expression second)
{
this.first = first;
this.second = second;
}
}
class Min: Operation
{
public Min (Expression first, Expression second): base(first, second) {}
public override double Eval() { return Math.Min(First.Eval(), Second.Eval()); }
public override string ToString() { return"Min(" + First.ToString() + ", " + Second.ToString() + ")"; }
}
class Max: Operation
{
public Max(Expression first, Expression second): base(first, second) {}
public override double Eval() { return Math.Max(First.Eval(), Second.Eval()); }
public override string ToString() { return"Max(" + First.ToString() + ", " + Second.ToString() + ")"; }
}
class Plus: Operation
{
public Plus(Expression first, Expression second): base(first, second) {}
public override double Eval() { return First.Eval() + Second.Eval(); }
public override string ToString() { return First.ToString() + " + " + Second.ToString(); }
}
class Minus: Operation
{
public Minus(Expression first, Expression second): base(first, second) {}
public override double Eval() { return First.Eval() - Second.Eval(); }
public override string ToString() { return First.ToString() + " - " + Second.ToString(); }
}
class Mul: Operation
{
public Mul(Expression first, Expression second): base(first, second) {}
public override double Eval() { return First.Eval() * Second.Eval(); }
public override string ToString() { return First.ToString() + " * " + Second.ToString(); }
}
class Div: Operation
{
public Div (Expression first, Expression second): base(first, second) {}
public override double Eval() { return First.Eval() / Second.Eval(); }
public override string ToString() { return First.ToString() + " / " + Second.ToString(); }
}
public class Program
{
public static void Main(string[] arguments)
{
Expression expression = new Plus(new Literal(1.23), new Min(new Literal(3.45), new Literal(1.23)));
Console.WriteLine("Expression '{0}' = {1}", expression.ToString(), expression.Eval());
}
}
Здравствуйте, <Аноним>, Вы писали:
VD>>В чистом ООП оно имеет смысл, так как упрощает равитие системы, при наличии паттерн-матчинга оно попросту бессмысленно. А>А главное отжирает по три строке в каждом классе. Я к тому, что в том примере C# и Nemerle сравнивались нечестно, за счет чего и получилась, столь впечатляющая разница в объеме кода в строках. В килобайтах, кстати не так много, не в 5 раз, а в 3.
Ох уж мне эти приверженцы единственно правильных решений.
Отжирается конечно не по три строки. В приличном ОО-обществе принято каждый класс помещать в тдельный файл, а значит для каждого из них дублируется просторанство имен, using-и и прочая дрибедень. Здесь svn://rsdn.ru/RSharp находятся исходники R# который я создал до знакомства с Nemerle. Уверя, что если бы я писал подобный код сейчас и на Nemerle, то он был бы не в 3 или 5 раз меньше, а раз в 10.
VD>>2. Это очень примитивный случай. В нем паттерн-матчинг действительно не так сложно эмулировать на операторах is/as. Но с разрастанием кода станет очевидным, что эмуляция хуже оригинала. Как я говорил в статье, паттерн-матчинг поддерживеет сложные вложенные паттерны, что значительно увеличивает наши возможности. Например, если нам понадобиться распознать некий паттерн в коде (ну скажем упростить мат.выражения), то с паттерн-матчингом прийдется добавить всего пару строк кода, а в императивном коду это уже красиво не выразить. Прийдется лепить кучи if-ов. А>Ну и где такой пример, который бы с одной стороны имел достаточную сложность для показа превосходства Nemerle, а с другой не являлся бы, например, исходниками компилятора Nemerle.
Понимаю, что мышление человека инерно. По этому предлагаю начать с малого. Давай расширим оба примера поддержкой фунций с 3 и 5 параметрами. Причем на этот раз ты продемонстриуешь свое решение первым.
Вторая здачка будет интереснее. Произвольная трансформация выражений. Берем исходное выражение применяем к нему некоторую функцию в параметах которой задем условия трасформации и получаем на выходе трансформированное (оптимизированное выражение). Для простоты, в качестве теста, будем использовать следующую оптимизацию — если выражение преставляет из себя "<любое выражение> * 1", то заменяем его на "<любое выражение>". Причем подразумевается, что это всего лишь один из возможных вариантов замены. Другими словами вызвав код вида:
expr = expr.Convert(<условия преобразования>);
мы должны получить преобразованное выражение.
Например:
До трансформации
Expression '1.23 + max(1, 2) * 1' = 3.23
После трансформации
Expression '1.23 + max(1, 2)' = 3.23
Задача понятна?
Теперь ход что называется за тобой. Ты представляешь свое решение, а затем я демонструю свое. Кстити, свое я уже написал .
VD>>Ну, и надо заметить, что кода все равно значительно больше чем на Nemerle, хотя и не так много как в чисто ОО-решении. А>В этом и мысль.
В чем? В том, что ты потерял все приемущество ООП, но все равно не достиг такой же ясности и простоты кода?
А> Конечно превосходство Nemerle очевидно при сравнении 250 строк кода на С# и 50 строк кода на Nemerle, однако если аналогичный код на С# легко умещается в 80 строк, преимущество Nemerle испаряется.
Знаешь, многие были бы счасливы иметь 50 строк вместо 80, но на практике предпочитают 250, так как пользоваться инструментами вроде Nemerle они не умеют или боятся, а используя ООЯ прибегают к классическим паттернам проектирования приводящим именно к 250-строчному коду. И я их понимаю (правда, понимаю только в выборе паттернов, но не инструмента и парадигмы).
VD>>К тому же в нем ради "укомпакчивания" кода бвли допущены разные вольности вроде наличия Value у всех выражений хотя только оно нужно только у литералов. А>Этого нет, Value только у Literal'ов.
Ой. Сори. Это я невнимательно смотрел код. Ну, да когда ты попыташся добавить поддеркжу метода с 3 и т.п. параметрами ты увидишь, что косяки поползут сами собой. А уж пример с оптимизацией, надесь, приведет к полному прозрению.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.