Паттерн-матчинг - отличие от обычных конструкций
От: SmbdRsdn  
Дата: 22.04.08 16:11
Оценка: :)))
Здравствуйте, z00n, Вы писали:

Z>Здравствуйте, Lazy Cjow Rhrr, Вы писали:


LCR>>Ещё в качестве альтернативы предлагаю вот этот микробенчмарк, названный Jon Harrop Challenge Problem .

Z>Отличная мысль. Аналогично, Одерски как-то хвастался перед явистами паттерн-матчингом на таком примере:
Z>
Z>// SCALA!
Z>class Term
Z>case class Num(n: int)           extends Term
Z>case class Var(name: String)     extends Term
Z>case class Mul(l: Term, r: Term) extends Term
Z>case class Add(l: Term, r: Term) extends Term

Z>// Now let's say we want to implement some simplification rules on such terms.
Z>// Two useful simplifications apply the equalities:
Z>// 0 * x  =  0
Z>// 1 * x  =  x

Z>def simplify(term: Term) = term match {
Z>  case Mul(Num(0), x) => Num(0)
Z>  case Mul(Num(1), x) => x
Z>  case _ => term
Z>}
Z>


Z>Ему ответили, что мултиплай — диспатч на яве — это просто

В чем принципиальное отличие от такого кода на Яве?
    public Term simplify(Term term) {
        _Term x = new _Term();
        if (term.equals(new Mul(new Num(0), x))) return new Num(0);
        if (term.equals(new Mul(new Num(1), x))) return x.value;
        return term;
    }


01.05.08 08:35: Ветка выделена из темы Действительно ли ML языки хороши в компиляторописании
Автор: FR
Дата: 13.12.07
— AndrewVK
Re: Паттерн-матчинг - отличие от обычных конструкций
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 23.04.08 09:51
Оценка: 34 (4)
SmbdRsdn,

Z>class Term
Z>case class Num(n: int)           extends Term
Z>case class Var(name: String)     extends Term
Z>case class Mul(l: Term, r: Term) extends Term
Z>case class Add(l: Term, r: Term) extends Term

Z>def simplify(term: Term) = term match {
Z>  case Mul(Num(0), x) => Num(0)
Z>  case Mul(Num(1), x) => x
Z>  case _ => term
Z>}


Z>>Ему ответили, что мултиплай — диспатч на яве — это просто

SR>В чем принципиальное отличие от такого кода на Яве?
SR>    public Term simplify(Term term) {
SR>        _Term x = new _Term();
SR>        if (term.equals(new Mul(new Num(0), x))) return new Num(0);
SR>        if (term.equals(new Mul(new Num(1), x))) return x.value;
SR>        return term;
SR>    }


Отличия колоссальные:
1. Паттерн-матчинг осуществляет разбор одновременно с биндингом, так что в первом случае x получит в качестве значения всё поддерево, чего бы там ни было
2. Паттерн-матчинг не пересоздаёт выражения чтобы выяснить соответствие выражений паттерну
3. Алгоритмы паттерн-матчинга не выясняют структуры подвыражений, если структура стала известна на предыдущих шагах
4. Паттерн-матчинг может содержать вайлдкарды и гварды
5. Паттерн-матчинг может быть исследован на полноту в процессе компиляции

Подробнее можно прочитать хере

Для окончательного прояснения различия я декомпилировал код на Скале, функция simplify в данном случае будет выглядеть так:
public Term simplify(Term term)
{
    Term term1 = term;
    Term term4;
    if(term1 instanceof Mul)
    {
        Mul mul = (Mul)term1;
        Term term2 = mul.l();
        Term term3 = mul.r();
        if(term2 instanceof Num)
        {
            switch(((Num)term2).n())
            {
            default:
                break;

            case 0: // '\0'
                term4 = new Num(0);
                break;

            case 1: // '\001'
                term4 = term3;
                break;
            }
        }
    }
    term4 = term;
    return term4;
}


Стоит чуть-чуть навернуть simplify,
def simplify(term: Term) = term match {
  case Mul(Num(0), x) => Num(0)
  case Mul(Num(1), x) => x
  case Add(Num(0), f) => f
  case Add(f, Num(0)) => f
  case _ => term
}

и чтобы сэмулировать это на Йаве, потребуется небольшой такой напряг (кстати, у jad-а при декомпиляции пупок надорвался):
    public Term simplify(Term term)
    {
        Term term1 = term;
        if(!(term1 instanceof Mul)) goto _L2; else goto _L1
_L1:
        Term term2;
        Term term3;
        Mul mul = (Mul)term1;
        term2 = mul.l();
        term3 = mul.r();
        if(!(term2 instanceof Num)) goto _L4; else goto _L3
_L3:
        ((Num)term2).n();
        Term term6;
        JVM INSTR tableswitch 0 1: default 192
    //                   0 64
    //                   1 75;
           goto _L4 _L5 _L6
_L5:
        term6 = new Num(0);
          goto _L7
_L6:
        term6 = term3;
          goto _L7
_L2:
        if(!(term1 instanceof Add)) goto _L4; else goto _L8
_L8:
        Term term4;
        Term term5;
        Add add = (Add)term1;
        term4 = add.l();
        term5 = add.r();
        if(!(term4 instanceof Num)) goto _L10; else goto _L9
_L9:
        Num num = (Num)term4;
        if(num.n() != 0) goto _L12; else goto _L11
_L11:
        term5;
          goto _L13
_L12:
        if(!(term5 instanceof Num) || ((Num)term5).n() != 0) goto _L4; else goto _L14
_L14:
        Term f = num;
          goto _L15
_L10:
        if(!(term5 instanceof Num) || ((Num)term5).n() != 0) goto _L4; else goto _L16
_L16:
        f = term4;
_L15:
        f;
          goto _L13
_L4:
        term6 = term;
_L7:
        term6;
_L13:
        return;
    }

Можно попробовать перевести на человеческий, это будет каскад вложенных условий, но сейчас времени нет.
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re[2]: Паттерн-матчинг - отличие от обычных конструкций
От: z00n  
Дата: 23.04.08 11:25
Оценка: 87 (8)
Здравствуйте, Lazy Cjow Rhrr, Вы писали:
LCR>Можно попробовать перевести на человеческий, это будет каскад вложенных условий, но сейчас времени нет.

У меня есть самопальный компилятор, который умеет переписывать патернматчинг как каскад вложенных if-else, правда таргетит язык с динамической типизацией(Lua) — что очевидным образом увеличивает количество тестов. Вообще, конечно, свитчи или goto лучше, но в луа goto есть только в байткоде, а мне хотелось сохранить минимальную читаемость. Использован модифицированный алгоритм Sestoft'а, как в MosML, MLKit, Nemerle.

Итак исходный пример переписанный на неком надмножестве луа(похоже на Ocaml), добавил рекурсию:
require'zstd'
local ppp = zstd.ppp

-- Scala:
-- def simplify(term: Term) = term match {
--   case Mul(Num(0), x) => Num(0)
--   case Mul(Num(1), x) => x
--   case Add(Num(0), f) => f
--   case Add(f, Num(0)) => f
--   case _ => term
-- }

local fun simplify
  | {"Mul",{"Num",0},x} -> return {"Num",0}
  | {"Mul",{"Num",1},x} -> return simplify(x)
  | {"Add",{"Num",0},f} -> return simplify(f)
  | {"Add",f,{"Num",0}} -> return simplify(f)
  | term -> return term
end

-- TEST:
ppp(1, simplify({"Mul",{"Num",1},"X"}))                    --> "X"
ppp(2, simplify({"Mul",{"Num",0},"X"}))                    --> {"Num",0}
ppp(3, simplify({"Add",{"Num",0},"F"}))                    --> "F"
ppp(4, simplify({"Add","F",{"Num",0}}))                    --> "F"
ppp(5, simplify({"Mul",{"Num",1}, {"Add",{"Num",0},"F"}})) --> "F"


Вот как выглядит результат компиляции функции simplify на Lua 5.1
local function simplify(_u0)
    if type(_u0) == 'table' then
        if #_u0 == 3 then
            if _u0[1] == "Mul" then
                if type(_u0[2]) == 'table' then
                    if #_u0[2] == 2 then
                        if _u0[2][1] == "Num" then
                            if _u0[2][2] == 0 then return {"Num";0}
                            else
                                if _u0[2][2] == 1 then return simplify(_u0[3])
                                else return _u0
                                end;
                            end;
                        else return _u0
                        end;
                    else return _u0
                    end;
                else return _u0
                end;
            else
                if _u0[1] == "Add" then
                    if type(_u0[2]) == 'table' then
                        if #_u0[2] == 2 then
                            if _u0[2][1] == "Num" then
                                if _u0[2][2] == 0 then return simplify(_u0[3])
                                else
                                    if type(_u0[3]) == 'table' then
                                        if #_u0[3] == 2 then
                                            if _u0[3][1] == "Num" then
                                                if _u0[3][2] == 0 then
                                                    return simplify(_u0[2])
                                                else return _u0
                                                end;
                                            else return _u0
                                            end;
                                        else return _u0
                                        end;
                                    else return _u0
                                    end;
                                end;
                            else
                                if type(_u0[3]) == 'table' then
                                    if #_u0[3] == 2 then
                                        if _u0[3][1] == "Num" then
                                            if _u0[3][2] == 0 then
                                                return simplify(_u0[2])
                                            else return _u0
                                            end;
                                        else return _u0
                                        end;
                                    else return _u0
                                    end;
                                else return _u0
                                end;
                            end;
                        else
                            if type(_u0[3]) == 'table' then
                                if #_u0[3] == 2 then
                                    if _u0[3][1] == "Num" then
                                        if _u0[3][2] == 0 then
                                            return simplify(_u0[2])
                                        else return _u0
                                        end;
                                    else return _u0
                                    end;
                                else return _u0
                                end;
                            else return _u0
                            end;
                        end;
                    else
                        if type(_u0[3]) == 'table' then
                            if #_u0[3] == 2 then
                                if _u0[3][1] == "Num" then
                                    if _u0[3][2] == 0 then
                                        return simplify(_u0[2])
                                    else return _u0
                                    end;
                                else return _u0
                                end;
                            else return _u0
                            end;
                        else return _u0
                        end;
                    end;
                else return _u0
                end;
            end;
        else return _u0
        end;
    else return _u0
    end;
end;


Справка по синтаксису Луа:
-- комментарий
x = {1,2,{11,12}} -- x массив из 3-х элементов, индексы начинаются с 1.
#x == 3 -- # оператор взятия длинны
x[3][2] == 12 -- сабскрипт
Re[2]: Паттерн-матчинг - отличие от обычных конструкций
От: SmbdRsdn  
Дата: 23.04.08 16:56
Оценка:
Здравствуйте, Lazy Cjow Rhrr, Вы писали:


LCR>Отличия колоссальные:

Речь шла про синтаксис, а в этом случае различия малосущественные.
LCR>1. Паттерн-матчинг осуществляет разбор одновременно с биндингом, так что в первом случае x получит в качестве значения всё поддерево, чего бы там ни было
Также и в моем примере.

LCR>2. Паттерн-матчинг не пересоздаёт выражения чтобы выяснить соответствие выражений паттерну

LCR>3. Алгоритмы паттерн-матчинга не выясняют структуры подвыражений, если структура стала известна на предыдущих шагах
Это лишь вопросы (2. и 3.) производительности, соответственно достаточно интеллектуальный компилятор, особенно если ему помочь аннотацией, вполне способен развернуть equals в набор if'ов. В расширении же синтаксиса и тем более в переходе на новый язык нет необходимости.

LCR>4. Паттерн-матчинг может содержать вайлдкарды и гварды

Wildcard есть и в моем примере, это переменная x.
Guard же это вообще насколько я понимаю практически чистый if или оператор ?:.

LCR>5. Паттерн-матчинг может быть исследован на полноту в процессе компиляции

Да, это определенное преимущество.
Но опять же, достаточно интеллектуальный компилятор java может провести подобное исследование.
Что-то в духе:
boolean is(int number) {
  if (number > 0)
    return true;
  if (number <= 0)
    return false;
  // отсутствует ошибка, что "This method must return a result of type boolean."
}

С другой стороны в примере на Scala такой проверки нет использутся case _.

LCR>Для окончательного прояснения различия я декомпилировал код на Скале, функция simplify в данном случае будет выглядеть так:

Я тоже это проделывал, очевидно, что Jad'у не удалось получить правильно работающий код.

LCR>
LCR>public Term simplify(Term term)
LCR>{
LCR>    Term term1 = term;
LCR>    Term term4;
LCR>    if(term1 instanceof Mul)
LCR>    {
LCR>       ...
LCR>    }
LCR>    term4 = term;
LCR>    return term4;
LCR>}
LCR>


LCR>Стоит чуть-чуть навернуть simplify,

LCR>
LCR>def simplify(term: Term) = term match {
LCR>  case Mul(Num(0), x) => Num(0)
LCR>  case Mul(Num(1), x) => x
LCR>  case Add(Num(0), f) => f
LCR>  case Add(f, Num(0)) => f
LCR>  case _ => term
LCR>}
LCR>

LCR>и чтобы сэмулировать это на Йаве, потребуется небольшой такой напряг (кстати, у jad-а при декомпиляции пупок надорвался):
Никакого напряга не потребуется.
public Term simplify(Term term) {
_Term x = new _Term();
if (term.equals(new Mul(new Num(0), x)))
  return new Num(0);
if (term.equals(new Mul(new Num(1), x))
  return x.value;
_Term f = new _Term();
if (term.equals(new Add(new Num(0), f))
  return f.value;
if (term.equals(new Add(f, new Num(0)))
  return f.value;
return term;
}

Правда и пример ничем существенно не отличается от предыдущего.
В исходном сообщении Одерски есть ссылка на гораздо более сложный simplify, тем не менее он тоже вполне успешно воспроизводится.
Re[3]: Паттерн-матчинг - отличие от обычных конструкций
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 24.04.08 08:39
Оценка: 15 (1) +1
SmbdRsdn,

SR>Речь шла про синтаксис, а в этом случае различия малосущественные.

Даже если ограничиться синтаксисом, то ПМ выглядит чище. Плюс отсутствует дополнительный уровень косвенности, который привносит equals.

LCR>>1. Паттерн-матчинг осуществляет разбор одновременно с биндингом, так что в первом случае x получит в качестве значения всё поддерево, чего бы там ни было

SR>Также и в моем примере.
Тогда ты забыл привести определение метода equals. (Сдаётся мне, что он будет иметь побочные эффекты, и потребуется сбрасывать x перед каждым новым if).

LCR>>2. Паттерн-матчинг не пересоздаёт выражения чтобы выяснить соответствие выражений паттерну

LCR>>3. Алгоритмы паттерн-матчинга не выясняют структуры подвыражений, если структура стала известна на предыдущих шагах
SR>Это лишь вопросы (2. и 3.) производительности, соответственно достаточно интеллектуальный компилятор, особенно если ему помочь аннотацией, вполне способен развернуть equals в набор if'ов. В расширении же синтаксиса и тем более в переходе на новый язык нет необходимости.
Игнорирование вопросов производительности приводит к тому, что приходится делать квадратичное количество сравнений вместо линейного. Никакой интеллектуальный компилятор не сможет избежать создания трёх объектов ("new Mul(new Num(0), x)"), ибо они передаются в функцию. Короче, зачем нужны кривые подпорки, если есть прямое средство?

LCR>>4. Паттерн-матчинг может содержать вайлдкарды и гварды

SR>Wildcard есть и в моем примере, это переменная x.
SR>Guard же это вообще насколько я понимаю практически чистый if или оператор ?:.
Добро пожаловать в Turing tarpit.

LCR>>5. Паттерн-матчинг может быть исследован на полноту в процессе компиляции

SR>Да, это определенное преимущество.
SR>Но опять же, достаточно интеллектуальный компилятор java может провести подобное исследование.
SR>Что-то в духе:
SR>
SR>boolean is(int number) {
SR>  if (number > 0)
SR>    return true;
SR>  if (number <= 0)
SR>    return false;
SR>  // отсутствует ошибка, что "This method must return a result of type boolean."
SR>}
SR>

SR>С другой стороны в примере на Scala такой проверки нет, и использутся case _.
Плохие новости номер раз. Сейчас достаточно интеллектуального компилятора Йавы нет и учитывая скорость развития её в ближайшую декаду не предвидится.

Плохие новости номер два. Даже если он вдруг неожиданно появится, есть принципиальные моменты, в которые этот компилятор упрётся. Например, попробуй хотя бы сам доказать, что здесь не нужно сообщение "this method must return bla-bla-bla..."
    if (((int) Math.pow(number, 19)) - number) % 19 == 0)
        return true;

Теорему Райса ещё никто не отменял, и "интеллектуальному компилятору" это тоже не удастся.

Плохие новости номер три. Очевидным решением будет "попробовать проверить эти if-ы, если удастся что-нибудь доказать, то выдать результат". Это решение будет в 99% выдавать "не смог", потому что синтаксис if-ов общий, и компилятор не может делать консервативные предположения, в отличие от паттерн-матчинга, где синтаксис может выбран так, чтобы с одной стороны он был как можно более общий, а с другой, чтобы он оставался разрешимым относительно exhaustiveness checking.

Плохие новости номер четыре. В Scala такая проверка таки есть:
sealed abstract class Term
case class Num(n: int)           extends Term
case class Var(name: String)     extends Term
case class Mul(l: Term, r: Term) extends Term
case class Add(l: Term, r: Term) extends Term

class App extends Application
{
    def simplify(term: Term) = term match {
      case Mul(Num(0), x) => Num(0)
      case Mul(Num(1), x) => x
      case Add(Num(0), f) => f
      case Add(f, Num(0)) => f
//      case _ => term
    }
}

имеем
test2.scala:16: warning: match is not exhaustive!
missing combination            Num
missing combination            Var
        def simplify(term: Term) = term match {
                                   ^
one warning found



LCR>>и чтобы сэмулировать это на Йаве, потребуется небольшой такой напряг (кстати, у jad-а при декомпиляции пупок надорвался):

SR>Никакого напряга не потребуется.
SR>
SR>public Term simplify(Term term) {
SR>_Term x = new _Term();
SR>if (term.equals(new Mul(new Num(0), x)))
SR>  return new Num(0);
SR>if (term.equals(new Mul(new Num(1), x))
SR>  return x.value;
SR>_Term f = new _Term();
SR>if (term.equals(new Add(new Num(0), f))
SR>  return f.value;
SR>if (term.equals(new Add(f, new Num(0)))
SR>  return f.value;
SR>return term;
SR>}
SR>

SR>Правда и пример ничем существенно не отличается от предыдущего.
Сделай мне, чтобы твой код имел тот-же порядок сложности, что и ПМ, а то я тут вижу слишком много лишних действий. Ну примерно так:
    public Term simplify(Term term)
    {
        if(term instanceof Mul)
        {
            Mul term2 = (Mul) term;
            Term term3 = term2.l();
            Term term4 = term2.r();
            if (term3 instanceof Num)
            {
                switch(((Num)term3).n())
                {
                case 0:
                    return new Num(0);
                case 1:
                    return term4;
                default:
                    return term;
                }
            }
        }
        else if (term instanceof Add)
        {
            Add term2 = (Add) term;
            Term term3 = term2.l();
            Term term4 = term2.r();
            if (term3 instanceof Num && ((Num)term3).n() == 0)
                return term4;
            else if (term4 instanceof Num && ((Num)term4).n() == 0)
                return term3;
        }
        return term;
    }

Никаких лишних сравнений подвыражений на instanceof, и, где это выгодно, используется switch.

BTW, если бы я вынужден был бы оставаться в Йаве, то вместо изобретения велосипеда я бы просто взял готовую библиотеку с реализацией какого-нибудь алгоритма ПМ, например Tom.
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re[3]: Паттерн-матчинг - отличие от обычных конструкций
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 24.04.08 08:50
Оценка:
z00n,

Z>У меня есть самопальный компилятор, который умеет переписывать патернматчинг как каскад вложенных if-else, правда таргетит язык с динамической типизацией(Lua) — что очевидным образом увеличивает количество тестов. Вообще, конечно, свитчи или goto лучше, но в луа goto есть только в байткоде, а мне хотелось сохранить минимальную читаемость. Использован модифицированный алгоритм Sestoft'а, как в MosML, MLKit, Nemerle.


Круто!

То есть, как я понял, ты какое-то время писал на голом Луа, потом тебя это достало, и ты решил создать метаязык для Луа, так? Паттерн-матчинг значит там уже есть, а ещё что? ФВП, list comprehensions, continuations?
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re[4]: Паттерн-матчинг - отличие от обычных конструкций
От: SmbdRsdn  
Дата: 24.04.08 10:35
Оценка: -2 :)))
Здравствуйте, Lazy Cjow Rhrr, Вы писали:

LCR>Даже если ограничиться синтаксисом, то ПМ выглядит чище.

Или излишне сокращенно, короче дело вкуса.
LCR>Плюс отсутствует дополнительный уровень косвенности, который привносит equals.
Или присутствует дополнительный уровень явности — программа читается как текст, что является преимуществом.
Название метода даже можно заменить на matches или что-то вроде этого.

LCR>Тогда ты забыл привести определение метода equals. (Сдаётся мне, что он будет иметь побочные эффекты, и потребуется сбрасывать x перед каждым новым if).

Я не забыл, просто для обсуждения возможного синтаксиса PM в Java он не существенен.
А сбрасывать нет необходимости, значение там появится только когда будет совпадение с образцом.

LCR>Игнорирование вопросов производительности приводит к тому, что приходится делать квадратичное количество сравнений вместо линейного. Никакой интеллектуальный компилятор не сможет избежать создания трёх объектов ("new Mul(new Num(0), x)"), ибо они передаются в функцию. Короче, зачем нужны кривые подпорки, если есть прямое средство?

Вопросы производительности не нужно игнорировать, а нужно как и в большинстве случаев при разработке ПО откладывать.
Особого интеллекта компилятору не нужно, в теме уже приводились примеры развертывания PM на блок if'ов.

SR>>Guard же это вообще насколько я понимаю практически чистый if или оператор ?:.

LCR> Добро пожаловать в Turing tarpit.
Гм? Как раз Guard переносится на Java один в один, что тут загадочного или не с ходу понятного?

LCR>Плохие новости номер раз. Сейчас достаточно интеллектуального компилятора Йавы нет и учитывая скорость развития её в ближайшую декаду не предвидится.

Так как синтаксис не меняется, то тут достаточно компиляторы из Java в Java. А отличных парсеров Java предостаточно, пока думаю остановится на ASTParser из eclipse.

LCR>Плохие новости номер два. Даже если он вдруг неожиданно появится, есть принципиальные моменты, в которые этот компилятор упрётся. Например, попробуй хотя бы сам доказать, что здесь не нужно сообщение "this method must return bla-bla-bla..."

LCR>
LCR>    if (((int) Math.pow(number, 19)) - number) % 19 == 0)
LCR>        return true;
LCR>

LCR>Теорему Райса ещё никто не отменял, и "интеллектуальному компилятору" это тоже не удастся.
А зачем Java бежать впереди паровоза? Пусть сначала подобная возможность появится в Scala.

LCR>Плохие новости номер три. Очевидным решением будет "попробовать проверить эти if-ы, если удастся что-нибудь доказать, то выдать результат". Это решение будет в 99% выдавать "не смог", потому что синтаксис if-ов общий, и компилятор не может делать консервативные предположения, в отличие от паттерн-матчинга, где синтаксис может выбран так, чтобы с одной стороны он был как можно более общий, а с другой, чтобы он оставался разрешимым относительно exhaustiveness checking.

В обобщенном применении и нет необходимости. Достаточно если будут обрабатываться методы помеченные определенным атрибутом и if'ы имеющие определенную форму.

LCR>Плохие новости номер четыре. В Scala такая проверка таки есть:

LCR>
LCR>class App extends Application
LCR>{
LCR>    def simplify(term: Term) = term match {
LCR>      case Mul(Num(0), x) => Num(0)
LCR>      case Mul(Num(1), x) => x
LCR>      case Add(Num(0), f) => f
LCR>      case Add(f, Num(0)) => f
LCR>//      case _ => term
LCR>    }
LCR>}
LCR>

LCR>имеем
LCR>
LCR>test2.scala:16: warning: match is not exhaustive!
LCR>missing combination            Num
LCR>missing combination            Var
LCR>        def simplify(term: Term) = term match {
LCR>                                   ^
LCR>one warning found
LCR>

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

SR>>Правда и пример ничем существенно не отличается от предыдущего.

LCR>Сделай мне, чтобы твой код имел тот-же порядок сложности, что и ПМ, а то я тут вижу слишком много лишних действий. Ну примерно так:
Я уже писал, это дело компилятора. Впрочем возможно я займусь подобной оптимизацией в ближайшее время.

LCR>BTW, если бы я вынужден был бы оставаться в Йаве, то вместо изобретения велосипеда я бы просто взял готовую библиотеку с реализацией какого-нибудь алгоритма ПМ, например Tom.

Неужели, думаете, что начиная рассуждать о PM в Java я не был в курсе про Tom?
К тому же PM в Tom мощнее того, что в Scala.
Re[5]: Действительно ли ML языки хороши в компиляторописани
От: SmbdRsdn  
Дата: 24.04.08 10:40
Оценка:
Здравствуйте, SmbdRsdn, Вы писали:

SR>По приведенному-то коду не особенно впечатляюще. Вот если бы не было case_ и образцы были бы посложнее.

case _ и не было, но простоту образцов это не отменяет.
Re[4]: Паттерн-матчинг - отличие от обычных конструкций
От: z00n  
Дата: 24.04.08 12:18
Оценка: 121 (6)
Здравствуйте, Lazy Cjow Rhrr, Вы писали:
LCR>Круто!
Спасибо

LCR>То есть, как я понял, ты какое-то время писал на голом Луа, потом тебя это достало, и ты решил создать метаязык для Луа, так? Паттерн-матчинг значит там уже есть, а ещё что? ФВП, list comprehensions, continuations?


Паттерн матчинг для луа я увидел в metalua, но он был реализован наивно(проверяем все подряд пока не false, goto следующая строка) — захотелось сделать лучше
ФВП — там уже есть, вместо continuations есть coroutines, вместо list comprehensions есть итераторы. Я добавил поддержку списков в стиле ML, что вкупе с паттерн-матчингом позволило мне воровать достаточно нетривиальные программы написанные на Haskell или Ocaml.
-- Сердце Вадлеровского претти-принтера украденное одновременно у Линдига(Quest)
-- и у Daan Leijen (Haskell PPrint)

  -- best : (sb,int,int,Cells) -> sb
  -- @sb - string buffer
  -- @n  - indentation of current line
  -- @k  - current column
  -- Cells : listof {indent, Mode, Doc}  
  local function best(sb, n, k, cells) case cells of
      | []                -> return sb
      | {i,_,Empty}  :: z -> return best(sb,n,k,z)
      | {i,m,{'Cons',x,y}}  :: z -> return best(sb,n,k,{i,m,x}::{i,m,y}::z)
      | {i,m,{'Nest',j,x}}  :: z -> return best(sb,n,k,{i+j,m,x}::z)
      | {i,_,{'Text',s}}    :: z -> return best(emit(sb,s),n,k+#s,z)
      | {i,FLT,{'Break',s}} :: z -> return best(emit(sb,s),n,k+#s,z)
      | {i,BRK,{'Break',_}} :: z -> return best(nl(sb,i),i,i,z)
      | {i,FLT,{'Group',x}} :: z -> return best(sb,w,k,{i,FLT,x}::z)
      | {i,BRK,{'Group',x}} :: z -> 
          local ribbonleft = (w - k) `min` (ribbon - k + n)
          if fits(ribbonleft,k,{i,FLT,x}::z) then
            return best(sb,n,k,{i,FLT,x}::z)
          else
            return best(sb,n,k,{i,BRK,x}::z)
          end
      | {i,m,{'Column',f}}  :: z -> return best(sb,n,k,{i,m,f(k)}::z)
      | {i,m,{'Nesting',f}} :: z -> return best(sb,n,k,{i,m,f(i)}::z)
      | x -> error(tostring(x))
    end
  end
  -- return of layout
  return best(sb,0,0,{0,BRK,doc}::[])


Ну и по мелочи: добавил инфиксные-префиксные операторы в стиле Scala, компактную форму для thunks, etc
-- prefix                        
(?)  = assert                   ---> _prefix_question = assert;
?getImage("some.img")           ---> _prefix_question(getImage"some.img");

-- right assoc
(:-:) = docCons                 ---> _op_colon_dash_colon = docCons;
mydoc = adoc :-: bdoc :-: empty ---> mydoc = _op_colon_dash_colon(adoc
                                --->                             ,_op_colon_dash_colon(bdoc,empty));
-- infixl 5 pseudo-operator
x = (fn(x)=> x+42) `map` xs     ---> x = map((function(x) return x+42 end),xs);

-- Scala's thunk
print(=> 1/0)                   ---> print(function() return 1/0 end);
Re[5]: Действительно ли ML языки хороши в компиляторописани
От: Mamut Швеция http://dmitriid.com
Дата: 24.04.08 12:28
Оценка:
LCR>>Даже если ограничиться синтаксисом, то ПМ выглядит чище.
SR>Или излишне сокращенно, короче дело вкуса.
LCR>>Плюс отсутствует дополнительный уровень косвенности, который привносит equals.
SR>Или присутствует дополнительный уровень явности — программа читается как текст, что является преимуществом.
SR>Название метода даже можно заменить на matches или что-то вроде этого.


Дело далеко не в названии метода, а в его реализации
... << RSDN@Home 1.2.0 alpha 4 rev. 1084>>


dmitriid.comGitHubLinkedIn
Re[6]: Действительно ли ML языки хороши в компиляторописани
От: SmbdRsdn  
Дата: 24.04.08 13:25
Оценка:
Здравствуйте, Mamut, Вы писали:

M>Дело далеко не в названии метода, а в его реализации

А в чем дело с реализацией?
Re[5]: Действительно ли ML языки хороши в компиляторописани
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 24.04.08 13:34
Оценка:
SmbdRsdn,

LCR>>Тогда ты забыл привести определение метода equals. (Сдаётся мне, что он будет иметь побочные эффекты, и потребуется сбрасывать x перед каждым новым if).

SR>Я не забыл, просто для обсуждения возможного синтаксиса PM в Java он не существенен.
SR>А сбрасывать нет необходимости, значение там появится только когда будет совпадение с образцом.
А решать вопрос "сбрасывать или нет" каждый раз должен программист? Нет, спасибо, и так хватает головной боли.

SR>А зачем Java бежать впереди паровоза? Пусть сначала подобная возможность появится в Scala.

При чём здесь Scala, мы же говорили о "достаточно интеллектуальном компиляторе для явы"? Скала и не претендовала на звание "обобщённого упрощателя условных выражений".

SR>В обобщенном применении и нет необходимости. Достаточно если будут обрабатываться методы помеченные определенным атрибутом и if'ы имеющие определенную форму.

А! Вот оно. Всё понятно, это препроцессор для явы. Специально помеченные if-ы (которые, заметим, работают совсем не так как выглядят!) будут прогоняться через препроцессор, а последний будет выплёвывать уже явовский код, который потом будет компилироваться с помощью javac. Плохо.

SR>По приведенному-то коду не особенно впечатляюще. Вот если бы не было case_ и образцы были бы посложнее.

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

LCR>>BTW, если бы я вынужден был бы оставаться в Йаве, то вместо изобретения велосипеда я бы просто взял готовую библиотеку с реализацией какого-нибудь алгоритма ПМ, например Tom.

SR>Неужели, думаете, что начиная рассуждать о PM в Java я не был в курсе про Tom?
SR>К тому же PM в Tom мощнее того, что в Scala.
(Кста, на этом форуме всё же приятнее обращаться на "ты"). Если ты в курсе про Tom, тогда в курсе, что он тоже неидеален, и по сравнению со Скалой проигрывает по многим параметрам. Возможно в отношении ПМ он и мощнее (хотя мне это неочевидно, так как в Скале есть unapply, который открывает очень богатые возможности которых, кажется, хватит за глаза любому приложению), но в отношении остальных вещей остановился на уровне обычной явы.
И я так и не понял, в чём преимущества твоего решения (которого кстати я по-прежнему не увидел, даже наброска, а имею возможность лишь косвенно догадываться по кусочкам кода и брошенным фразам)? Ы?
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re[7]: Действительно ли ML языки хороши в компиляторописани
От: Mamut Швеция http://dmitriid.com
Дата: 24.04.08 13:45
Оценка:
M>>Дело далеко не в названии метода, а в его реализации
SR>А в чем дело с реализацией?

Возьму из своего же примера

У нас есть правила для валидации полей:

Проверить, существует ли такое поле

{field_name}


Сравнить поле field_name с полем field2_name (также можно использовать '=', '/=', '<', '=<', '>', '>=' )
{field_name, {'=', field2_name}}


Сравнить значение поля с любым другим значением:
{field_name, {'=', Value}}


Использовать callback-функцию. Функция может быть лямбдой или любой функцией из любого модуля в виде module:function/2 или {module, function}
{field_name, F}


Использовать callback-функцию с дополнительным значением. Функция может быть лямбдой или любой функцией из любого модуля в виде module:function/3 или {module, function}
{field_name, {F, Value}}



Ну и как написать term.equals на каждое из правил? Я предположил, что для этого каждое правило придется представлять в виде класса, отнаследованного от некоторого абстрактного Rules. С паттерн-матчингом это тривиально:
%%{field}
validate_rule({FieldName}) ->
    ok.

%%{field, Func}
validate_rule({FieldName, Func}) when is_function(Func) ->
    ok.

%%{field, {'=', field2}}
validate_rule({FieldName, {Operator, FieldName2}}) ->
    ok.


и так далее.

При этом при отсутствии ПМ на каждый сулчай, когда придется что-то матчить, придется писать новые классы с новым equals. В случае с ПМ можно сразу обработать структуру практически любой сложности.
... << RSDN@Home 1.2.0 alpha 4 rev. 1084>>


dmitriid.comGitHubLinkedIn
Re[8]: Действительно ли ML языки хороши в компиляторописани
От: SmbdRsdn  
Дата: 24.04.08 14:49
Оценка:
Здравствуйте, Mamut, Вы писали:

M>>>Дело далеко не в названии метода, а в его реализации

SR>>А в чем дело с реализацией?

M>Возьму из своего же примера

Пример слабопонятен. Вернее решение для него.
Что не так, например, в таком решении, если надо всего лишь проверить ввод?
    boolean validate(String login, String password, String password_repeat, String email) {
        return 
          ((login.length() >= 4 && login.length() <= 16)) &&
          ((password.length() >= 4 && password.length() <= 16)) &&
          ((password.equals(password_repeat))) &&
          ((email.matches("\\w+@\\w+\\.\\w+")));        
    }

Вероятно, что-то пропущено в формулировке.
Re[9]: Действительно ли ML языки хороши в компиляторописани
От: Mamut Швеция http://dmitriid.com
Дата: 24.04.08 15:27
Оценка:
примера
SR>Пример слабопонятен. Вернее решение для него.
SR>Что не так, например, в таком решении, если надо всего лишь проверить ввод?
SR>
SR>    boolean validate(String login, String password, String password_repeat, String email) {
SR>        return 
SR>          ((login.length() >= 4 && login.length() <= 16)) &&
SR>          ((password.length() >= 4 && password.length() <= 16)) &&
SR>          ((password.equals(password_repeat))) &&
SR>          ((email.matches("\\w+@\\w+\\.\\w+")));        
SR>    }
SR>

SR>Вероятно, что-то пропущено в формулировке.

Приведенный вариант просто вернет true или false. Мой вариант вернет список не прошедших валидацию полей и всех ошибок, которые с ними связаны (правила валидации могут передаваться списком).

Более того, проверка полей может отдаваться на растерзание внешним функциям.

А что, если полей станет больше? Как написать общую функцию, которая:
— примет неограниченый список полей;
— для кажого поля можно передать от 0 до бесконечного количества правил;
— каждое правило позволяет отдать проверку внешней функции или устроить сравнение с переданым значением или другим полем;
— возвращает осмысленный список полей, не пршедших валидацию, со списком ошибок для каждого поля?

process_signup(A) ->
    ValidateLength = fun(A, Field) ->
        %% проверяем на длину
    end,
    EmailCheck = fun(Args, Field2) ->
        %% проверяем правильность мыла
    end,

    buktu_form:validate(A, [
        {login, F},              %% проверяем с использованием внешней функции
        {email, EmailCheck},     %% проверяем с использованием внешней функции
        {password, [{'=', password_repeat}, F]} %% проверяем на равенство с другим полем
                                                %% а потом - с использованием внешней функции
    ]).


Если поля не будут введены:
[{login,invalid_field},
 {email,invalid_field},
 {password,[{invalid_fields,[password,password_repeat]},
            invalid_field]}]


Если неправильные значения:
[{login,length},
 {email,invalid_value},
 {password,[{not_equal,password_repeat},
            length]}]



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

Расширим пример. У нас появится пункт о лицензионном соглашении, еще несколько checkbox'ов, которые необходимо отметить, а также год рождени, который должен быть раньше 1980-го года:

validate([
    {registration},
    {checkbox1},
    {checkbox2},
    {year, {'<', 1980}}
    {login, F},              %% внешние функции остаются те же
    {email, EmailCheck},     
    {password, [{'=', password_repeat}, F]} 
])


Причем полученый список ошибок разбрать не просто легко, а очнь легко ПМ по [H|T] по списку и вперед


А функцию можно спокойно дописать для подержки более сложных правил. Например:
%% {field, {Rule, and, Rule2}}
%% оба правила должны сработать

validate(FieldName, {Rule1, and, Rule2}) ->
        %% разбиваем на валидацию двух правил
    validate({FieldName, Rule}) == {} andalso validate({FieldName, Rule2}) == {}

%% {field, {Rule, or, Rule2}}
%% одно из правил должно сработать

validate(FieldName, {Rule1, or, Rule2}) ->
        %% разбиваем на валидацию двух правил
    validate({FieldName, Rule}) == {} orelse validate({FieldName, Rule2}) == {}


При этом функция остается общей и может использоваться для любых полей любой веб-формы. Без ПМа здесь только один выход — создание некоего движка правил. Но это — совсем неудобно
... << RSDN@Home 1.2.0 alpha 4 rev. 1084>>


dmitriid.comGitHubLinkedIn
Re[10]: Действительно ли ML языки хороши в компиляторописани
От: Курилка Россия http://kirya.narod.ru/
Дата: 24.04.08 15:31
Оценка: :)
Здравствуйте, Mamut, Вы писали:

[cut]

M>При этом функция остается общей и может использоваться для любых полей любой веб-формы. Без ПМа здесь только один выход — создание некоего движка правил. Но это — совсем неудобно


Да нет, Дим, про "совсем" ты тут зря, некоторые ёжики продолжают колоться и лезть на кактус. Ну нравится им это и ничего ты с этим не сделаешь...
Re[6]: Действительно ли ML языки хороши в компиляторописани
От: SmbdRsdn  
Дата: 24.04.08 15:32
Оценка: :))
Здравствуйте, Lazy Cjow Rhrr, Вы писали:

LCR>SmbdRsdn,


LCR>А решать вопрос "сбрасывать или нет" каждый раз должен программист?

Нет. Сбрасывать не зачем.

LCR>При чём здесь Scala,

Я написал свой ответ на сравнение сопоставления с образцом в Scala и в Java.

LCR>мы же говорили о "достаточно интеллектуальном компиляторе для явы"?

Но и не об обобщенно-интеллектуальном на все случаи жизни.

Я, конечно, понимаю, отчего возникло непонимание.
Мне было лень писать что-то вроде:

abstract class Term {};
class Num extends Term {};
class Var extends Term {}

@Exhaustive
Term simplify(Term term) {
  if (term instanceof Num)
     return ...;
  if (term instanceof Var)
     return ...;
  // отсутствует "This method must return a result of type Term."
}


для показа как может выглядеть проверка на полноту при сопоставлении с образцом в Java.

LCR>А! Вот оно. Всё понятно, это препроцессор для явы.

Термин препроцессор перегружен не в том направлении.

LCR>Специально помеченные if-ы

Не отдельные if, а отдельный метод. Помеченный аннотацией. Что является законным способом добавления мета-данных в Java.

LCR>(которые, заметим, работают совсем не так как выглядят!)

Работают так же как и выглядят. То есть делают тоже, что и написано. Просто быстрее.
К тому же в Java сообществе имеется много материалов, о том как написать свой equals и для каких целей это может потребоваться.
То есть код вида
if (object.equals(new Object(...))

достаточно широко распространен.

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

зачем прогоняться? В ASTParser можно на лету заменять отдельные ветки на другие. Короче, в чем-то аналог R#.

LCR>Если проверка на полноту реализована (а это так), то она будет работать и с простым кодом, и со сложным, и с одноэтажным, и с многоэтажным, и с невпечатляющим, и с впечатляющим.

Вот именно. Я это знаю, поэтому меня и удивило, что демонстрация проверки полноты производилась на сравнительно простом примере.

LCR>(Кста, на этом форуме всё же приятнее обращаться на "ты").

Я знаю, но предпочитаю обращаться на Вы. Впрочем не требую того же в ответ.

LCR>И я так и не понял, в чём преимущества твоего решения (которого кстати я по-прежнему не увидел, даже наброска, а имею возможность лишь косвенно догадываться по кусочкам кода и брошенным фразам)? Ы?

Готового производительного решения и нет. А преимущества в отсутствии необходимости перехода на другой язык с сильно измененным синтаксисом только для возможности сопоставления с образцом.
Re[10]: Действительно ли ML языки хороши в компиляторописани
От: SmbdRsdn  
Дата: 24.04.08 17:13
Оценка: :))
Здравствуйте, Mamut, Вы писали:

M>Приведенный вариант просто вернет true или false. Мой вариант вернет список не прошедших валидацию полей и всех ошибок, которые с ними связаны (правила валидации могут передаваться списком).

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

M>Более того, проверка полей может отдаваться на растерзание внешним функциям.

Неужели думаете что в Java нельзя сообщить функции, что определенное действие она может проделать при помощи некоторой внешней функции, неизвестной на момент разработки?

M>А что, если полей станет больше? Как написать общую функцию, которая:

M>- примет неограниченый список полей;
Думаете в Java функция не может принять неограниченный список параметров?
M>- для кажого поля можно передать от 0 до бесконечного количества правил;
Думаете в Java не поддерживаются массивы с переменной длиной, от 0 до ...?
M>- каждое правило позволяет отдать проверку внешней функции или устроить сравнение с переданым значением или другим полем;
Неужели думаете что в Java нельзя сообщить функции, что определенное действие она может проделать при помощи некоторой внешней функции, неизвестной на момент разработки?
M>- возвращает осмысленный список полей, не пршедших валидацию, со списком ошибок для каждого поля?

M>При этом функция — достаточно общая, позволяет передавать 5 видов различных правил, причем по нескольку штук на поле.


M>Расширим пример. У нас появится пункт о лицензионном соглашении, еще несколько checkbox'ов, которые необходимо отметить, а также год рождени, который должен быть раньше 1980-го года:


M>Причем полученый список ошибок разбрать не просто легко, а очнь легко ПМ по [H|T] по списку и вперед

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

M>А функцию можно спокойно дописать для подержки более сложных правил. Например:

Неужели сомневаетесь, что на Java можно дописывать функции?

M>При этом функция остается общей и может использоваться для любых полей любой веб-формы. Без ПМа здесь только один выход — создание некоего движка правил. Но это — совсем неудобно

А у вас стало быть не движок правил?

    public void main() {
        Form form = new Form();
        form.put("login", "name");
        form.put("password", "123");
        form.put("password_repeated", "123");
        form.put("email", "name@domain.com");
        form.put("born_year", "1980");
        
        Object[] rules = new Object[] {
            "login", "length", parameters(4, 16),
            "password", "length", parameters(4, 16),
            "password", "=", parameters("password_repeated"),
            "email", "email", parameters(),
            "born_year", "<", parameters("1990")
        };
        
        check(form, rules);
    }

    // О боже мой, кажется, я реализовал сопоставление с образцом для списка в Java!
    // Этого не может быть, наверное я сплю.
    private void check(Form form, Object[] rules) {
        for (int index = 0; index < rules.length; index += 3) {
            String field = rules[index].toString();
            String rule = rules[index+1].toString();
            Object[] parameters = (Object[])rules[index+2];
        
            check(form, field, rule, parameters);
        }
    }

    private void check(Form form, String field, String rule, Object[] parameters) {
        if (rule.equals("length")) { 
            int min = (Integer)parameters[0]; 
            int max = (Integer)parameters[1];
            checkLength(form.get(field), min, max);
        }
        ...
        // остальной код в той же мере тривиален
    }
Re[5]: Действительно ли ML языки хороши в компиляторописани
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 25.04.08 05:16
Оценка:
z00n,

Z>Паттерн матчинг для луа я увидел в metalua, но он был реализован наивно(проверяем все подряд пока не false, goto следующая строка) — захотелось сделать лучше

Z>ФВП — там уже есть, вместо continuations есть coroutines, вместо list comprehensions есть итераторы. Я добавил поддержку списков в стиле ML, что вкупе с паттерн-матчингом позволило мне воровать достаточно нетривиальные программы написанные на Haskell или Ocaml.

Спасибо за информацию, интересно. Как говорят в определённых кругах, "автор, пиши ещё"
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re[3]: Паттерн-матчинг - отличие от обычных конструкций
От: GlebZ Россия  
Дата: 25.04.08 06:50
Оценка:
Здравствуйте, z00n, Вы писали:

Z>У меня есть самопальный компилятор, который умеет переписывать патернматчинг как каскад вложенных if-else, правда таргетит язык с динамической типизацией(Lua) — что очевидным образом увеличивает количество тестов. Вообще, конечно, свитчи или goto лучше, но в луа goto есть только в байткоде, а мне хотелось сохранить минимальную читаемость. Использован модифицированный алгоритм Sestoft'а, как в MosML, MLKit, Nemerle.

Sestoft — это вот этот?
... << RSDN@Home 1.2.0 alpha rev. 789>>
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.