Re[6]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 14.05.07 21:42
Оценка:
Здравствуйте, <Аноним>, Вы писали:

K>>Если честно эмулировать паттерн-матчинг, то строк должно быть около 120. Более чем в 2 раза — уже достойный результат.

А>Не вижу, где нечестно и чего нехватает.

А ты реализуй предложенные мной расширения и увидишь. Понимаешь, ли в чем дело? Ты изменил дизайн кода в рассчете на конекретные ограничения. Например, функция у тебя имеет принципиально 2 параметра (она же выражение с двумя праметрами), в варинте с паттерн-матчингом это не имеет значения. Вместо паттерна:
Call("max", [arg1, arg2])

всегда можно написать:
Call("max", [arg1, arg2, arg3, arg4, arg5])

Это ничего не стоит в поддержке. У нас же список, а не наследование.

K>>Причём, это C# так хорошо отделался на простом примере, дай ему что-то посложнее, и он быстро обломается.

А>Из данного примера облом не очевиден.

На самом деле конечно же C# не сломается ни на каком примере. Просто чем дальше в лес, тем очевиднее будет, что код на C# далек от идиала.

Примером для посвященных тут является сам компилятор Nemerle. Это всего 1.5 мегабайта кода, но делает этот код коду больше чем делает код трех компиляторов ООЯ вместе взятых.

Но главное даже не то, что код получается значительно компактнее, а то, что его намного проще изменять. Вот два примера из, что называется, совсем ближайшего прошлого:
Добавил поддержку auto-property (как в C# 3.0)
Автор: VladD2
Дата: 09.05.07

Прикольный макрос похожий на предыдущее расширение
Автор: VladD2
Дата: 14.05.07


Исползуй я C# или темболее C++ и вряд ли бы я вообще отважился на добавление подобного расширения. А с паттерн-матчингом и квази-цитированием (супер-мега-фича для генерации кода) — это превратилось в веселое интеллектуальное развлечение на вечер. Точнее первая на вечер, а вторая вообще заняла 20 минут.

Так что исползование С++ в разрботке компилятора C# является настоящим преступленим — преступной халатностью. Ведь имея в своем распоряжении типобезопасный язык с паттерн-матчингом и квази-цитированием они могли бы сократить время воплощения фич с лет до месяцев и даже недель. И я уже не говорю о прототипировании и тестировании.

Но у МС моного делег и они могут позволить себе писать даже на ассемблере (что и происходило во времена ДОС-а). А у большинства из нас таких сверх-ресурсов нет .
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 14.05.07 21:42
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>>Я обратил. Пока думаю, как реализовать это на C#.


А>Достаточно перенести код для ToString и Eval в методы класса, тем самым если появится новый класс — то эти методы придется реализовать


Позрдарвлю ты вернулся к ООП .

Вот только с чем боролись на то и напоролись. От первоначального удобства видеть все однотипные операции вместе не осталось и следа. Теперь прийдется шариться по всем классам.

Следующим шагом будет избавление от большого количества методов в телах АСТ-классов и группировка их в одном месте, то есть реализация паттерна Посетитель. Так поступают 99% создателей компиляторов использующих ООЯ. И я их понимаю.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 14.05.07 23:22
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>См. исходники здесь. Особое внимание советую обратить на src/Common/Bnf/BnfUtils.n. И заодно рекомендую прикинуть, как будет выглядеть аналогичный код на C#, и сколько времени уйдёт на его написание.


Кстати, в файле Common.Tests.nproj убери версию NUnit-а, а то, например, у меня это дело не компилируется. Достаточно:
    <Reference Include="nunit.framework"/>

nunit.framework регистриуется в ГАК-е. Так что полные пути к нему не нужны.

И еще хорошо бы избавиться от варнинков. А то их слишком много. Темболее что у Немерла варнинги — это почти ошибки.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Функциональное программирование в Nemerle
От: Аноним  
Дата: 15.05.07 19:10
Оценка: :)
Здравствуйте, VladD2, Вы писали:

VD>Следующим шагом будет избавление от большого количества методов в телах АСТ-классов и группировка их в одном месте, то есть реализация паттерна Посетитель. Так поступают 99% создателей компиляторов использующих ООЯ. И я их понимаю.


Похоже, Посетитель — плохой паттерн.
Re[5]: Функциональное программирование в Nemerle
От: Аноним  
Дата: 15.05.07 19:16
Оценка: :))
Здравствуйте, VladD2, Вы писали:

VD>Понимаю, что мышление человека инерно. По этому предлагаю начать с малого. Давай расширим оба примера поддержкой фунций с 3 и 5 параметрами. Причем на этот раз ты продемонстриуешь свое решение первым.


VD>Вторая здачка будет интереснее. Произвольная трансформация выражений. Берем исходное выражение применяем к нему некоторую функцию в параметах которой задем условия трасформации и получаем на выходе трансформированное (оптимизированное выражение). Для простоты, в качестве теста, будем использовать следующую оптимизацию — если выражение преставляет из себя "<любое выражение> * 1", то заменяем его на "<любое выражение>". Причем подразумевается, что это всего лишь один из возможных вариантов замены. Другими словами вызвав код вида:

VD>Теперь ход что называется за тобой. Ты представляешь свое решение, а затем я демонструю свое. Кстити, свое я уже написал .

Не мог вечером зайти в форум, какая-то ошибка, связанная с Dictionary Key, поэтому вечером писал по-памяти, что прочитал с утра, так что требования не соблюдены точно, но достаточно изоморфны.

150 строк, пока в c# не разочаровался.

using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;

abstract class Expression
{
  public abstract double Eval();
  public abstract override string ToString();
  public abstract void RemoveOneParameterMin(ref Expression expression);
}

class Literal : Expression
{
  double value;
  public Literal(double value) { this.value = value; }
  public override double Eval() { return value; }
  public override string ToString() { return value.ToString(); }
  public override void RemoveOneParameterMin(ref Expression expression) { }
}

abstract class Operation : Expression
{
  protected 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;
  }
  public override void RemoveOneParameterMin(ref Expression expression)
  {
    first.RemoveOneParameterMin(ref first);
    second.RemoveOneParameterMin(ref second);
  }
}

class Addition : Operation
{
  public Addition(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 Subtraction : Operation
{
  public Subtraction(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 Multiplication : Operation
{
  public Multiplication(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 Division : Operation
{
  public Division(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(); }
}

abstract class Call : Expression
{
  protected Expression[] expressions;
  public Expression[] Expressions { get { return expressions; } }
  public Call(params Expression[] expressions) { this.expressions = expressions; }
  protected string Join(string splitter)
  {
    if (expressions.Length == 0) return "";
    StringBuilder stringBuilder = new StringBuilder();
    foreach (Expression expression in expressions)
    {
      stringBuilder.Append(expression.ToString());
      stringBuilder.Append(splitter);
    }
    return stringBuilder.Remove(stringBuilder.Length - splitter.Length, splitter.Length).ToString();
  }
  public override void RemoveOneParameterMin(ref Expression expression)
  {
    for (int index = 0; index < expressions.Length; index++ )
    {
      expression.RemoveOneParameterMin(ref expressions[index]);
    }
  }
}

abstract class AtLeastOneParameterCall : Call
{
  public AtLeastOneParameterCall(Expression first, params Expression[] expressions) : 
    base(AtLeastOneParameterCall.Concat(first, expressions)) { }
  private static Expression[] Concat(Expression first, params Expression[] expressions)
  {
    List<Expression> list = new List<Expression>(expressions);
    list.Insert(0, first);
    return list.ToArray();
  }
  protected delegate void CompareValues(double current, ref double value);
  protected double FindValue(CompareValues compareValues)
  {
    double value = expressions[0].Eval();
    for (int index = 1; index < expressions.Length; index++)
    {
      compareValues(expressions[index].Eval(), ref value);
    }
    return value;
  }
}

class Min : AtLeastOneParameterCall
{
  public Min(Expression first, params Expression[] expressions) : base(first, expressions) { }
  public override string ToString() { return "Min(" + Join(", ") + ")"; }
  public override double Eval() { return FindValue(ChooseMin); }
  private void ChooseMin(double current, ref double value) { if (current < value) value = current; }
  public override void RemoveOneParameterMin(ref Expression expression)
  {
    if (expressions.Length == 1) 
    { 
      expression = expressions[0]; 
    }
    else 
    {
      base.RemoveOneParameterMin(ref expression); 
    }
  }
}

class Max : AtLeastOneParameterCall
{
  public Max(Expression first, params Expression[] expressions) : base(first, expressions) { }
  public override string ToString() { return "Max(" + Join(", ") + ")"; }
  public override double Eval() { return FindValue(ChooseMax); }
  private void ChooseMax(double current, ref double value) { if (current > value) value = current; }
}

public class Program
{
  public static void Main(string[] arguments)
  {
    // Min(1.23, 2.34 + Min(3.45))
    Expression expression = new Min(new Literal(1.23), new Addition(new Literal(2.34), new Min(new Literal(3.45))));
    Console.WriteLine("Expression '{0}' - {1}.", expression.ToString(), expression.Eval());
    expression.RemoveOneParameterMin(ref expression);
    Console.WriteLine("Expression '{0}' - {1}.", expression.ToString(), expression.Eval());
    Console.ReadLine();
  }
}
Re[9]: Функциональное программирование в Nemerle
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 15.05.07 20:17
Оценка: +1
Здравствуйте, <Аноним>, Вы писали:

VD>>Следующим шагом будет избавление от большого количества методов в телах АСТ-классов и группировка их в одном месте, то есть реализация паттерна Посетитель. Так поступают 99% создателей компиляторов использующих ООЯ. И я их понимаю.


А>Похоже, Посетитель — плохой паттерн.


Нет, похоже, что C# — не самый продвинутый язык. А ортодоксальное следование ООП (да и вообще чему-либо) — не самая лучшая идея. Вот именно поэтому приходится реализовывать посетителя, т.к. он является наилучшим компромиссом в данной ситуации.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[6]: Функциональное программирование в Nemerle
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 15.05.07 20:17
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Не мог вечером зайти в форум, какая-то ошибка, связанная с Dictionary Key, поэтому вечером писал по-памяти, что прочитал с утра, так что требования не соблюдены точно, но достаточно изоморфны.


В смысле

А>150 строк, пока в c# не разочаровался.


[skip]

А мне казалось, что грамотнее было бы при упрощении выражения не модифицировать новое дерево, а конструировать новое. Кроме того, стоит оставлять по одной строчке между методами — в приведённом виде код читается плохо. Так же я уверен, что код, упрощающий выражения вида 1 * x и x * 1 на C# получился бы длиннее (и запутаннее).
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[9]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 15.05.07 21:24
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Похоже, Посетитель — плохой паттерн.


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

Вот только все это делается паттерн-матчингом намного лучше.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 15.05.07 21:24
Оценка: 26 (4) +1
Здравствуйте, <Аноним>, Вы писали:

А>Не мог вечером зайти в форум, какая-то ошибка, связанная с Dictionary Key, поэтому вечером писал по-памяти, что прочитал с утра, так что требования не соблюдены точно, но достаточно изоморфны.


Ладно, не важно. Главное, что ты теперь сможешь оценить альтернативное решение.

А>150 строк, пока в c# не разочаровался.


Ну, что же... неплохой результат. А сколько тебе потребовалось времени на изменения?

Ну, а теперь решение на вариантах и паттерн-матчинге:
using System;
using System.Console;
using Nemerle.Utility;
using Expr;

public variant Expr
{
  | Literal { value : double; }
  | Call    { name  : string; parms : list[Expr]; }
  | Plus    { expr1 : Expr;   expr2 : Expr; }
  | Minus   { expr1 : Expr;   expr2 : Expr; }
  | Mul     { expr1 : Expr;   expr2 : Expr; }
  | Div     { expr1 : Expr;   expr2 : Expr; }
  
  public override ToString() : string
  {
    match (this)
    {
      | Literal(value)    => value.ToString()
      | Call(name, parms) => $"$name(..$parms)"
      | Plus(e1, e2) with op = "+" | Minus(e1, e2) with op = "-" 
      | Mul (e1, e2) with op = "*" | Div  (e1, e2) with op = "/" 
                          => $"$e1 $op $e2"
    }
  }

  public Eval() : double
  {
    match (this)
    {
      | Literal(value)            => value
      | Plus (e1, e2)             => e1.Eval() + e2.Eval()
      | Minus(e1, e2)             => e1.Eval() - e2.Eval()
      | Mul  (e1, e2)             => e1.Eval() * e2.Eval()
      | Div  (e1, e2)             => e1.Eval() / e2.Eval()
      | Call("max", [arg1, arg2]) => Math.Max(arg1.Eval(), arg2.Eval())
      | Call("min", [arg1, arg2]) => Math.Min(arg1.Eval(), arg2.Eval())
      | Call("foo", [arg1])       => arg1.Eval() // добавили поддержку фунции foo() с одним параметром
      | Call(_, _)   => throw InvalidOperationException(this.ToString());
    }
  }

  public Convert(transform : Expr -> Expr) : Expr // добавили поддержку трансформации выражений
  {
    def res = transform(this);
    match (res)
    {
      | Literal           => res
      | Plus (e1, e2)     => Plus (e1.Convert(transform), e2.Convert(transform))
      | Minus(e1, e2)     => Minus(e1.Convert(transform), e2.Convert(transform))
      | Mul  (e1, e2)     => Mul  (e1.Convert(transform), e2.Convert(transform))
      | Div  (e1, e2)     => Div  (e1.Convert(transform), e2.Convert(transform))
      | Call(name, parms) => Call(name, parms.Map(_.Convert(transform)))
    }
  }
}

module Program
{
  Main() : void
  {
    def expr = Mul(Plus(Literal(1.23), Call("max", [Literal(1), Literal(2)])), Literal(1));
    WriteLine("До трансформации");
    WriteLine($"Expression '$expr' = $(expr.Eval())");

    def expr = expr.Convert(fun(e : Expr) { | Mul(x, Literal(1.0)) => x | _ => e });
    WriteLine("После трансформации");
    WriteLine($"Expression '$expr' = $(expr.Eval())");
    WriteLine("..."); _ = ReadLine();
  }
}


Итого 70 строк. И вряд ли кто-то будет спорить, что они намного лучше читаются.

Причем, заметь, я не менял весь код а добавил всего два фрагмента. Фнукцию Convert:
public Convert(transform : Expr -> Expr) : Expr // добавили поддержку трансформации выражений
{
    def res = transform(this);
    match (res)
    {
        | Literal           => res
        | Plus (e1, e2)     => Plus (e1.Convert(transform), e2.Convert(transform))
        | Minus(e1, e2)     => Minus(e1.Convert(transform), e2.Convert(transform))
        | Mul  (e1, e2)     => Mul  (e1.Convert(transform), e2.Convert(transform))
        | Div  (e1, e2)     => Div  (e1.Convert(transform), e2.Convert(transform))
        | Call(name, parms) => Call(name, parms.Map(_.Convert(transform)))
    }
}

и одну строчку в функцию Eval() чтобы добавить обработку функции foo().


Теперь давай проанализируем что получилось.
К каждому классу иерархии был добавлен соврешенно метод RemoveOneParameterMin(). Он увеличил код и сделал его плохо читаемым. К тому же RemoveOneParameterMin не универсален, а в исходном задании было сказано о том, что требуется универсальное решение, а не для конкретного случая.
Не выполнено по сути и задие с добавлением фунций. Ведь у тебя количество параметров не контролируется.

Теперь можно продолжить наши упраженния. Сколько тебе потребуется времени и сил чтобы добавить еще две оптимизирующие трансформации — заменять выражение max(константа, константа) на значение большей из констант. И соответственно min(константа, константа), меньшей? Мне — пол минуты. Я просто добавлю пару образцов и все. Вот измененный код метода Main() делающий эти оптимизации:
module Program
{
  Main() : void
  {
    def expr = Mul(Plus(Literal(1.23), Call("max", [Literal(1), Literal(2)])), Literal(1));
    WriteLine("До трансформации");
    WriteLine($"Expression '$expr' = $(expr.Eval())");

    def expr = expr.Convert(fun(e : Expr) { 
      | Mul(x, Literal(1.0))                  => x 
      | Call("max", [Literal(x), Literal(y)]) => Literal(Math.Max(x, y))
      | Call("mix", [Literal(x), Literal(y)]) => Literal(Math.Min(x, y))
      | _                                     => e });
    WriteLine("После трансформации");
    WriteLine($"Expression '$expr' = $(expr.Eval())");
    WriteLine("..."); _ = ReadLine();
  }
}

Консольный вывод:
До трансформации
Expression '1.23 + max(1, 2) * 1' = 3.23
После трансформации
Expression '1.23 + 2' = 3.23
...

Заметь, по сути мне нужно было добавить две строки. Тебе же прийдется еще раз менять дизайн всего приложения.
С Посетителем код у тебя может и был бы несколько больше, но все же не пришлось бы каждый раз курочить все классы ради подобных вещей.
Ну, и естественно, что без паттерн-матчинга как бы ты не извивался, но твой код все равно будет больше и непонятнее.
И знаешь почему? Потому ты говоришь компилятору "как надо делать", а я "что надо делать".
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 15.05.07 21:30
Оценка:
Здравствуйте, <Аноним>, Вы писали:

Да, забыл сказать...

Я не даром попросил тебя написать код первым. Проблема ООП не только в том, что не для всех задач в нем есть удобные и красивые решения, а в том, что он еще и наявзявает императивный образ мышления. Так что твой код несомненно можно причесать, сделав более универасальным и кратким. Но до кода на языке поддерживающем паттерн-мачинг он не дотянется. И совершенно бессысленно пытаться выиграть строчки за счет "укомпакчивания" кода. Ведь читаться он будет в лучшем случае так же как нормально отформатированный (а реально хуже). Тут же выигрышь не за счет компактной записи, а за счет применения более мощьных (и более подходящих для данной задачи) средств.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[10]: Функциональное программирование в Nemerle
От: lonli Беларусь  
Дата: 16.05.07 10:54
Оценка: :))) :)
Здравствуйте, VladD2, Вы писали:

VD>Вот только все это делается паттерн-матчингом намного лучше.


Блин я в нем попробовал после пива разобраться. После часа безуспешных попыток вспомнил как туземцы съедали печень и сердце, чтобы получить силу. VladD2, я хочу съесть твой моск
Цинизм ненавижу за его общедоступность. ©Ф.Раневская
Re: Функциональное программирование в Nemerle
От: FDSC Россия consp11.github.io блог
Дата: 16.05.07 19:05
Оценка: -1
Здравствуйте, Чистяков Влад (VladD2), Вы писали:

ЧВV>Статья:

ЧВV>Функциональное программирование в Nemerle
Автор(ы): Чистяков Влад (VladD2)
Дата: 03.03.2007
Язык программирования Nemerle заинтересовал многих в первую очередь своей мощнейшей подсистемой мак-росов. Однако и без них Nemerle предоставляет ряд су-щественных улучшений по сравнению с традиционными, императивными языками программирования (такими как Java, C# и C++).
Nemerle, кроме традиционного императивного програм-мирования, поддерживает функциональное программи-рование. Это выражается в наличии конструкций, упро-щающих манипуляцию функциями, построение и анализ сложных структур данных и т.п.
К сожалению, если вы не использовали возможности, присущие функциональным языкам ранее, то вам будет трудно оценить, насколько Nemerle может оказаться вам полезным в реальной повседневной работе. Данная статья призвана в неформальной форме продемонс-трировать это.


Честно скажу: старался читать внимательно и прочитал даже чуть больше половины После чего у меня перегорел последний защитный предохранитель и я понял: "Влад опять рассказывает фичи Немерле по отдельности...".

P.S. Для тех, кто не понял. Опять не рассказали, как думать так, что бы использовать возможности Nemerle, зато привели кучу примеров, какие возможности у него есть. Короче говоря, как правильно было сказано, для того, что бы понять преимущества Немерле, нужно начать на нём правильно писать программы, но вот как правильно их писать я так и не понял в очередной раз.

Кода в статье слишком много... воды бы побольше.... хочется услышать чего-то общего, каких-то графических схем, сравнения, скажем, структуры (не кода) программы на C# 1.1 и Nemerle, потому что всякие там вещи по типу "смотрится лучше" и "написать что-то(_, x) вместо определения вспомогательной функции" всё-таки не производит впечатления, если учесть, что нужно для этого переходить на язык, интеграция которого к VisualStudio требует скачать огромную SDK... неудобства пока перевешивают удобства. Хотя, конечно, те же замыкания — это жутко приятно


P.P.S. Летом, на досуге, может ещё раз прочитаю... может пойму что-нибудь
Re[2]: Функциональное программирование в Nemerle
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 16.05.07 19:50
Оценка: +1
Здравствуйте, FDSC, Вы писали:

ЧВV>>Статья:

ЧВV>>Функциональное программирование в Nemerle
Автор(ы): Чистяков Влад (VladD2)
Дата: 03.03.2007
Язык программирования Nemerle заинтересовал многих в первую очередь своей мощнейшей подсистемой мак-росов. Однако и без них Nemerle предоставляет ряд су-щественных улучшений по сравнению с традиционными, императивными языками программирования (такими как Java, C# и C++).
Nemerle, кроме традиционного императивного програм-мирования, поддерживает функциональное программи-рование. Это выражается в наличии конструкций, упро-щающих манипуляцию функциями, построение и анализ сложных структур данных и т.п.
К сожалению, если вы не использовали возможности, присущие функциональным языкам ранее, то вам будет трудно оценить, насколько Nemerle может оказаться вам полезным в реальной повседневной работе. Данная статья призвана в неформальной форме продемонс-трировать это.


FDS>Честно скажу: старался читать внимательно и прочитал даже чуть больше половины После чего у меня перегорел последний защитный предохранитель и я понял: "Влад опять рассказывает фичи Немерле по отдельности...".


Это Влад рассказывает про фичи Немерле, которые он заимствовал из других ФЯ.

FDS>Кода в статье слишком много... воды бы побольше.... хочется услышать чего-то общего, каких-то графических схем, сравнения, скажем, структуры (не кода) программы на C# 1.1 и Nemerle, потому что всякие там вещи по типу "смотрится лучше" и "написать что-то(_, x) вместо определения вспомогательной функции" всё-таки не производит впечатления, если учесть, что нужно для этого переходить на язык, интеграция которого к VisualStudio требует скачать огромную SDK... неудобства пока перевешивают удобства. Хотя, конечно, те же замыкания — это жутко приятно


Какое SDK? Сейчас Блудов Павел раз в месяц выкладывает инсталлятор, и ничего (кроме инсталлятора, 2Мб) качать не нужно. Правда, есть неудобство — Интеграция пока жутко глючит. Но это всё же гораздо удобнее, чем в Блокноте.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[2]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 17.05.07 13:26
Оценка:
Здравствуйте, FDSC, Вы писали:

FDS>Честно скажу: старался читать внимательно и прочитал даже чуть больше половины После чего у меня перегорел последний защитный предохранитель и я понял: "Влад опять рассказывает фичи Немерле по отдельности...".


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

FDS>P.S. Для тех, кто не понял. Опять не рассказали, как думать так, что бы использовать возможности Nemerle, зато привели кучу примеров, какие возможности у него есть. Короче говоря, как правильно было сказано, для того, что бы понять преимущества Немерле, нужно начать на нём правильно писать программы, но вот как правильно их писать я так и не понял в очередной раз.


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

Наверно общим принципом является дизайнерский выбор того как решать задачу. Если выбирается подход с эмуляцией проблемы с помощью объектов и изменения их состояний, то надо использовать ОО-фичи языка которые почти 1 в 1 как в C#. Другой подход который можно предпочесть — это описание модели как набора вариантов, а их обработку как набор преобразований (конвертаций) этих данных в другие представления.

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

Вот приведенный пример калькулятора как раз хорошо подпадает под фунициональное решение. На входе мы имеем модель одного вида (AST), а на выходе другого (вычесленное выражение и строковое предсавление). Приведенный здесь
Автор: VladD2
Дата: 16.05.07
пример оптимизации тоже подподает под понятие "конвертации".

FDS>Кода в статье слишком много... воды бы побольше.... хочется услышать чего-то общего, каких-то графических схем, сравнения, скажем, структуры (не кода) программы на C# 1.1 и Nemerle, потому что всякие там вещи по типу "смотрится лучше" и "написать что-то(_, x) вместо определения вспомогательной функции" всё-таки не производит впечатления, если учесть, что нужно для этого переходить на язык, интеграция которого к VisualStudio требует скачать огромную SDK...


Ну, SDK не требуется. В прочем SDK по сравнению с самой студией — это просто ничто (студия гигабайты, а SDK сто с копейками мег.).

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

По сути учиться писать фунционально можно и на C# 2.0. Только результат не всего удет выглядеть хорошо. Но это не главное. Главное научиться думать о прорамме как о серии преобразований исходных данных в конечные. Тогада все непонятные фичи вдруг сразу станут очень понятными и удобными.

FDS> неудобства пока перевешивают удобства. Хотя, конечно, те же замыкания — это жутко приятно


Да, замыкания в купе с локальными функциями и лямбдами — это супер. И приятно, что это очевидно. Но паттерн-матчинг и варинаты — это тоже очень удобно. Только нужно это прочувствовать.

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

FDS>P.P.S. Летом, на досуге, может ещё раз прочитаю... может пойму что-нибудь


Надо пробовать. Как показала практика все программисты которые хотели освоить новый язык и принимали участие в том или ином проекте через некоторое время отлично его осваивали.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Функциональное программирование в Nemerle
От: andrey.bond  
Дата: 17.05.07 13:36
Оценка:
Здравствуйте, VladD2, Вы писали:

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


Вот оно как
Тогда возникает следующий вопрос — зачем тогда язык поддерживает и то и другое?? я просто все пытаюсь понять как получить выгоду от слияния этих двух подходов, а как выясняется надо выбрать чтото 1.... бага
Re[3]: Функциональное программирование в Nemerle
От: andrey.bond  
Дата: 17.05.07 13:47
Оценка:
Здравствуйте, VladD2, Вы писали:


VD>Наверно общим принципом является дизайнерский выбор того как решать задачу. Если выбирается подход с эмуляцией проблемы с помощью объектов и изменения их состояний, то надо использовать ОО-фичи языка которые почти 1 в 1 как в C#. Другой подход который можно предпочесть — это описание модели как набора вариантов, а их обработку как набор преобразований (конвертаций) этих данных в другие представления.


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

Заранее благодарю.
Re[7]: Функциональное программирование в Nemerle
От: WolfHound  
Дата: 17.05.07 14:07
Оценка: 10 (1)
Здравствуйте, VladD2, Вы писали:

VD>
VD>  public Convert(transform : Expr -> Expr) : Expr // добавили поддержку трансформации выражений
VD>  {
VD>    def res = transform(this);
VD>    match (res)
VD>    {
VD>      | Literal           => res
VD>      | Plus (e1, e2)     => Plus (e1.Convert(transform), e2.Convert(transform))
VD>      | Minus(e1, e2)     => Minus(e1.Convert(transform), e2.Convert(transform))
VD>      | Mul  (e1, e2)     => Mul  (e1.Convert(transform), e2.Convert(transform))
VD>      | Div  (e1, e2)     => Div  (e1.Convert(transform), e2.Convert(transform))
VD>      | Call(name, parms) => Call(name, parms.Map(_.Convert(transform)))
VD>    }
VD>  }

VD>    def expr = expr.Convert(fun(e : Expr) { | Mul(x, Literal(1.0)) => x | _ => e });
VD>

ИМХО у тебя тут ошибочка.

Ибо выражение
Mul(Mul(Literal(5), Literal(1)), Literal(1))

Будет преобразовано в
Mul(Literal(5), Literal(1))

Я бы написал так:
  public Convert(transform : Expr -> Expr) : Expr // добавили поддержку трансформации выражений
  {
    def res = match (this)
    {
      | Literal           => this
      | Plus (e1, e2)     => Plus (e1.Convert(transform), e2.Convert(transform))
      | Minus(e1, e2)     => Minus(e1.Convert(transform), e2.Convert(transform))
      | Mul  (e1, e2)     => Mul  (e1.Convert(transform), e2.Convert(transform))
      | Div  (e1, e2)     => Div  (e1.Convert(transform), e2.Convert(transform))
      | Call(name, parms) => Call(name, parms.Map(_.Convert(transform)))
    }
        transform(res);
  }

Тогда это выражение будет преобразовано в Literal(5)
... << RSDN@Home 1.2.0 alpha rev. 673>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[8]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 17.05.07 15:52
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>...Тогда это выражение будет преобразовано в Literal(5)


Да, так лучше.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Функциональное программирование в Nemerle
От: VladD2 Российская Империя www.nemerle.org
Дата: 17.05.07 15:52
Оценка:
Здравствуйте, andrey.bond, Вы писали:

AB>Тогда возникает следующий вопрос — зачем тогда язык поддерживает и то и другое??


Потому что есть задачи которые неудобно или не эффективно решать фунционально.
Например, работа с GUI-формами императивна по сути. Мы меняем состояние окна. По этому форму лучше сделать классом, а вот рассчеты которые производятся внутри вполне себе могут быть функциональными.

AB> я просто все пытаюсь понять как получить выгоду от слияния этих двух подходов, а как выясняется надо выбрать чтото 1.... бага


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

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

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

В принципе лучше брать конкретную задачу и думать как ее лучше реализовать.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Функциональное программирование в Nemerle
От: Аноним  
Дата: 17.05.07 17:49
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Итого 70 строк. И вряд ли кто-то будет спорить, что они намного лучше читаются.

Мои 150 выполняют больше работы, но это, как я уже писал, из-за работы по памяти.
К тому же моя иерархия, возможно (из-за незнания Nemerle), строже.
Например у меня некоторые классы в иерархии абстрактные, то есть их нельзя создать.

VD>Причем, заметь, я не менял весь код а добавил всего два фрагмента. Фнукцию Convert:

Практически аналогично и у меня.

VD>К каждому классу иерархии был добавлен соврешенно метод RemoveOneParameterMin().

VD>Он увеличил код и сделал его плохо читаемым.
Спорно что плохочитаемым.

VD>К тому же RemoveOneParameterMin не универсален, а в исходном задании было сказано о том, что требуется универсальное решение, а не для конкретного случая.

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

VD>Не выполнено по сути и задие с добавлением фунций. Ведь у тебя количество параметров не контролируется.

Легко добавляется.

VD>Теперь можно продолжить наши упраженния. Сколько тебе потребуется времени и сил чтобы добавить еще две оптимизирующие трансформации — заменять выражение max(константа, константа) на значение большей из констант. И соответственно min(константа, константа), меньшей? Мне — пол минуты. Я просто добавлю пару образцов и все.

После написания функции Convert для поддержки конвертации, тоже, что-то в этом духе

VD>Заметь, по сути мне нужно было добавить две строки. Тебе же прийдется еще раз менять дизайн всего приложения.

Не придется.
VD>С Посетителем код у тебя может и был бы несколько больше, но все же не пришлось бы каждый раз курочить все классы ради подобных вещей.
Да, но похоже у посетителя, таже проблема, что и с общим методом в корневом классе. При добавлении классов в иерархию, ошибка не обработки, какого-либо класса переносится в runtime.



VD>Ну, и естественно, что без паттерн-матчинга как бы ты не извивался, но твой код все равно будет больше и непонятнее.

Все-таки вопрос привычки.

Вот код:

using System;

delegate Expr Converter(Expr expr);

abstract class Expr
{
    public abstract override string ToString();
    public abstract double Eval();
    public virtual Expr Convert(Converter converter) { return converter(this); }
}

class Lit: Expr
{
    private double value;
    public Lit(double value) { this.value = value; }
    public override string ToString() { return value.ToString(); }
    public override double Eval() { return value; }

}

abstract class Oper: Expr
{
    protected Expr first, second;
    public Expr First { get { return first; } }
    public Expr Second { get { return second; } }
    public Oper(Expr first, Expr second)
    {
        this.first = first;
        this.second = second;
    }
}

class Plus: Oper
{
    public Plus(Expr first, Expr second): base(first, second) {}
    public override string ToString() { return first.ToString() + "+" + second.ToString(); }
    public override double Eval() { return first.Eval() + second.Eval(); }
    public override Expr Convert(Converter converter) { return converter(new Plus(first.Convert(converter), second.Convert(converter))); }
}

class Minus: Oper
{
    public Minus(Expr first, Expr second): base(first, second) {}
    public override string ToString() { return first.ToString() + "-" + second.ToString(); }
    public override double Eval() { return first.Eval() - second.Eval(); }
    public override Expr Convert(Converter converter) { return converter(new Minus(first.Convert(converter), second.Convert(converter))); }
}

class Mul: Oper
{
    public Mul(Expr first, Expr second): base(first, second) {}
    public override string ToString() { return first.ToString() + "*" + second.ToString(); }
    public override double Eval() { return first.Eval() * second.Eval(); }
    public override Expr Convert(Converter converter) { return converter(new Mul(first.Convert(converter), second.Convert(converter))); }
}

class Div: Oper
{
    public Div(Expr first, Expr second): base(first, second) {}
    public override string ToString() { return first.ToString() + "/" + second.ToString(); }
    public override double Eval() { return first.Eval() / second.Eval(); }
    public override Expr Convert(Converter converter) { return converter(new Div(first.Convert(converter), second.Convert(converter))); }
}

abstract class Call: Expr
{
    protected string name;
    protected Expr[] arguments;
    public Call(string name, params Expr[] arguments)
    {
        this.name = name;
        this.arguments = arguments;
    }
    public override string ToString() { return name + "(" + String.Join(", ", Array.ConvertAll<Expr, string>(arguments, System.Convert.ToString)) + ")"; }
}

abstract class TwoParameterCall: Call
{
    public TwoParameterCall(string name, params Expr[] arguments): base(name, arguments) {}
    public Expr First { get { return arguments[0]; } }
    public Expr Second { get { return arguments[1]; } }
}

class Min: TwoParameterCall
{
    public Min(Expr first, Expr second): base("Min", first, second) {}
    public override double Eval() { return Math.Min(arguments[0].Eval(), arguments[1].Eval()); }
    public override Expr Convert(Converter converter) { return converter(new Min(arguments[0].Convert(converter), arguments[1].Convert(converter))); }
}

class Max: TwoParameterCall
{
    public Max(Expr first, Expr second): base("Max", first, second) {}
    public override double Eval() { return Math.Max(arguments[0].Eval(), arguments[1].Eval()); }
    public override Expr Convert(Converter converter) { return converter(new Max(arguments[0].Convert(converter), arguments[1].Convert(converter))); }
}

class Foo: Call
{
    public Foo(Expr first): base("Foo", first) {}
    public override double Eval() { return arguments[0].Eval(); }
    public override Expr Convert(Converter converter) { return converter(new Foo(arguments[0].Convert(converter))); }
}

public class Program
{
    public static void Main(string[] arguments)
    {
        Expr exprWithMulToOne = new Mul(new Lit(2.34), new Lit(1.00));
        WriteLine(exprWithMulToOne);

        Expr exprWithoutMulToOne = exprWithMulToOne.Convert(RemoveMulToOne);
        WriteLine(exprWithoutMulToOne);

        Expr exprWithMaxOfLiterals = new Max(new Lit(1.23), new Lit(2.34));
        WriteLine(exprWithMaxOfLiterals);

        Expr exprWithoutMaxOfLiterals = exprWithMaxOfLiterals.Convert(RemoveMaxOfLiterals);
        WriteLine(exprWithoutMaxOfLiterals);

        Expr exprWithMinOfLiterals = new Min(new Lit(1.23), new Lit(2.34));
        WriteLine(exprWithMinOfLiterals);

        Expr exprWithoutMinOfLiterals = exprWithMinOfLiterals.Convert(RemoveMinOfLiterals);
        WriteLine(exprWithoutMinOfLiterals);
    }

    static void WriteLine(Expr expr)
    {
        Console.WriteLine(String.Format("Expression '{0}' = {1}", expr.ToString(), expr.Eval()));
    }

    static Expr RemoveMulToOne(Expr expr)
    {
        Mul mul = expr as Mul; if (mul != null) 
        {
            Lit lit = mul.Second as Lit; if (lit != null && lit.Eval() == 1)
            {
                return mul.First;
            }
        }
        return expr;
    }

    static Expr RemoveMaxOfLiterals(Expr expr)
    {
        Max max = expr as Max; if (max != null) 
        {            
            Lit first = max.First as Lit, second = max.Second as Lit; if (first != null && second != null)
            {
                return new Lit(Math.Max(first.Eval(), second.Eval()));
            }
        }
        return expr;
    }

    static Expr RemoveMinOfLiterals(Expr expr)
    {
        Min min = expr as Min; if (min != null) 
        {
            Lit first = min.First as Lit, second = min.Second as Lit; if (first != null && second != null)
            {
                return new Lit(Math.Min(first.Eval(), second.Eval()));
            }
        }
        return expr;
    }
}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.