Привет, Dev.to ! Сегодня мы исследуем сопоставление шаблонов . Это то, чем славятся функциональные языки. Java и другие C-подобные языки могут “имитировать” его в некоторой степени (используя несколько операторов if или switch), но только в некоторой степени. По умолчанию Java не может справиться со сложными условиями сопоставления с образцом, но с Vavr это намного проще реализовать.
В этом посте мы будем следовать этому подходу: мы начнем с краткого погружения в теорию, а затем подробно изучим API сопоставления шаблонов Var. Кстати, эта тема тесно связана с другими функциями Vavr, такими как Опция , Потоки и Попробуйте .
Что такое сопоставление с образцом?
Давайте немного углубимся в теорию и посмотрим, что компьютерная наука понимает под сопоставлением шаблонов . С технической точки зрения, это механизм для проверки значения по шаблону. Мы должны отличать это от распознавания образов: в отличие от последних, сопоставление с образцом утверждает точные совпадения.
Как упоминалось ранее, C-подобные языки могут в некоторой степени эмулировать функцию сопоставления с образцом. Например, мы можем использовать операторы switch для выполнения некоторой логики, основанной на совпадении (например, имени).:
String name = "Carolina";
switch(name){
case "Aneta":
System.out.println("Hello, Aneta!");
break;
case "Barbora":
System.out.println("Hello, Barbora!");
break;
case "Carolina":
System.out.println("Hello, Carolina!");
break;
case "Denisa":
System.out.println("Hello, Denisa!");
break;
default:
System.out.println("Hello, stranger!");
break;
}
Другое поведение – установить значение на основе входных данных. Давайте взглянем на другой фрагмент кода:
enum DIAS{
LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO
}
String weekday;
Dias dia = Dias.MIERCOLES;
switch(dia){
case LUNES:
weekday = "Monday";
break;
case MARTES:
weekday = "Tuesday";
break;
case MIERCOLES:
weekday = "Wednesday";
break;
case JUEVES:
weekday = "Thursday";
break;
case VIERNES:
weekday = "Friday";
break;
case SABADO:
weekday = "Saturday";
break;
case DOMINGO:
weekday = "Sunday";
break;
}
System.out.println(weekday); // output: Wednesday
Вы можете добиться одинаковых результатов с помощью нескольких операторов if. Однако оба примера очень просты. Но что, если нам нужно написать более сложные условия? Представьте, что у нас есть приложение, которое проверяет, является ли имя пользователя мужчиной или женщиной. Хотя кому-то это может показаться дискриминацией, в славянских языках нам нужно определить пол пользователей, чтобы предоставлять грамматически правильные сообщения. На этот раз давайте использовать операторы if:
String name = "Barbora";
String gender;
if (name.equalsIgnoreCase("Adam") || name.equalsIgnoreCase("Boris")){
gender = "male";
} else if (name.equalsIgnoreCase("Anna") || name.equalsIgnoreCase("Barbora")){
gender = "female";
}
System.out.println(gender); // output: female
Что ж, наше приложение довольно необразованно: оно знает только 4 имени! Представьте себе, если мы добавим к этому условию еще имена (только чешские, а на планете есть и другие славянские страны!)… В принципе, что нам нужно сделать, так это иметь 2 списка имен: мужские и женские, а затем утверждать, если ввод находится в конкретном списке. Что ж, мы можем сделать это так:
ListfemaleNames = ...; List maleNames = ...; String name = "Lukas"; String gender; for (String n:femaleNames){ if (name.equalsIgnoreCase(n)){ gender = "female"; } } for (String n:maleNames){ if (name.equalsIgnoreCase(n)){ gender = "male"; } } System.out.println(gender); //output = "male"
Как вы можете видеть, хотя мы можем имитировать некоторые сопоставления с образцами, используя “собственные” инструменты Java, они очень ограничены. Надеюсь, вам не нужно изучать Haskell (хотя это хорошая идея), если вы хотите использовать сопоставление шаблонов в своем приложении. Вы можете использовать библиотеку Var. Теперь давайте посмотрим, как это сделать.
Анатомия API сопоставления шаблонов Var
Во-первых, мы разберемся со структурой API сопоставления с образцами в библиотеке Var. Он состоит из трех основных элементов: Совпадение , Падеж и шаблонов. Давайте исследуем их шаг за шагом.
Матч
Var предоставляет API соответствия это близко к соответствию Scala. Он представлен классом io.var. интерфейс прикладного программирования.Совпадение . Это отправная точка сопоставления с образцом, которая принимает значение, которое нам нужно будет сопоставить . Давайте взглянем на фрагмент кода ниже:
String dia = "Miercoles";
String weekday = Match(dia).of(
Case($("Lunes"), "Monday"),
Case($("Martes"), "Tuesday"),
Case($("Miercoles"), "Wednesday"),
Case($("Jueves"), "Thursday"),
Case($("Viernes"), "Friday"),
Case($("Sabado"), "Saturday"),
Case($("Domingo"), "Sunday"));
Что-то вроде этого! С технической точки зрения, класс Match представляет собой простую структуру:
- Принимает входное значение в качестве аргумента
- Создает новое значение в результате сопоставления условий как значение или опция .
Мы уже исследовали Опция Var ранее. В примере с днями мы получаем точное значение в результате сопоставления с образцом. Но мы тоже можем вернуть Опцию . Проверьте этот код:
String number = "Four"; Optiondigit = Match(number).of( Case($("One"), 1), Case($("Two"), 2), Case($("Three"), 3)); if (digit.isDefined()){ System.out.println("Result is: "+digit.get()); } else { System.out.println("No result!"); }
Таким образом, нам не нужно всегда возвращать точное значение, так как мы можем обернуть результат сопоставления с образцом в качестве опции. Давайте теперь перейдем к делам.
Случай
Случаи определены в API.Матч. Случай . Как вы могли заметить из предыдущего примера, этот класс позволяет сопоставлять условные шаблоны. Как правило, он имеет следующую структуру:
Case($(predicate), ...)
С помощью Case мы можем написать гораздо более лаконичный код. Есть две части:
- Во-первых, часть со знаком доллара (это напоминает мне синтаксис jQuery) – это условие для соответствия
- Вторая часть – произведенная стоимость
Как мы отметим позже, условия могут быть простыми и сложными. Взгляните на этот пример:
int integer = 3;
String string = Match(integer).of(
Case($(1), "one"),
Case($(2), "two"),
Case($(), "?")
);
System.out.println(string);
Это очень простое утверждение. Мы можем использовать различные встроенные шаблоны для построения условий, а также комбинировать несколько условий в одном предикате. Давайте изменим наше предыдущее имя с помощью родов и имен, используя находится в предикате из Vavr:
String name = "Carolina";
String gender = Match(name).of(
Case($(isIn("Anna", "Beata", "Carolina", "Denisa")), "female"),
Case($(isIn("Adam", "Boris", "Cyril", "David")), "male"));
System.out.println(gender);
Писать сложные условия с помощью Vavr намного проще, чем с помощью инструментов Java. Теперь давайте перейдем к шаблонам.
Узоры
Давайте взглянем на условия, предусмотренные в предыдущих примерах. Вы можете отметить, что они используют несколько “моделей”. В Vavr есть три типа шаблонов:
- $(точное значение) наиболее распространенный шаблон. Мы проверяем, что значение соответствует точному значению предоставленному.
- $() подстановочный знак . Мы используем этот шаблон, как по умолчанию в переключателях. В предыдущем примере мы использовали $() для обработки случаев, когда входные данные не соответствуют какому-либо предоставленному шаблону.
- $(предикат) по этому шаблону мы можем применить функцию предиката к входным данным, и результирующее логическое значение используется для принятия решения.
Мы уже видели использование первых двух типов: $ (точное значение) и $() подстановочный знак. Мы также можем использовать предикаты – в Var есть различные встроенные предикаты, облегчающие жизнь нашему разработчику. Давайте исследуем их.
Встроенные предикаты
В предыдущей части, в примере с именами и родами, мы использовали встроенный есть в предикате. Есть много удобных предикатов, которые уже предоставлены нам Vavr. Вы можете найти их в io.var. Предикаты. * . Позвольте увидеть их.
является
Одним из наиболее распространенных предикатов является является предикатом. Мы уже видим примеры использования Var для сопоставления с образцом. является предикат проверяет, равен ли объект указанному значению, используя Objects.equals(Объект, Объект) для сравнения. Давайте проверим пример:
int integer = 2;
String string = Match(integer).of(
Case($(is(1)), "one"),
Case($(is(2)), "two"),
Case($(), "nothing")
);
В приведенном выше фрагменте кода мы использовали is для соответствия точному значению предоставленного входного аргумента. В то время как с примитивами и строками вы можете отбросить предикаты и использовать только шаблон $ (точное значение) , с объектами вам нужно использовать is , который будет утверждать равенство объектов на основе реализации метода equals() .
исИн
Мы уже говорили раньше, что находится в – это удобный способ утверждать, что значение принадлежит определенному диапазону объектов, аналогично тому, что мы сделали с именами и полами. Вот еще одно использование В предикате – проверьте четное/нечетное число в стиле Вавра:
int number = 6;
String result = Match(number).of(
Case($(isIn(2, 4, 6, 8)), "Even"),
Case($(isIn(1, 3, 5, 7, 9)), "Odd"));
System.out.println(result); //Even
Вы могли бы понять находится в предикате как содержит() метод коллекций. Когда вы используете этот предикат с пользовательскими объектами, пожалуйста, обратите внимание, что, как и в случае is , он также использует логику равно для сравнения входных данных и шаблона.
Является полным/не является полным
Входные данные могут быть нулевыми, поэтому мы можем использовать Var для проверки на нуль тоже. Для этого есть два встроенных предиката:
- isNull проверяет, что предоставленные входные данные равны нулю
- не равно нулю проверяет, что предоставленные входные данные не равны нулю
Давайте приведем краткий пример:
Object object = null;
String result = Match(object).of(
Case($(isNull()), "Input is null"),
Case($(isNotNull()), "Input is not null"));
Экземпляр
В Java есть оператор instanceOf . Он используется для проверки того, является ли объект экземпляром указанного типа (класса, подкласса или интерфейса). В общем виде это выглядит так:
[Object] instanceof [Type]
Vavra также предлагает встроенный предикат instanceOf . Он проверяет, является ли объект экземпляром указанного типа. Предположим, у нас есть классы Слон и Панда , которые оба являются дочерними классами Животных . Взгляните на фрагмент кода ниже:
Elephant john = new Elephant("John");
Panda xiao = new Panda("Xiao");
String who = Match(xiao).of(
Case($(instanceOf(Elephant.class)), "This is an elephant, no doubts!"),
Case($(instanceOf(Animal.class)), "It can be an elephant or not. But I am sure that is an animal!"));
System.out.pritnln(who);
Вы можете найти Javadoc для API предикатов здесь . До сих пор мы использовали один предикат для сопоставления с образцом. Однако мы можем комбинировать несколько условий. Давайте рассмотрим, как это сделать.
Объединить несколько предикатов
Существует три основных способа объединения предикатов в одном условии – эти методы также являются частью Предикат пакет:
- любой из , который проверяет, удовлетворен ли хотя бы один из заданных предикатов.
- все из , которое проверяет, выполнены ли все заданные предикаты.
- ни один Из , который проверяет, не удовлетворен ли ни один из заданных предикатов.
Давайте рассмотрим на примере, как объединить несколько предикатов Var:
String word = "Tres";
String language = Match(word).of(
Case($(anyOf(isIn("Lunes", "Martes","Miercoles"), isIn("Uno", "Dos", "Tres"))), "Spanish"),
Case($(anyOf(isIn("Monday", "Tuesday", "Wednesday"), isIn("One", "Two", "Third"))), "English"));
System.out.println("Language is :"+language);
В приведенном ниже фрагменте кода мы объединяем два условия: слово либо находится в списке дней недели, либо в списке чисел. В случае совпадения Var сообщает нам, на каком языке существует указанное слово. Таким же образом мы можем создать условия, удовлетворяющие совпадению всех или ни одного предиката. Вот еще один пример:
Elephant elephant = new Elephant("John");
Elephant david = new Elephant("David");
Option result = Match(elephant).of(
Case($(allOf(instanceOf(Elephant.class), is(david))), "This elephant is David"));
if (result.isDefined()){
System.out.println(result.get());
}
Мы соответствуем этому все условия выполнены. Если вам нужно утвердить более конкретные условия, подумайте о том, чтобы написать свои собственные предикаты.
Создание пользовательских предикатов
В случае, если встроенные предикаты не обеспечивают требуемой функциональности, вы можете реализовать свои собственные предикаты, используя обычные Java-лямбды. Давайте взглянем на простой пример:
int number = 4;
String result = Match(number).of(
Case($(n->n%2==0), "even"),
Case($(n->n%2!=0), "odd"));
System.out.println(result);
В приведенном выше фрагменте кода мы используем лямбда-предикаты, чтобы утверждать, что входные данные четные или нечетные. Другим примером может быть задача FizzBuzz. На случай, если вы не знаете эту классическую задачу программирования: “Напишите программу, которая печатает числа от 1 до 100. Но для чисел, кратных трем, вместо числа выведите “Шипение”, а для чисел, кратных пяти, выведите “Гудение”. Для чисел, кратных трем и пяти, выведите “FizzBuzz”. ( ссылка ).
В этом примере мы можем использовать несколько из вышеперечисленных инструментов. Взгляните на мою реализацию:
int number = 45;
String message = Match(number).of(
Case($(allOf((n->n%3==0), n(n->n%5==0))), "FizzBuzz"),
Case($(n->n%5==0), "Buzz"),
Case($(n->n%3==0), "Fizz"),
Case($(), "Nothing"));
System.out.println(message);
В этом коде используется все предикат для утверждения обоих условий для печати Шипение . Затем есть две лямбды Java для проверки отдельных шаблонов, и, наконец, в нем есть шаблон подстановочный знак $() . У Грегора Трефса есть еще одна реализация FizzBuzz с сопоставлением шаблонов Vavr , которую я приглашаю вас проверить.
Вывод
В этом посте мы рассмотрели, как написать сопоставление шаблонов на Java. Хотя язык предлагает нам несколько способов имитировать эту концепцию функционального программирования, лучше использовать библиотеку Var. Мы начали со структуры Match API и его элементов. Затем мы наблюдали встроенные предикаты, предлагаемые из коробки, например is , находится в , не заполнен и т.д. Мы также рассмотрели, как использовать Java-лямбды для создания пользовательских предикатов и объединения нескольких условий с помощью Сплав , Любой из и ни Один Из . В будущем мы продолжим разговор о Vavr и функциональной Java. Тем временем, не стесняйтесь задавать вопросы в комментариях ниже или отправлять мне сообщения через социальные каналы.
Рекомендации
- Эмре Савчи Функциональное Программирование На Java и Сопоставление Шаблонов С VAVR (2019) Среда, читайте здесь
- Грегор Трефс Шесть способов создать FizzBuzz с помощью Vavr (2017) Точка доступа, читайте здесь
- Никита Павленко Как использовать сопоставление шаблонов Javaslang? (2017), читайте здесь
Оригинал: “https://dev.to/iuriimednikov/implement-pattern-matching-in-java-with-vavr-library-2amf”