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>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.