M>>>>Где он был не совсем корректным и не соответствовал условиям задачи. SR>>>Не припоминаю особых некорректностей, также как и несоответствия условиям задачи.
M>>Байндились ненужные переменные SR>Связывать ненужные переменные можно и в Scala, так что мимо.
Ключевой момент состоит в том, что не нужные переменные в Скала (и в любом языке с ПМом) можно и не связывать:
single match {case _:MySingleton.type => println("OK")}
Символ подчеркивания виден? Он означает: сама переменная мне нафиг не сдалась, но главное, чтобы она была типа MySingleton
M>>>>Кстати, насчет связывания переменных.
M>>>>
M>>>>[{elem1, elem2}|T]
M>>>>
M>>>>
M>>>>data.matches(list(elem1, elem2), T)
M>>>>
M>>>>Что такое T в примере на Java, и откуда оно взялось? SR>>>
SR>>>Tail T = new Tail();
SR>>>class Tail {
SR>>> public object[] value;
SR>>>}
SR>>>
M>>чаво чаво? T у нас может быть любым объектом — списком, кортежем, классом, встроенным типом и т.п. SR>Стало быть это мне надо спрашивать?
Что — это? В списке у нас есть Head, который может быть любым типом, и Tail, который может быть любым типом. Причем принимающей функции иногда даже не надо знать, что это за переменная, а передать ее дальше.
M>>>>Что такое T в примере на Scala, и откуда оно взялось?
M>>Более того, этот T мы можем сразу взять и использовать дальше — в последующих сопоставлениях или сразу по назначению: SR>Так и в Java, неужели не видите, что раз term переменная и ее можно и использовать в соспоставлении с образцом, то и T переменная и ее тоже можно использовать далее как угодно, в том числе и при сопоставлении с образцом.
Я вижу, что вместо того, чтобы сразу разобрать структуру и забайндить только то, что мне нужно, мне приходится создавать какие-то временные переменные, писать свои собственные функции обработки и т.п.
M>>Ключевой момент — в выделенном. Тут мы создаи и заюзали переменную y встроенного типа Int. А могли бы дополнить: SR>Если нужна типизация, так в Java есть обобщенные классы. z = new Match<MyClass>().
Мывсе еще ждем реализацию этой мега функции/класса Match, который, как оказалось, уже и типы проверять может.
M>>Ну и где же пресловутая всеобщая функция, которая: SR>С чего вы взяли, что это будет одна и та же функция? Функции в Java поддерживают перегрузку параметров. SR>Применение функций будет выглядеть примерно одинаково.
Правильно. И вместо того, чтобы использовать 2 строчки ПМа, будем писать n строчек перегруженных функций, отличающихся только одним символом, ага
T>>Как будем различать массивы интов, даблов и собственно Object-ов? SR>Трудности с различением не очевидны.
Если неочивидны, приведи пример, как будут выглядеть соответствующие паттерны.
Только вместе с описанием типов _ и x.
T>>Вот про это тебе и говорят "псевдокод". То что можно построить некий тул, который будет переписывать АСД никто не сомнивается. SR>И где это говорят про псевдокод применительно к аннотоциям? Наоборот, мне предлагают привести, код раз он уже написан. SR>А про аннотации и про компилятор поддерживающий их я никогда не заявлял, что они готовы.
Хорошо, про аннотации больше не будем.
rational n + rational m -> rational(n + m)
rational n * rational m -> rational(n * m)
symbol x -> symbol x
0+f -> f
f+0 -> f
0*f -> 0
f*0 -> 0
1*f -> f
f*1 -> f
a+(b+c) -> (a+b)+c
a*(b*c) -> (a*b)*c
Итого: 33 строки. Из них 2 служебных, 2 — пустые, для улучшения восприятия.
На каждое правило из задания — 2 строки реализации + 6 строк на 3 "подразумеваемые" правила.
Приведи исходник/исходники на java, делающий то же самое.
Компилироваться и работать должно без дополнительных библиотек, на любой версии.
Тесты пишем в main и естественно его длинну не учитываем в статистике.
P.S. Правила, как мне кажется, замечательно читаются и в 1 строку, но учитывая, что пост могут чить люди совершенно не знакомые с синтаксисом erlang-а, я решил более наглядно отделить шаблоны от выводов.
... << RSDN@Home 1.2.0 alpha 4 rev. 1065>>
Re[26]: Действительно ли ML языки хороши в компиляторописани
Здравствуйте, SmbdRsdn, Вы писали:
C>>Попробуйте поставить брейкпоинт где-нибудь в java.util.collections. Запустите программу. Удивитесь. SR>Ах вот оно что, отладка для вас означает установку точки останова.
По большей части — да. Отладочную печать в JDK воткнуть затруднительно.
Хотя ещё иногда объекты из JDK инспектирую в отладчике — это да.
C>>А всё потому, что JDK в стандартной поставке не имеет отладочных символов. Их можно скачать дополнительно, но это отдельный разговор. SR>Скачиваете Eclipse, ставите точку останова, запускаете на отладку.
Нафиг? У меня IDEA в фоне открыта. Отладочные символы тоже есть.
C>>Так вот, код в JDK мне не приходилось отлаживать ни разу. SR>Ну раз 8 лет, значит так и надо.
А вот код в разных библиотеках и приложениях (Hibernate, JBoss, Jetty, Tomcat, ....) мне приходилось отлаживать неоднократно.
Sapienti sat!
Re[9]: Действительно ли ML языки хороши в компиляторописани
SmbdRsdn,
SR>Не забывайте, что Скала не может выйти за пределы JVM.
Тем не менее компилятор Scala в курсе, что такое выражение match, а компилятору Java вызов функции matches абсолютно фиолетов — они для него все на одно лицо.
SR>Надуманная проблема, тем не менее хорошо, что она была озвучена. Решение — конструктор базового класса видимый только из пакета, потомки sealed.
Это не решение, поскольку компилятор не получит гарантии. Всё равно есть потенциальная возможность получить "незарегистрированного" потомка, эта потенциальная возможность убивает оптимизацию. Решение, правда, есть — это внутренние классы, но оно плохое с точки зрения удобства и скорости. Читай ниже.
Вместе с тем, проблема очень даже реальная. Это та же самая проблема, из-за которой компилятор не может элиминировать косвенные вызовы методов и сделать инлайнинг — в подавляющем большинстве случаев он просто не знает реальный тип объекта на этапе компиляции. Это может сделать только JIT и то в очень специальных случаях (ну вот как твой) — потому что есть ещё кастом класслоадеры. Однако JIT не может содержать достаточно глубокий анализатор, поскольку здесь уже очень критично время анализа. Отсюда следует, что магическое превращение средствами самой Java-мащины метода
public Term simplify(Term term) {
_Term x = new _Term();
if (term.matches(new Mul(new Num(0), x)))
return new Num(0);
if (term.matches(new Mul(new Num(1), x))
return x.value;
_Term f = new _Term();
if (term.matches(new Add(new Num(0), f))
return f.value;
if (term.matches(new Add(f, new Num(0)))
return f.value;
return term;
}
в
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;
}
ну, кхм... очень маловероятно.
SR>>>Не отдельные if, а отдельный метод. Помеченный аннотацией. Что является законным способом добавления мета-данных в Java. LCR>>Как ты собираешься отделять "паттерн-матчинговские" if-ы от обычных, в которых объекты просто сравниваются между собой? SR>Зачем? Вполне разумно вынести сопоставление с образцом в отдельный метод, в котором не будет if'ов вида (if (object.equals(new Object()))) не представляющих сопоставление с образцом.
Тогда эти подпорки никогда не сравняются с ПМ в плане юзабилити. Видишь ли, в Скале (а также Nemerle, *ML, Haskell etc) match — это выражение, в отличие от Java, где if — это оператор. Отсюда следует, что написать
mylist.map(x => match x { ... })
в "твоей яве" невозможно в принципе.
Ещё вот в Эрланге например
T1 = insert(K, T0)
это никакое не присваивание, а паттерн матчинг, прикинь! Отсюда следует, что выражения
[H | T] = get_list()
{A1, A2} = get_tuple()
это не какие-то там костыли, прикрученные сбоку, а совершенно штатная возможность, прямо вытекающая из определения.
В языках с паттерн-матчингом встречаются и хардкорные возможности, зависящие от языка. Скажем, в Скале можно сделать так:
override def flatMap[B](f: A => Iterable[B]): Stream[B] =
if (isEmpty) Stream.empty
else
{
val s: Stream[B] = f(head) match
{
case x: Stream[_] => x
case y: List[_] => y.toStream
case z => z.toList.toStream
}
s append (tail flatMap f)
}
Обрати внимание, вайлдкардом является параметр типа.
Ещё одно наблюдение. Пишем
@PatternMatching
Term simplify(Term term, MyPattern p) {
if (!p.matches("hola"))
{
if (term.matches(new Num(1)))
...
}
}
и у нас всё упало, потому что в MyPattern метод matches — это просто метод реализующий регэкспы, и никакого отношения к "твоей яве" с её паттерн-матчингом не имеет. Выход? Правильно:
Term simplify(Term term, MyPattern p) {
if (!p.matches("hola"))
returntrueSimplify(term);
}
@PatternMatching
Term trueSimplify(Term term)
if (term.matches(new Num(1)))
...
Или специальные паттерн-матчинговые методы надо называть __matches__ дабы не было конфликта имён.
LCR>>возвращает значения через параметры (то есть метод имеет побочные эффекты!), SR>Не забывайте, это же Java! SR>Что еще за требование, что метод не может иметь побочных эффектов? SR>Может и от оператора присваивания отказаться?
Это требование к equals, спека надеюсь известна. И вообще, согласно принципу наименьшего удивления, если метод всего-лишь что-то проверяет, то результат проверки должен быть либо true, либо false, результат очередной проверки не должен зависеть от результатов предыдущих проверок, то есть проверять можно любые объекты в любом порядке. Если есть метод checkThatStuffAndAdvancePosition, то должны быть веские причины для того, чтобы не разбивать это на два метода isThatStuffOk и advancePosition. В моём случае я это делал для эффективности, и, соответственно, тщательно комментировал.
LCR>>Код стал более понятным? Щаз. Лично меня ты ввёл в конкретное заблуждение. SR>Ну если так легко ввести в заблуждением кодом вида if (object.equals(new Object())), тогда не знаю. SR>Видимо надо начинать спрашивать про опыт программирования на Java.
Гы, ну спроси. Java такой примитивный язык, что если не разбавлять этот экспириенс чем-нибудь сильно другим, то чем больше на нём программируешь, тем больше тупеешь. Привычка видеть объекты и классы везде и всюду оказывается палкой в колесе, когда сталкиваешься, например с Прологом, Хаскелем и подобными (да даже со Скалой при попытках понять вещи, которых в Java нет). А эту несчастную строчку понять можно и без опыта на java, а бить копытом в грудь это как-нть без меня.
LCR>>4. код неявно предполагает, что объекты типов Term, Num, Mul, Add и т.п. иммутабельны, а конструкторы не имеют побочных эффектов. SR>Из кода не видно, зачем ему это предположение, видимо очень не явно. На Java с Yo можно сопоставлять аналогично предыдущим классам.
Ну вот мы и добрались до сути...
Вообще, рассматривать паттерн матчинг в отрыве от алгебраических типов слегка неправильно. Поэтому правильный вопрос: как реализовать алгебраические типы в Java, а потом думать, как их матчить. Лично я знаю 2 способа, как честно (то есть без всяких кодогенераторов, препроцессоров и прочей фигни, чисто на языке) реализовать алгебраические типы и паттерн-матчинг. Для алгебраических типов определяются две операции — пересечение и объединение. Их аналоги в ООП: пересечение — это класс с полями, объединение — это наследование от общего предка (я просто повторил для полноты картины, всё это известно).
Способ первый: внутренние интерфейсы и рефлекшн.
interface Term
{
interface Num extends Term { int n(); }
interface Mul extends Term { Term l(); Term r(); }
interface Add extends Term { Term l(); Term r(); }
}
// реализацииclass NumClass implements Term.Num
{
int n;
public NumClass(int n) { this.n = n; }
public int n() { return n; }
}
class MulClass implements Term.Mul
{
Term l, r;
public MulClass(Term l, Term r) { this.l = l; this.r = r; }
public int l() { return l; }
public int r() { return r; }
}
class AddClass implements Term.Num
{
Term l, r;
public AddClass(Term l, Term r) { this.l = l; this.r = r; }
public int l() { return l; }
public int r() { return r; }
}
// у него методы объявлены final чтобы случайно не перекрылиclass PatternMatcher
{
// общий выковыриватель типов объектовpublic final Object matches(Object[] a)
{
Class c[] = new Class[a.length];
for (int k = 0; k < a.length; k++)
c[k] = a[k].getClass();
return matches(a, c);
}
// вызывальщик методов, которые матчатpublic final Object matches(Objec[] a, Class[] c)
{
try { this.getClass().getMethod("matches", c).invoke(this, a); }
catch(Exception e) {throw new MatchingException(); }
}
}
class Simplifier extends PatternMatcher
{
public Term matches(Mul m)
{
if (m.l().equals(new Num(0))
return new Num(0);
else if (m.l().equals(new Num(1))
return m.r();
else
return m;
}
public Term matches(Add a)
{
if (a.l().equals(new Num(0))
return a.r();
else if (a.r().equals(new Num(0))
return a.l();
else
return a;
}
// вот этот метод должен пользователь
// вызывать явно в своём кодеpublic Term matches(Term t)
{
return matches(new Object[]{t}); // вызывается метод в классе PatternMatcher
}
}
Производительности ради можно завести кэши в PatternMatcher-е, чтобы не строить каждый раз массив заново и не проезжаться по нему в поисках нужного метода, но это уже детали.
Способ второй: визиторы. Стандартная унылая тягомотина...
abstract class Term { public void accept(TermVisitor v); }
class Num extends Term
{
int n;
public Num(int n) { this.n = n; }
public void accept(TermVisitor v) { v.visit(this); }
}
class Mul extends Term
{
Term l, r;
public Mul(Term l, Term r) { this.l = l; this.r = r; }
public void accept(TermVisitor v) { v.visit(this); }}
}
class Add extends Term
{
Term l, r;
public Add(Term l, Term r) { this.l = l; this.r = r; }
public void accept(TermVisitor v) { v.visit(this); }}
}
interface TermVisitor
{
public void visit(Num n);
public void visit(Mul m);
public void visit(Add a);
}
class SimplifyVisitor implements TermVisitor
{
Term t; // возвращаемое значениеpublic SimplifyVisitor(Term t) { this.t = t; }
public void visit(Num n) {}
public void visit(Mul m)
{
if (m.l().equals(new Num(0))
t = new Num(0);
else if (m.l().equals(new Num(1))
t = m.r();
}
public void visit(Add a)
{
if (a.l().equals(new Num(0))
t = a.r();
else if (a.r().equals(new Num(0))
t = a.l();
}
}
(Оба способа достаточно уродливы, только второй чаще упоминается)
Вот здесь и видно, зачем нужно отсутствие побочных эффектов и иммутабельность. Если методы l(), r() и подобные будут иметь побочные эффекты, то результат matches будет зависеть от порядка вызова методов, от состояния матчера, от фазы Луны... А иммутабельность как раз и позволяет изничтожить создания объектов на каждый чих (вон там повсюду new Num(0)), а расшаривать несколько объектов среди многих точек использования. Тот самый пулинг, о котором ты говорил немного ранее.
А теперь на закуску. Вроде ты где-то жаловался, что примеры слишком простые. Ну вот 3 не слишком простых примера. Первый пример — это балансировка красно-чёрного дерева с помощью ПМ:
private def mkTree[B](isBlack: Boolean, k: A, v: B, l: Tree[B], r: Tree[B]) =
if (isBlack) BlackTree(k, v, l, r) else RedTree(k, v, l, r)
def upd[B1 >: B](k: A, v: B1): Tree[B1] =
{
def balanceLeft(isBlack: Boolean, z: A, zv: B, l: Tree[B1], d: Tree[B1]) = l match
{
case RedTree(y, yv, RedTree(x, xv, a, b), c) =>
RedTree(y, yv, BlackTree(x, xv, a, b), BlackTree(z, zv, c, d))
case RedTree(x, xv, a, RedTree(y, yv, b, c)) =>
RedTree(y, yv, BlackTree(x, xv, a, b), BlackTree(z, zv, c, d))
case _ =>
mkTree(isBlack, z, zv, l, d)
}
def balanceRight(isBlack: Boolean, x: A, xv: B, a: Tree[B1], r: Tree[B1]) = r match
{
case RedTree(z, zv, RedTree(y, yv, b, c), d) =>
RedTree(y, yv, BlackTree(x, xv, a, b), BlackTree(z, zv, c, d))
case RedTree(y, yv, b, RedTree(z, zv, c, d)) =>
RedTree(y, yv, BlackTree(x, xv, a, b), BlackTree(z, zv, c, d))
case _ =>
mkTree(isBlack, x, xv, a, r)
}
if (k < key)
balanceLeft(isBlack, key, value, left.upd(k, v), right)
else if (key < k)
balanceRight(isBlack, key, value, left, right.upd(k, v))
else
mkTree(isBlack, k, v, left, right)
}
Обрати внимание, как классно здесь помогает type inference — локальные функции внутри принимают обобщённые параметры, и это учитывается и паттерн-матчинге! Это вообще здорово, когда фичи языка вот в такой гармонии работают. А вот теперь я тебе щас зарисую твой эквивалент
private <A, B> Tree<B> mkTree(Boolean isBlack, A k, B v, Tree<B> l, Tree<B> r)
{
if (isBlack)
return new BlackTree<B>(k, v, l, r);
else
return new RedTree<B>(k, v, l, r);
}
@PatternMatching
private <A, B, B1> Tree<B> balanceLeft(Boolean isBlack, A z, B zv, Tree<B1> l, Tree<B1> d)
{
_Boolean x = new _Boolean();
_Boolean y = new _Boolean();
_A yv = new _A();
_A xv = new _A();
_Tree<B1> a = new _Tree<B1>();
_Tree<B1> b = new _Tree<B1>();
_Tree<B1> c = new _Tree<B1>();
if (l.matches(new RedTree<B>(y, yv, new RedTree<B>(x, xv, a, b), c)))
return new RedTree<B>(y.value, yv.value,
new BlackTree<B>(x.value, xv.value, a.value, b.value),
new BlackTree<B>(z.value, zv.value, c.value, d.value));
else if (l.matches(RedTree<B>(x, xv, a, new RedTree<B>(y, yv, b, c))))
return new RedTree<B>(y.value, yv.value,
new BlackTree<B>(x.value, xv.value, a.value, b.value),
new BlackTree<B>(z.value, zv.value, c.value, d.value));
else
return mkTree(isBlack, z, zv, l, d);
}
@PatternMatching
private <A, B, B1> Tree<B> balanceRight(Boolean isBlack, A x, B xv, Tree<B1> a, Tree<B1> r)
{
_Boolean z = new _Boolean();
_Boolean y = new _Boolean();
_A zv = new _A();
_A yv = new _A();
_Tree<B1> b = new _Tree<B1>();
_Tree<B1> c = new _Tree<B1>();
_Tree<B1> d = new _Tree<B1>();
if (r.matches(new RedTree<B>(z, zv, new RedTree<B>(y, yv, b, c), d)))
return new RedTree<B>(y.value, yv.value,
new BlackTree<B>(x.value, xv.value, a.value, b.value),
new BlackTree<B>(z.value, zv.value, c.value, d.value));
else if (r.matches(RedTree<B>(y, yv, b, new RedTree<B>(z, zv, c, d))))
return new RedTree<B>(y.value, yv.value,
new BlackTree<B>(x.value, xv.value, a.value, b.value),
new BlackTree<B>(z.value, zv.value, c.value, d.value));
else
return mkTree(isBlack, x, xv, a, r);
}
public <A, B> Tree<B> upd(A k, B v)
{
if (k < key)
balanceLeft(isBlack, key, value, left.upd(k, v), right);
else if (key < k)
balanceRight(isBlack, key, value, left, right.upd(k, v));
else
mkTree(isBlack, k, v, left, right);
}
Тут и без синтаксического мусора приходится думать определённое время, а когда код загажен несущественными деталями, то вообще какая-то труба.
Во-первых, этот код не работает вообще. То есть я как-то начал переводить, а потом наткнулся на дженерик параметры, и что с ними делать — Я тупо заполнил эти места _A. Не, ну что-то с ними конечно можно сделать, нарисовать холдеры Object-ов, кастинг там в нужном месте сделать... Но это криво.
Во-вторых, конструкторы BlackTree тоже должны быть от типов-параметров — где-то от <B>, где-то от <B1>, но я везде взял и налепил <B>. Тут, извини, нужна усидчивость. А у меня её упс — нету. Я люблю когда всё легко и просто.
В-третьих, холдеры для Boolean-ов я обозначил _Boolean. Продолжая в том же духе, можно пройтись по всем типам стандартной библиотеки. Ну ты можешь, конечно, круто обобщить, и сделать обобщённый холдер Holder<T> который можно будет толкать во все дыры в твоём паттерн-матчинге. Это наверное будет прадедушка всех алгебраических типов. Самое интересное начнётся, когда потребуется матчить какие-нибудь типы наследующие от Holder<T>, тогда появятся F-ограничения типа Holder<T extends Holder<T>>, интереснейшие
хреновины, надо сказать. Если это вот-так вот вколотить в редактор, компилятор будет нехорошо ругаться. Чтобы можно было инстанцировать эти штуки, потребуется предварительно их замкнуть. Вах, какая захватывающая работа тебе предстоит, аж завидно!
Ну, а в-четвёртых, напрягает предварительно объявлять все переменные. Раз уж у нас есть препроцессинг, пусть он как-нибудь сам нагенерит объявления, что-ли...
Второй пример — это проверка, могут ли процессы взаимодействовать (из реализации pi-calculus)
private def matches(gs1: List[UGP], gs2: List[UGP]): Option[(() => Unit, () => Any, () => Any)] =
(gs1, gs2) match {
case (Nil, _) => None
case (_, Nil) => None
case (UGP(a1, d1, v1, c1) :: rest1, UGP(a2, d2, v2, c2) :: rest2) =>
if (a1 == a2 && d1 == !d2)
Some(((() => if (d1) a1.log(v2) else a1.log(v1)), (() => c1(v2)), (() => c2(v1))))
else matches(gs1, rest2) match {
case None => matches(rest1, gs2)
case Some(t) => Some(t)
}
}
Обрати внимание, как на лету создаётся тупл, а потом он разбирается на кусочки и в else-ветке происходит рекурсивный вызов себя же и результат рекурсивного вызова прямо на месте опять матчится. Перевод в каноническую форму "твоей java" 1-в-1 снова обломится. Там нужно будет заводить 2 взаимно-рекурсивных метода, рисовать аннотации и создавать несуществующий Tuple<A,B>(gs1, gs2) у которого вдруг должен оказаться снова волшебный метод matches. Но переводить что-то уже лень. Я ж говорю, усидчивость нужна.
. Это AVL-деревья. На сей раз на Эрланге. Там тоже деревья изящно так разбираются на кусочки, а потом из этих кусочков склеиваются новые деревья. Вот только возможно тебе не понравится, слишком уж там всё кратко. Я бы даже сказал чересчур. Так и хочется куда-нибудь впихнуть new. Или @PatternMatching...
Примечание. Ниже код, особенно на Яве — этакий полупсевдокод В Яве я намного ольше читатель, чем писатель. Functor — это анонимная функция, принимающая на вход параметр типа FieldType и возвращающая FieldType. Возможно, в Яве это реализуется делегатами или анонимными классами — не суть важно.
Итак...
ПМ позволяет уменьшить количество кода, увеличивая его читаемость (что плохо видно на мелких примерах, но все же)
// Scala
def runFilters(in_val: FieldType, filter: List[FieldType => FieldType]): FieldType =
filter match {
case Nil => in_value
case x :: xs => runFilters(x(in_val), xs)
}
// Java
FieldType runFilters(FiledType in_val, Functor filter[])
{
for(filter in filters)
{
if(filter != NULL)
in_val = filter(in);
}
return in_val;
}
ПМ позволяет легче вносить изменения (безусловно не везде, но в большом классе задач, свзаных с проверкой), не ухудшая читаемости и декларативности кода:
// Scala
def runFilters(in_val: FieldType, filter: List[FieldType => FieldType]): FieldType =
filter match {
case Nil => in_val
case x:MyFilter.type :: xs => runFilters(x(in_val), xs)
// мы еще не реализовали другие фильтрыcase _ :: xs => runFilters(in_val, xs)
}
// Java
FieldType runFilters(FiledType in_val, Functor filter[])
{
for(filter in filters)
{
if(filter != NULL && filter instanceof MyFilter)
filter(in_val);
}
}
Если логика усложняется, декларативность остается:
// Scala
// Реализованы только фильтры MyFilter и ComplexFlter
// Для ComplexFilter необходимо елаь препроцессинг
def runFilters(in_val: FieldType, filter: List[FieldType => FieldType]): FieldType =
filter match {
case Nil => in_val
case x:MyFilter.type :: xs => runFilters(x(in_val), xs)
case x:ComplexFilter.type :: xs =>
in_val = Preprocess(in_val);
runFilters(in_val, xs)
case _ :: xs => runFilters(in_val, xs)
}
// Java
// Реализованы только фильтры MyFilter и ComplexFlter
// Для ComplexFilter необходимо елаь препроцессинг
FieldType runFilters(FiledType in_val, Functor filter[])
{
for(filter in filters)
{
if(filter != NULL)
{
if(filter instanceof MyFilter)
{
in_val = filter(in_val);
}
else if(filter instanceof ComplexFilter)
{
in_val = Preprocess(in_val);
in_val = filter(in_val);
}
}
}
return in_val;
}
И так далее. Уже видно, что ПМ полностью соответствует комментариям, там даже комментарии не нужны, в коде и так все понятно. В то время как без ПМа с усложнением логики код становится все менее и менее читаемым из-за леса if'ов.
M>>// Java M>>// Реализованы только фильтры MyFilter и ComplexFlter M>>// Для ComplexFilter необходимо елаь препроцессинг
ANS>Ерунда. Я несколько раз уже пытался донести мысль
Здравствуйте, Mamut, Вы писали:
M>>>// Java M>>>// Реализованы только фильтры MyFilter и ComplexFlter M>>>// Для ComplexFilter необходимо елаь препроцессинг
ANS>>Ерунда. Я несколько раз уже пытался донести мысль
, что не нужно один в один переносить код с ЯП с ПМ на java (или что-то еще). Потому как на ней будет всё иначе.
M>Вот мы примерно про это же и говорим — без ПМ это каждый раз буде по-другому. С ПМ это будет некий унифицированый подход
Ну ПМ он тоже не совсем везде одинаков, да и в том же Эрланге можно ещё и бинарисы паттернматчить, но это уже настолько мощная магия, что многие приготовят целые ящики тухлых помидоров
Re[10]: Действительно ли ML языки хороши в компиляторописани
LCR> Это AVL-деревья. На сей раз на Эрланге. Там тоже деревья изящно так разбираются на кусочки, а потом из этих кусочков склеиваются новые деревья. Вот только возможно тебе не понравится, слишком уж там всё кратко. Я бы даже сказал чересчур. Так и хочется куда-нибудь впихнуть new. Или @PatternMatching...
А может пойти от противного ? Записать примеры на J и показать что скала с эрлангом очень даже многословные языки
Re[11]: Действительно ли ML языки хороши в компиляторописани
LCR>Сложность реализации будет чуток зашкаливать, потому что дерево надо будет закодировать в массив каким-нибудь извращенским способом.
Ну можно влоб боксами. Аля ЛЫСП
Правда эффективность будет не ахти, и ограничения есть на глубину боксинга..
Смотреть раздел obstacles http://www.jsoftware.com/jwiki/DanBron/Temp/Tree
Наверное ты прав, получится как в том стишке.
Если взрослого мыша взять и, бережно держа,
Натыкать в него иголок, вы получите ежа.
Если этого ежа, нос заткнув, чтоб не дышал,
Кинуть в речку, где поглубже, вы получите ерша.
Если этого ерша, головой в тиски зажав,
Посильней тянуть за хвост, вы получите ужа.
Если этого ужа, приготовив два ножа...
Впрочем, он, наверно, сдохнет. Но идея хороша!
Re[3]: Паттерн-матчинг - отличие от обычных конструкций
Здравствуйте, z00n, Вы писали:
Z>У меня есть самопальный компилятор, который умеет переписывать патернматчинг как каскад вложенных if-else, правда таргетит язык с динамической типизацией(Lua)
хм. а про metalua ты слышал?
--
-- The general form of a pattern matching statement is:
--
-- match <tested_term> with
-- | <pattern_1_1> | <pattern_1_2> | <pattern_1_3> -> <block_1>
-- | <pattern_2> -> <block_2>
-- | <pattern_3_1> | <pattern_3_2> if <some_condition> -> <block_3>
-- end
--
там вообще дофига интереснейших расширений языка: adt, list comprehension, ?:, type checking, try/catch, RAII, lambdas/lazy values, statements in expressions. словом, metalua можно рассматривать как двольно полноценный динамичсекий императивный ФП язык. а самое сочное там — расширяемость с синтаксическими макросами. собственно, половина вышеописанных вещей в базовый язык не входит, а порсто включены в качестве примеров. разница, собственно говоря невелика — metalua основана на parsec-подобной комбинаторной библиотеке парсинга gg, НО — поддерживающей расширение на ходу синтаксиса разюираемого языка. так что большой разницы где описан синтаксис этих расширений нету, ну а генератор кода опять же в любом случае пишется на самом Lua. ну и сам возможности метапрограммирования похожи на Template Haskell — на уровень вверх, на уровень вниз. код на Lua автоматом ковертируется в AST и пожалуйста — ходи по нему сколько хочешь
Люди, я люблю вас! Будьте бдительны!!!
Re[4]: Паттерн-матчинг - отличие от обычных конструкций
Здравствуйте, BulatZiganshin, Вы писали:
BZ>хм. а про metalua ты слышал?
Слышал.
BZ>там вообще дофига интереснейших расширений языка: adt, list comprehension, ?:, type checking, try/catch, RAII, lambdas/lazy values, statements in expressions. словом, metalua можно рассматривать как двольно полноценный динамичсекий императивный ФП язык. а самое сочное там — расширяемость с синтаксическими макросами. собственно, половина вышеописанных вещей в базовый язык не входит, а порсто включены в качестве примеров.
Я готов добавить хоть поддержку монадного сахара и Arrows — если есть потребность конечно
BZ> разница, собственно говоря невелика — metalua основана на parsec-подобной комбинаторной библиотеке парсинга gg, НО — поддерживающей расширение на ходу синтаксиса разюираемого языка. так что большой разницы где описан синтаксис этих расширений нету, ну а генератор кода опять же в любом случае пишется на самом Lua. ну и сам возможности метапрограммирования похожи на Template Haskell — на уровень вверх, на уровень вниз. код на Lua автоматом ковертируется в AST и пожалуйста — ходи по нему сколько хочешь
У меня тоже конвертируется, просто AST на Scala. Вы на чем предпочли бы работать с AST: на Haskell или на Lua? Если серьезно — в свете появления Lpeg (PEG парсера для Lua) — я, когда у меня выпадет свободная неделя переписал бы это на луа, бутстрэпнув его нынешним компилятором.
(Часть посвященная Философии программирования)
По природе своей я lisp-guy, но по поводу макросов для языков с инфиксной нотацией у меня есть большие сомнения. Макросы нужны когда нужно изменить компилятор, но в его недра залезть нереально. Макросы для не-лиспов сложнее писать, ими легче сломать синтаксис, и постоянно возникает ощущение что вам недодают выразительных средств (см. например топик про Nemerle и LINQ: http://www.rsdn.ru/forum/message/2913896.1.aspx
). C другой стороны, программу можно трансформировать и внешними средствами, и это может быть даже проще. И все равно 99% процентов программистов никогда не будут делать ни того ни другого, а будут брать, что дают.
(Часть посвяшенная практическим вещам)
Все началось с того, что мне понадобился в Луа паттерн-матчинг и я радостно схатился за металуа, в тот момент версии 0.2 alpha. К сожалению, в тот момент (во многом и сейчас) metalua не была готова к практическому использованию.
-- Там был огромный нечитаемый рукописный парсер. Поскольку он рукописный, он никак не проверяет непротиворечивость синтаксиса расширений, которые вы пишете – а синтаксис то не лисповый. Металуа вообще сильно сложнее и раза в 2-3 больше того, что я написал. В результате у меня получилось ~3K строк на скале (из которых треть pattern-matching compiler) + ~250 строк грамматики core lua на Xtc Rats! + ~250 строк грамматики расширений + 200 сток грамматики литералов, ключевых слов и прочих тривиальных вещей. Core metalua сейчас (раньше больше) занимает ~6K строк на луа + 1.5K собственно расширений на металуа + 1.5K библиотека.
-- Паттерн матчинг был (и есть) реализован наивно – т.е имеет сложность N*M в патологическом случае. Правда, я подозреваю, что в Erlang он тоже реализован наивно, и с этим можно жить, и будь это единственной проблемой – можно было бы смириться. Я молчу про то, что pattern-language бедноват.
-- Все это для работы требует изрядного рантайма написанного на lua, причем автор подхачил механизм загрузки модулей и у меня сразу начались всякие непонятные чудеса, которые усугублялись тем, что автор разрабатывал под Linux, а я пытался использовать это под Win.
-- В AST были очевидные переупрощения (их поправили в следующей версии), например в большинстве языков скобки в (expression) не несут семантической нагрузки. В lua, если выражение возвращает несколько значений заключение его в скобки урезает (как по русски будет coercion?) его до первого. Переупрощения появились, разумеется из-за желания иметь для макросов AST как можно меньшего размера.
-- Ну и главное – metalua порождает бинарный файл и в ней нет даже нормального претти-принтера (там есть некий рудиментарный механизм дампа AST – но ничего серьезного им не посмотришь). Искать место, в котором возникла ошибка или отлаживать макрос – приключение, поскольку луа ничего о металуа не знает. Бинарник она порождает для lua5.1 – а мне вот как раз недавно понадобилось таргетить lua 5.0 – минут за 20 я добавил опцию совместимости в паре мест подправил кодогенератор и написал на луа пару недостающих в 5.0 функций.
Что я получил написав свой компилятор?
-- У меня есть практически все, что есть в металуа, а чего нет, можно добавить примерно за час – два -- я добавил все что мне понравилось/показалось полезным.
-- Алгоритм паттерн-матчинга лучше, pattern-language – лучше. Бесплатная диагностика достижимости rhs (полноты тоже, но в динамическом языке не особенно нужно), линеарности паттерна, правильности употребления переменных в OR-паттерне (а в Scala их просто запретили) etc.
-- Есть то, чего нет в metalua, а мне хотелось иметь: инфиксные операторы, ML-like списки x::y::[], которые можно паттерн-матчить.
-- На выходе я получаю красиво отформатированную, ни от чего не зависящую программу. Единственная опциональная внешняя зависимость – 20 строк поддержки списков, которых ни в луа ни в металуа нет.
-- Имея свой компилятор с PEG парсером — расширять синтаксис достаточно просто:
— описываем грамматтику расширения на роскошном(по сравнению с металуа) языке Xtc Rats!
— пишем пару примеров и смотрим красиво отформатированное дерево разбора.
— запускаем официальные тесты луа, убеждаемся, что мы не сломали обратную совместимость с языком. Этого не случалось ни разу(хотя я нашел так пару тонких мест), поскольку PEG грамматики по настоящему расширяемые.
— дописываем клаузу в преобразование “разбор->AST_EX” – эта лишняя стадия необходима из-за того, что Rats! таргетит Java, а не Scala.
— дописываем клаузу в неявное преобразование AST_EX -> AST .
Все стадии претти-принтятся, включая всякие тонкие места типа генерации decision trees.
Ну и разумеется куча фана и всяких полезных наблюдений и ошибок.
Re[3]: Паттерн-матчинг - отличие от обычных конструкций
Здравствуйте, SmbdRsdn, Вы писали:
LCR>>1. Паттерн-матчинг осуществляет разбор одновременно с биндингом, так что в первом случае x получит в качестве значения всё поддерево, чего бы там ни было SR>Также и в моем примере.
У тебя вообще нет биндинга. Тебе же сказали, что в "х" может быть любое выражение.
LCR>>2. Паттерн-матчинг не пересоздаёт выражения чтобы выяснить соответствие выражений паттерну LCR>>3. Алгоритмы паттерн-матчинга не выясняют структуры подвыражений, если структура стала известна на предыдущих шагах SR>Это лишь вопросы (2. и 3.) производительности, соответственно достаточно интеллектуальный компилятор, особенно если ему помочь аннотацией, вполне способен развернуть equals в набор if'ов. В расширении же синтаксиса и тем более в переходе на новый язык нет необходимости.
Елы палы. Ну, попробуй реализуй свой "достаточно интеллектуальный компилятор" (с). Как только ты дойдешь до реальной реализации, то поймешь, что то что ты назвал "достаточно интеллектуальный компилятор" на самом деле является "думателем" (с). Этот думатель есть очень серьезно продуманный и математически обоснованный (доказанный) код. Под ним лежит железная теория. Твой "достаточно интеллектуальный компилятор" конечно сделать можно, но это и будет тот самый думатель запахнутый в рамки синтаксиса Явы. Ты будешь вынужден ввести все ограничения которые есть в алгебраических типах данных (case class-ы Скалы), так как без них нельзя гарантировать что конструкторы или методы не создают неких побочных эффектов, ты должен будешь ограничить наследование, чтобы иметь право предсказывать доказательство полноты... в общем, ты создашь тоже самое, но с более привычным синтаксисом... привычным тебе и таким как ты. Не проще ли просто выучить новый синтаксис и не заниматься велосипедостроением на пустом месте?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Действительно ли ML языки хороши в компиляторописани
Здравствуйте, SmbdRsdn, Вы писали:
SR>зачем прогоняться? В ASTParser можно на лету заменять отдельные ветки на другие. Короче, в чем-то аналог R#.
Как автор R# скажу, что закончил его развитие я как раз когда вплотную познакомился с Nemerle, который является братом-близнецом Scala, но на платформе .NET. Правда причиной, по началу, было не наличие ПМ, а наличие более совершенной мета-подсистемы, но мета-подсистема была только приманкой, а когда я разобрался в том как она устроена, то я понял, что базисом этой подсистемы как раз и является паттерн-матчинг и алгебраические типы (с которыми ты борешься).
Реально твоя увидел, что на Ява можно выразить нечто похожее по начертанию на ПМ в Скале, но пока что ты еще не разобрался с глубинными механизмами делающими тот самый эффективный код из столь заманчиво краткого и ясного синтаксиса. Когда же ты разберешся, то поймешь, что синтаксис — это самое малое, что есть в языках поддерживающих МП. Реально же там главное — это правила и мудреные алгоритмы.
Так что твои слоа не более чем предложение добавить в Яву паттерн-матчинг, но с синтаксисом похожим на вызовы методов Явы.
Это конечно же вполне возможно. Но как я уже скзал, дело далеко не в синтаксисе.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.