Многие современные языки поддерживают сопоставление с образцом (pattern matching) на уровне языка. Java в данный момент не поддерживает pattern matching, но есть надежды что в будущем все может измениться.
Сопоставление с образцом раскрывают перед разработчиком возможность писать код более гибко и красивее, при этом оставляя его понятным.
Используя возможности Java 8, можно реализовать некоторые возможности pattern matching в виде библиотеки. При этом можно использовать как утверждение так и выражения.
Constant pattern позволяет проверить на равность с константами. В Java switch позволяет проверить на равность числа, перечисления и строки. Но иногда хочется проверить на равность константы объектов используя метод equals().
switch (data) {
case new Person("man") -> System.out.println("man");
case new Person("woman") -> System.out.println("woman");
case new Person("child") -> System.out.println("child");
case null -> System.out.println("Null value ");
default -> System.out.println("Default value: " + data);
};
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 константами.
import org.kl.state.Else;
import org.kl.state.Null;
import static org.kl.pattern.ConstantPattern.matches;
matches(data,
new Person("man"), () -> System.out.println("man");
new Person("woman"), () -> System.out.println("woman");
new Person("child"), () -> System.out.println("child");
Null.class, () -> System.out.println("Null value "),
Else.class, () -> System.out.println("Default value: " + data)
);
```<cut/>
<i><b>Tuple pattern</b></i> позволяет проверить на равность нескольких перемен с константами одновременно.
```java
switch (side, width) {
case "top", 25 -> System.out.println("top");
case "bottom", 30 -> System.out.println("bottom");
case "left", 15 -> System.out.println("left");
case "right", 15 -> System.out.println("right");
case null -> System.out.println("Null value ");
default -> System.out.println("Default value ");
};
В данный момент библиотека поддерживает возможность указывать 4 переменные и 6 веток.
import org.kl.state.Else;
import org.kl.state.Null;
import static org.kl.pattern.TuplePattern.matches;
matches(side, width,
"top", 25, () -> System.out.println("top");
"bottom", 30, () -> System.out.println("bottom");
"left", 15, () -> System.out.println("left");
"right", 15, () -> System.out.println("right");
Null.class, () -> System.out.println("Null value"),
Else.class, () -> System.out.println("Default value")
);
Type test pattern позволяет одновременно сопоставить тип и извлечь значение переменной. В Java для этого нам нужно сначала проверить тип, привести к типу и потом присвоить новой переменной.
switch (data) {
case Integer i -> System.out.println(i * i);
case Byte b -> System.out.println(b * b);
case Long l -> System.out.println(l * l);
case String s -> System.out.println(s * s);
case null -> System.out.println("Null value ");
default -> System.out.println("Default value: " + data);
};
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами.
import org.kl.state.Else;
import org.kl.state.Null;
import static org.kl.pattern.VerifyPattern.matches;
matches(data,
Integer.class, i -> { System.out.println(i * i); },
Byte.class, b -> { System.out.println(b * b); },
Long.class, l -> { System.out.println(l * l); },
String.class, s -> { System.out.println(s * s); },
Null.class, () -> { System.out.println("Null value "); },
Else.class, () -> { System.out.println("Default value: " + data); }
);
Guard pattern позволяет одновременно сопоставить тип и проверить на условия.
switch (data) {
case Integer i && i != 0 -> System.out.println(i * i);
case Byte b && b > -1 -> System.out.println(b * b);
case Long l && l < 5 -> System.out.println(l * l);
case String s && !s.empty() -> System.out.println(s * s);
case null -> System.out.println("Null value ");
default -> System.out.println("Default: " + data);
};
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами и условиями.
import org.kl.state.Else;
import org.kl.state.Null;
import static org.kl.pattern.GuardPattern.matches;
matches(data,
Integer.class, i -> i != 0, i -> { System.out.println(i * i); },
Byte.class, b -> b > -1, b -> { System.out.println(b * b); },
Long.class, l -> l == 5, l -> { System.out.println(l * l); },
Null.class, () -> { System.out.println("Null value "); },
Else.class, () -> { System.out.println("Default value: " + data); }
);
Для упрощения написания условия, разработчик может использовать следующее функции для сравнения: lessThan/lt, greaterThan/gt, lessThanOrEqual/le, greaterThanOrEqual/ge,
equal/eq, notEqual/ne. А для того чтобы опустить условия можно пременить: always/yes, never/no.
matches(data,
Integer.class, ne(0), i -> { System.out.println(i * i); },
Byte.class, gt(-1), b -> { System.out.println(b * b); },
Long.class, eq(5), l -> { System.out.println(l * l); },
Null.class, () -> { System.out.println("Null value "); },
Else.class, () -> { System.out.println("Default value: " + data); }
);
Deconstruction pattern позволяет одновременно сопоставить тип и разложить объект на составляющие. В Java для этого нам нужно сначала проверить тип, привести к типу, присвоить новой переменной и только тогда через геттеры доступиться к полям класса.
let (int w, int h) = figure;
switch (figure) {
case Rectangle(int w, int h) -> out.println("square: " + (w * h));
case Circle(int r) -> out.println("square: " + (2 * Math.PI * r));
default -> out.println("Default square: " + 0);
};
for ((int w, int h) : listFigures) {
System.out.println("square: " + (w * h));
}
В данный момент библиотека поддерживает возможность проверять значения переменной с 3 типами и раскладывать объект на 3 составляющее.
import org.kl.state.Else;
import static org.kl.pattern.DeconstructPattern.matches;
import static org.kl.pattern.DeconstructPattern.foreach;
import static org.kl.pattern.DeconstructPattern.let;
Figure figure = new Rectangle();
let(figure, (int w, int h) -> {
System.out.println("border: " + w + " " + h));
});
matches(figure,
Rectangle.class, (int w, int h) -> out.println("square: " + (w * h)),
Circle.class, (int r) -> out.println("square: " + (2 * Math.PI * r)),
Else.class, () -> out.println("Default square: " + 0)
);
foreach(listRectangles, (int w, int h) -> {
System.out.println("square: " + (w * h));
});
При этом чтобы получить составляющее, класс должен иметь один или несколько деконструирующих методов. Эти методы должны быть помечены аннотаций Extract.
Все параметры должны быть открытыми. Поскольку примитивы нельзя передать в метод по ссылке, нужно использовать обертки на примитивы IntRef, FloatRef и т.д.
import org.kl.type.IntRef;
...
@Extract
public void deconstruct(IntRef width, IntRef height) {
width.set(this.width);
height.set(this.height);
}
Также используя Java 11, можно выводить типы деконструирующих параметров.
Figure figure = new Rectangle();
let(figure, (var w, var h) -> {
System.out.println("border: " + w + " " + h));
});
matches(figure,
Rectangle.class, (var w, var h) -> out.println("square: " + (w * h)),
Circle.class, (var r) -> out.println("square: " + (2 * Math.PI * r)),
Else.class, () -> out.println("Default square: " + 0)
);
foreach(listRectangles, (var w, var h) -> {
System.out.println("square: " + (w * h));
});
Property pattern позволяет одновременно сопоставить тип и доступиться к полям класса по их именам. Вместо того, чтобы предоставить все поля, можно доступиться к нужным и в любой последовательности.
let (w: int w, h:int h) = figure;
switch (figure) {
case Rect(w: int w == 5, h: int h == 10) -> out.println("sqr: " + (w * h));
case Rect(w: int w == 10, h: int h == 15) -> out.println("sqr: " + (w * h));
case Circle (r: int r) -> out.println("sqr: " + (2 * Math.PI * r));
default -> out.println("Default sqr: " + 0);
};
for ((w: int w, h: int h) : listRectangles) {
System.out.println("square: " + (w * h));
}
В данный момент библиотека поддерживает возможность проверять значения переменной с 3 типами и раскладывать объект на 3 составляющее.
import org.kl.state.Else;
import static org.kl.pattern.PropertyPattern.matches;
import static org.kl.pattern.PropertyPattern.foreach;
import static org.kl.pattern.PropertyPattern.let;
import static org.kl.pattern.PropertyPattern.of;
Figure figure = new Rectangle();
let(figure, of("w", "h"), (int w, int h) -> {
System.out.println("border: " + w + " " + h));
});
matches(figure,
Rect.class, of("w", 5, "h", 10), (int w, int h) -> out.println("sqr: " + (w * h)),
Rect.class, of("w", 10, "h", 15), (int w, int h) -> out.println("sqr: " + (w * h)),
Circle.class, of("r"), (int r) -> out.println("sqr: " + (2 * Math.PI * r)),
Else.class, () -> out.println("Default sqr: " + 0)
);
foreach(listRectangles, of("x", "y"), (int w, int h) -> {
System.out.println("square: " + (w * h));
});
Также для упрощения именования полей можно использовать другой способ.
Figure figure = new Rect();
let(figure, Rect::w, Rect::h, (int w, int h) -> {
System.out.println("border: " + w + " " + h));
});
matches(figure,
Rect.class, Rect::w, Rect::h, (int w, int h) -> out.println("sqr: " + (w * h)),
Circle.class, Circle::r, (int r) -> out.println("sqr: " + (2 * Math.PI * r)),
Else.class, () -> System.out.println("Default sqr: " + 0)
);
foreach(listRectangles, Rect::w, Rect::h, (int w, int h) -> {
System.out.println("square: " + (w * h));
});
Position pattern позволяет одновременно сопоставить тип и проверить значение полей в порядке объявления. В Java для этого нам нужно сначала проверить тип, привести к типу, присвоить новой переменной и только тогда через геттеры доступиться к полям класса и проверить на равность.
switch (data) {
case Circle(5) -> System.out.println("small circle");
case Circle(15) -> System.out.println("middle circle");
case null -> System.out.println("Null value ");
default -> System.out.println("Default value: " + data);
};
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами и проверять сразу 4 поля.
import org.kl.state.Else;
import org.kl.state.Null;
import static org.kl.pattern.PositionPattern.matches;
import static org.kl.pattern.PositionPattern.of;
matches(data,
Circle.class, of(5), () -> { System.out.println("small circle"); },
Circle.class, of(15), () -> { System.out.println("middle circle"); },
Null.class, () -> { System.out.println("Null value "); },
Else.class, () -> { System.out.println("Default value: " + data); }
);
Также если разработчик не хочет проверять некоторые поля, эти поля должны быть помечены аннотаций @Exclude. Эти поля должны быть объявлены последними.
class Circle {
private int radius;
@Exclude
private int temp;
}
Static pattern позволяет одновременно сопоставить тип и деконструировать объект используя фабричные методы.
Optional some = ...;
switch (some) {
case Optional.empty() -> System.out.println("empty value");
case Optional.of(var v) -> System.out.println("value: " + v);
default -> System.out.println("Default value");
};
В данный момент библиотека поддерживает возможность проверять значения переменной с 6 типами и раскладывать объект на 3 составляющее.
import org.kl.state.Else;
import static org.kl.pattern.StaticPattern.matches;
import static org.kl.pattern.StaticPattern.of;
Optional some = ...;
matches(figure,
Optinal.class, of("empty"), () -> System.out.println("empty value"),
Optinal.class, of("of") , (var v) -> System.out.println("value: " + v),
Else.class, () -> System.out.println("Default value")
);
При этом чтобы получить составляющее, класс должен иметь один или несколько деконструирующих методов, помеченные аннотаций Extract.
@Extract
public void of(IntRef value) {
value.set(this.value);
}
Sequence pattern позволяет проще обрабатывать последовательности данных.
List<Integer> list = ...;
switch (list) {
case Ranges.head(var h) -> System.out.println("list head: " + h);
case Ranges.tail(var t) -> System.out.println("list tail: " + t);
case Ranges.empty() -> System.out.println("Empty value");
default -> System.out.println("Default value");
};
Используя библиотеку можно писать следующим образом.
import org.kl.state.Else;
import org.kl.range.Ranges;
import static org.kl.pattern.SequencePattern.matches;
List<Integer> list = ...;
matches(figure,
Ranges.head(), (var h) -> System.out.println("list head: " + h),
Ranges.tail(), (var t) -> System.out.println("list tail: " + t),
Ranges.empty() () -> System.out.println("Empty value"),
Else.class, () -> System.out.println("Default value")
);
Также для упрощения кода, можно использовать следующее функции.
import static org.kl.pattern.CommonPattern.with;
import static org.kl.pattern.CommonPattern.when;
Rectangle rect = new Rectangle();
with(rect, it -> {
it.setWidth(5);
it.setHeight(10);
});
when(
side == Side.LEFT, () -> System.out.println("left value"),
side == Side.RIGHT, () -> System.out.println("right value")
);
Как можно видеть pattern matching сильный инструмент, который намного упрощает написание кода. Используя лямбды-выражения, ссылки на метод и вывод типов параметров лямбды можно сэмулировать возможности pattern matching самыми средствами языка.
Исходной код библиотеки открыт и доступный на github.
Комментарии (37)
datacompboy
20.07.2019 19:05+1Вообще по статье: гораздо интереснее читать как это было реализовано, какие накладыне расходы, что с совместимостью, какие требования.
aleksandy
20.07.2019 20:34Препроцессор какой-нибудь, типа ретролямбд, скорее всего.
datacompboy
20.07.2019 20:36+1тогда откуда странное ограничение на число веток? :)
тут явно напрашивается «ненормальное программирование» в хабы и хотел бы прочесть как человек дошел до такой жизни.koowaah Автор
23.07.2019 13:58Поскольку для разных вариантов параметров лямбды используются перегрузки метода matches().
sshikov
20.07.2019 21:34+1Было бы неплохо сравнить скажем с vavr, где почти все тоже самое уже есть довольно давно.
jreznot
20.07.2019 22:32BTW, давно (4 года уже) существует библиотека Motif: github.com/johnlcox/motif
Она реализует Scala-like pattern matching для Java 8+koowaah Автор
23.07.2019 13:55Motif библиотека использует Java Stream style.
Эта библиотека предоставляет более простой и подобный к стилю написанию C# pattern matching. Не нужно писать when(), get() и другие методы.
kommie2050
21.07.2019 01:10только это не паттерн матчинг а инстанс валидатор.
короче вроде универсального визитора? Достаёт значения через рефлекшн и конструирует реальные инстанции обьекта и сравнивает по equals? Ну так джава код и не предназначет для массивной сериализации инстанций)))
боже какие замороты с тем что в питоне есть просто так.
Притащенная соответственно функциональщиками, людьми, которые не знают что такое инкапсуляция.
есть также принцип разделения функций. Я тоже взглянув с перва подумал что это быдлокод со свичами, заменяющими полиморфизм, но тут можно выделить произвольную проверку в отдельный класс — принцип визитора
И кстати много кода не нужно. Тут в один небольшой класс поместится весь функционал вышеперечисленного, если с рефлекшном, а если без, то как вы достаёте поля?
А всё, на гитхабе кто посмотрит тот поймёт что достают рефлекшном например через fields. Можно делать одним классом вроде кода ниже только расширить.
package utils; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class AnyValidator { private Class lastClass = null; private String lastResult = ""; private Object dummy = new Integer(10); public boolean validateFields(Object instance, Map<String, Object> mp) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { lastClass = instance.getClass(); Iterator<Map.Entry<String, Object>> it = mp.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Object> entry = (Map.Entry<String, Object>)it.next(); String fieldName = entry.getKey(); Object goodValue = entry.getValue(); boolean isGoodField = equalsObjectField(fieldName, instance, goodValue); if (! isGoodField) { lastResult = "validateFields: field " + fieldName + " is wrong"; return false; } } return true; } public boolean equalsObjectField(String fieldName, Object instance, Object goodValue) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Field field = lastClass.getDeclaredField(fieldName); Object fieldValue = field.get(instance); if (goodValue == null) return fieldValue == null; return goodValue.equals(fieldValue); } public static void main(String[] args) { AnyValidator dummy = new AnyValidator(); AnyValidator val = new AnyValidator(); Map<String, Object> mp = new HashMap<String, Object> (); mp.put("dummy" , new Integer(10)); mp.put("lastResult" , ""); mp.put("lastClass" , null); try { boolean res = val.validateFields(dummy, mp); System.out.println(res); dummy.dummy = new Integer(11); res = val.validateFields(dummy, mp); System.out.println(res); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
koowaah Автор
21.07.2019 07:53Никакой магии под капотом нету. Используется рефлексия где нужно и те же if/else.
Ограничения на количество веток вызвано тем что, что для каждого случая используется разные перегрузки функции matches().
koowaah Автор
21.07.2019 08:01По сравнению с if/else цепочкой, matches() методы под капотом делает тоже, но где нужно используют рефлексия.
Накладные расходы добавляются с рефлексией, но без неё не как.
Написано на чистой Java 8.PROgrammer_JARvis
23.07.2019 13:43+1Не рассматривали, в качестве оптимизации, замену рефлексии возможностями
java.lang.invoke
, а, особенно, LambdaMetaFactory, т.е. генерацию на лету для вызовов методов реализаций неких функциональных интерфейсов? + кэширование соответствующих вещей
Допустим, в ВашемDeconstructPattern
вызовverifyExtractMethods(..)
основывается на (для каждого вызова) анализе класса для поиска аннотированных методов (takeExtractMethods(..)
) и дальнейшем их вызове через открытие доступа и рефлективый вызов. Вместо этого же можно было результат поиска метода кэшировать, причём для вызова самого метода генерируя объект функ. интерфейса, а ля
interface Extractor { void extract(Object... parameters); }
или даже JDKшный
Consumer<Object[]>
,
что позволило бы избавиться от постоянных накладных расходов на открытие доступа к рефлективной сущности, да и сделало код более явным.
Как по мне, учитывая то, что эта библиотека, в первую очередь, синтаксический сахар, вопрос производительности тут критичен, потому что, даже принимая во внимание магию JIT, сахар с сравнительно большим оверхедом проигрывает более многословным, но примитивных, с точки зрения генерируемого байткода, способам достижения такой же логики.
Если же углубляться в оптимизации, то можно попробовать генерировать в рантайме полностью свои сущности для доступа к каким-то вещам, или даже, используя Intrumentation API, при наличии соответствующего агента, заменять при загрузке засахаренных классов вызовы сложных методов байткодом подобного же, но без сахара (e.g, для всё того же Deconstructor'а производить, фактически, инструкции доступа к полям.
В целом же необычных подход к добавлению сахара :)
При вашем желании, могу накидать Issues на гитхабе
koowaah Автор
23.07.2019 13:48Идею использования java.lang.invoke, а, особенно, LambdaMetaFactory не рассматривал для оптимизации. Но обязательно попробую рассмотреть этот вариант.
Можете более детально описать как именно можно закэшировать, вместо прямого поиска и вызова метода через рефлексию?
На гитхаб можете писать Issues, буду рад.PROgrammer_JARvis
24.07.2019 13:16Можете более детально описать как именно можно закэшировать, вместо прямого поиска и вызова метода через рефлексию?
Что касается кэша, то, с точки зрения реализации, тут варианты любые, в зависимости от ваших же предпочтений (JDK: Map, Guava: Cache и т.д.). Я, скорее, говорил о подходе, что, учитывая, что у вас зачастую происходит вызов методов с одинаковыми параметрами, для которых происходит полный анализ класса, уместно результат этих самых вычислений сохранять для дальнейшего переиспользования.
Для примера, тот самый<V> List<Method> takeExtractMethods(V value)
уместно заменить наList<Method> getExtractMethods(Class<?> value)
(простите мне моё переименование, не позволяет тут совестьtake
оставить :) ) (кстати, генерик тоже тут излишен), который будет анализировать содержимое класса (ваше нынешнее тело метода) только в том случае, если в кэше для данного класса это ещё не найдено, после чего это значение будет заноситься в этот самый кэш, дабы при повторном вызове не пришлось производить эту затратную операцию. С этой точки зрения особенно хороша, как по мне, реализация Guava, так как она и функциональна (V get(K, Callable<? extends V>)
, гдеK
иV
типы ключей и значений соотв-но) и позволяет удобно настраивать режим освобождения кэша.
На гитхаб можете писать Issues, буду рад.
Отлишно, постараюсь накидать в ближайшее время
koowaah Автор
24.07.2019 14:07Ок. Попробую реализовать кеш аналогичен Guava Cache.
И внедрить в проект.
koowaah Автор
23.07.2019 10:17Вдохновениям для реализации библиотеки для паттерн матчинга стал С#. В С# 7,8 добавлены почти все эти паттерны. Как можно видеть паттерн матчинг прекрасно вписывается в С#.
З другой стороны паттерн матчинг является одной с приоритетных направлений Java.
Есть уже два jep:
pattern matching for instanceof.
pattern matching for switch.
Но ждать прийдется какия минимум к LTS Java 17 а то и больше.
pin2t
Pattern matching — функциональщина, притащенная «во многие языки» без понимания принципов, просто для того чтобы была лишняя фича, в рамках «развития языка». Притащенная соответственно функциональщиками, людьми, которые не знают что такое инкапсуляция.
Печально смотреть, как Java, хороший язык, со стройными изначально принципами ООП, все больше и больше превращают в PHP. Даже термин специальный придумали — деконструкция, ужас какой.
datacompboy
Простите, а при чем тут PHP?
pin2t
PHP просто как пример того как бездумное добавление новых возможностей в язык в итоге превращает его в непонятно что. Место PHP в этом примере легко займут и JavaScript, и Kotlin
datacompboy
Как ни странно, но ФП вполне сочетается с ООП. Даже в одном проекте.
Главное использовать подходящий к задаче в текущий момент.
pin2t
Вообще-то нет, не сочетается, причем совсем. Вот рассмотрим «чудесный» паттерн декоструктор из статьи
В ООП это было бы
или
Но никак не так как в примерах из статьи. Тоесть, либо одно, либо другое, вместе никак.
Ну а если в программе возникает необходимость писать подобный код
это значит лишь то что объектно-ориентированный дизайн зашел куда-то совсем не туда. Ну или это код внутри самой JDK, что врядли.
sshikov
Совершенно независимо от качества примеров автора (и ограниченности данной библиотеки), вы похоже не понимаете, что такое pattern matching, и зачем он нужен.
>В ООП это было бы
>out.println(String.format(«square: %f», figure.square()));
Не было бы. Представьте на мгновение, что figure — это не ваши классы, что они написаны сторонним разработчиком, и менять (а то и посмотреть на их код) вы не можете.
>зашел куда-то совсем не туда
А даже если и зашел? Если часть этого кода не ваша, у вас мало шансов поменять там дизайн.
И да, то что тут рассмотрено — это библиотека, она не может превращать Java как язык во что-либо.
pin2t
Я прекрасно понимаю что это за паттерн и зачем он нужен. И что он принципиально не совместим с Java.
В этом случае тем более никак и никогда не могу использовать паттерн матчинг, потому что все что я вижу из библиотеки — это интерфейсы. я не вижу внутренностей объектов. Попытка использовать тяжелую артиллерию вроде рефлекшена для того чтобы подсмотреть внутренности объектов в библиотеке — это ядерная бомба замедленного действия. В следующей версии библиотеки автор поменяет свои объекты, и весь паттерн матчинг рухнет как карточный домик.
Если я хочу что-то добавить к коду библиотеки, я оборачиваю библиотечные классы своими классами, но никак не лезу рефлекшеном внутрь библиотечных классов.
Вы просто не понимаете суть ООП, если для вас добавление чего-то к библиотечному коду это обязательно означает разобрать объект на кусочки.
mayorovp
Простите, а как связаны pattern matching и залезание рефлексией внутрь чужих объектов?
sshikov
>Я прекрасно понимаю что это за паттерн и зачем он нужен.
Совершенно не похоже. Вы бы хоть википедию что-ли прочитали? В тамошней вполне приличной формулировке матчинг — это поиск паттерна в последовательности токенов . Не в объекте — это лишь частный случай, а в последовательности объектов. Типичный случай — дерево (ну или список из головы и хвоста).
datacompboy
примеры из статьи — искуственные и показывают что можно делать (но отнюдь не что нужно).
сопоставление с образцом используется в конструировании потока управления данными, например. писать отдельный класс под каждый промежуточный вариант — та еще тухлятина.
те же конечные автоматы гораздо удобнее получаются.
еще хорошо для _лаконичных_ деконструкций. когда метод возвращает например два значения — и распаковка pair в две отдельных переменных это тоже сопоставление с образцом. особенно если нужна только одна из них дальше.
да, всегда можно написать иначе. ну на то и сахар, чтоб вкуснее было.
kommie2050
это делается через визитор. Вот такие свичи это практичеси всегда быдлокод.
не очень оптимально использовать рефлекшн, потому что он дольше чем обычный доступ к переменным. Тут прикручивается стирание классов например Питона к яве. Так берите Питон для этой задачи.
alex_blank
> это делается через визитор. Вот такие свичи это практичеси всегда быдлокод.
Визитор это и есть такой свитч, только obscured.
kommie2050
быдлокод в том что ты делаешь ветку вручную в то время как то же самое сделает для тебя компилятор такой фичей ОО как полиморфизм функций. Кроме того ручной свич работает в жаюе медленнее чем вызов функций — давным давно протестил. Я согласен что следует ввести другой синтаксис для визитора а не как сейчас через зад. Но это не оправдывает свич.
mayorovp
быдлокод в том, что приходится писать все эти фунцкии вручную ради всего одного вызова, в то время как это мог бы сделать поддерживающий сопоставление с образцом компилятор
sshikov
Быдлокод оно именно потому, что язык не поддерживает матчинг как следует — например, не отлавливает, все ли возможные случаи были рассмотрены.
Ну и да, визитор — это неудобный (частный, для ООП) способ реализации матчинга, если угодно.
pin2t
Приведите нормальный пример, который доказывает нужность этой концепции и этой библиотеки. Пока что все абсолютно примеры доказывают её вредность и ненужность.
Ну может вот этот только можно использовать
pin2t
Сопоставление с образцом используется в функциональных языках, потому что в них нет понятия инкапсуляции и типов (в том понимании в котором они используются в Java например).
В Java да, именно что под каждый промежуточный вариант надо делать свой класс. Так принято в Java. Но ненадо в Java тащить чужеродные концепции. Если не умеете программировать на Java, и очень хочется использовать pattern matching — идите в Haskel, Lisp, Scala и т.п.
datacompboy
Перевожу что вы сказали: «не трогайте то к чему я привык».
ОК, вам не нужен синтаксический сахар.
Я знаю как жавистам больно от введения «var».
Простите. Придётся учиться, энтерпрайз не повод продолжать писать как 50 лет назад.
sshikov
>Я знаю как жавистам больно от введения «var».
Вот не надо обобщать. Это мнение одного человека, не более того. У меня вот энтерпрайз в чистом виде — и никто даже не задумается, применять ли матчинг, если он вдруг появится в Java (в Scala — уже).