У Котлина нет точного соответствия шаблону, и это нормально. Чтобы создать подходящие классы в Scala, для его работы требуется очень много накладных расходов, и я очень уважаю цель Котлина — нигде не добавлять много накладных расходов. Но это не значит, что мы не можем попытаться найти собственный способ приблизиться к сопоставлению с образцом.
Использование when
Котлин, when
блок невероятно удобен; У этого есть несколько способов, которыми это может работать. Первый способ — это простая проверка на равенство:
1
2
3
4
5
|
when (x) { 1 -> print( "x == 1" ) 2 -> print( "x == 2" ) else -> print( "x is neither 1 nor 2" ) } |
И случаи могут быть объединены с помощью запятой:
1
2
3
4
|
when (x) { 0 , 1 -> print( "x == 0 or x == 1" ) else -> print( "otherwise" ) } |
Это также можно сделать и in
проверках:
1
2
3
4
|
when(x) { in 1 .. 10 -> print( "in range" ) is String -> print( "I guess it's not even a number" ) } |
И с последним, вы можете видеть, что вы можете объединить любой из предыдущих в один блок when
. Вам также не нужно else
если вы используете when
как выражение вместо выражения. Вам также не нужно else
если версия выражения имеет все перечисленные возможности (насколько может сказать компилятор).
Вы также можете использовать без значения top, чтобы он просто работал как набор блоков if-else if
:
1
2
3
4
5
|
when { a == b -> doSomething() b == c -> doSomethingElse() else -> doThatOtherThing() } |
Имея все эти возможности, знаете ли вы, какую версию мы будем использовать для построения нашей системы сопоставления с образцом? Удивительно, но это самый простой с проверками на равенство.
Теперь я понимаю, что вы можете делать sealed
классы как своего рода объединенный тип, и is
когда нужно сопоставлять их, но у этого есть ограниченный набор вариантов использования. Я полагаю, что с помощью следующей системы вы сможете охватить все варианты использования.
Так как мы это делаем?
Во-первых, мы понимаем, что проверки на равенство используют equals()
и equals()
— это то, что мы можем переопределить. Итак, мы делаем какой-то тип Pattern
для использования в блоке when
, и equals()
проверяет, является ли объект is Pattern
и переходит к использованию Pattern
для вычисления «равенства».
Вот краткий обзор того, как это выглядит слабо:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
interface Pattern<in Subject> { fun match(subject: Subject): Boolean } class MySubject { … fun equals(other: Any): Boolean { if (other is Pattern<*>) return other.match( this ) else … } } class SomePattern { override fun match(subject: Any): Boolean { … } } |
И это будет использоваться следующим образом:
1
2
3
4
5
6
7
|
val x = MySubject() … when(x) { SomePattern() -> doSomething() SomeOtherPattern() -> doSomethingElse() } So, you probably get the idea now. |
Tweaks
Есть довольно много вещей, которые можно сделать, чтобы изменить эту идею, чтобы сделать ее более приемлемой в различных ситуациях.
Shortcutting
Во-первых, вы можете попытаться сделать шаблоны немного более доступными, используя ярлыки на предметном классе. Если шаблон параметризован — например, List
может иметь параметризованный шаблон, который проверяет определенную длину, IsLength
который должен принимать параметр для длины — вы можете поместить функцию-ярлык в companion object
вместо прямого вызова конструктор класса. Если он не параметризован, вы можете кэшировать экземпляр шаблона как значение в companion object
предметного класса.
Лямбда-паттерн
Интерфейс Pattern
имеет только один метод. Вы знаете что это значит? Это функциональный интерфейс (в терминах Java 8). Это означает, что в Kotlin Pattern
даже не нужно существовать. Вместо того, чтобы equals()
проверял, является ли объект Pattern
, пусть он проверяет, является ли это Function1<SubjectType, Boolean>
. Очевидно, что вы все еще можете использовать некоторые встроенные шаблоны, но теперь вы можете даже вставить некоторые лямбды на лету в блок when
:
1
2
3
|
when(x) { {it: Subject -> it.isTheCoolest} -> doSomething() } |
К сожалению, это не так уж и полезно, поскольку вывод типа не сможет определить тип входного параметра. Тебе надо. В этот момент вы также можете использовать непараметризованный блок:
1
2
3
|
when { x.isTheCoolest -> doSomething() } |
Это не значит, что использование лямбд для шаблона — это плохо. Вы по-прежнему можете использовать ссылки на методы, что делает возможным быстрые и простые шаблоны на лету (даже для свойств):
1
2
3
|
when(x) { Subject::isTheCoolest -> doSomething() } |
Это, безусловно, лучше, чем полностью квалифицированная лямбда. Более сложные лямбды могут быть определены как функции или значения:
01
02
03
04
05
06
07
08
09
10
|
fun moreComplexCheck(subject: Subject): Boolean { … } val moreComplexCheck2 = {subject: Subject -> …} when(x) { ::moreComplexCheck -> doSomething() moreComplexCheck2 -> doSomethingElse() } |
Outro
Итак, вот оно! Лучшее сопоставление с образцом в Котлине! Что вы думаете? Я понимаю, что это неправильное использование equals()
, но я думаю, что в некоторых случаях оно того стоит.
Ссылка: | Усовершенствованное сопоставление с образцом в Котлине от нашего партнера по JCG Джейкоба Циммермана из блога « Идеи программирования с Джейком» . |