问题描述
我正在处理字符串,并且遇到 Regex或Wildcard 答案:可以将正则表达式放入具有自定义类的when
语句,该类将覆盖equals.虽然这确实有效地使用了类型系统来将语法糖插入when
语句中,但我发现以下情况非常难看,并且绝不会在我打算与其他开发人员共享的代码中做到这一点(引用travis ):
I'm processing strings, and I came across the Regex or Wildcard answer: that one can put regular expressions in a when
statement with a custom class that overrides equals. While this does effectively use the type system to shoehorn syntactic sugar into the when
statement, I find the following pretty ugly, and would never do this in code that I intend to share with another developer (quoting travis):
import kotlin.text.regex
when (RegexWhenArgument(uri)) {
Regex(/* pattern */) -> /* do stuff */
Regex(/* pattern */) -> /* do stuff */
/* etc */
}
其中RegexWhenArgument的最小定义为:
Where RegexWhenArgument is minimally defined as:
class RegexWhenArgument (val whenArgument: CharSequence) {
operator fun equals(whenEntry: Regex) = whenEntry.matches(whenArgument)
override operator fun equals(whenEntry: Any?) = (whenArgument == whenEntry)
}
(结束报价)
我认为将arg传递给when
然后引用对arg类型进行操作的函数将更具可读性.举一个人为的例子:
I think it would be much more readable to pass an arg to when
and then reference functions that operate on the type of the arg. For a contrived example:
// local declaration
val startsWithFn: (String) -> Boolean = {s -> s.startsWith("fn:")}
when(givenString) {
::startsWithHelp -> printHelp()
startsWithFn -> println("Hello, ${givenString.substring(3)}!")
}
// package level function
fun startsWithHelp(s:String) = s.startsWith("help", true)
但是,当然,此代码不会编译.有没有一种方法可以做到可读,可维护和简洁?也许使用流?有经验的Kotlin开发人员会做什么?
But of course, this code doesn't compile. Is there a way to do this that's readable, maintainable, and concise? Maybe using Streams? What would an experienced Kotlin developer do?
推荐答案
有很多解决您的问题的方法.我将从简单的内容开始,然后再介绍更复杂的内容.
There are quite a few solutions to your problem. I will start with the simple ones and then move on to the more complex.
科林文档说:
用例:
when {
startsWithHelp(givenString) -> printHelp()
startsWithFn(givenString) -> println("Hello, ${givenString.substring(3)}!")
}
专家:
-
不需要任何其他代码
Doesn't require any additional code
易于理解
缺点:
- 样板
(givenString)
覆盖等号的参数包装器
所需的包装器与RegexWhenArgument
相似,但不检查Regex
,而是调用(String) -> Boolean
. String
没有什么特别的,所以我将使用<T>
.
Argument wrapper which overrides equals
The required wrapper is similar to RegexWhenArgument
, but instead of checking Regex
it invokes (String) -> Boolean
. There is nothing special about String
, so I will use <T>
.
包装器功能:
fun <T> whenArg(arg: T) = object {
override fun equals(other: Any?) =
if (other is Function1<*, *>)
(other as Function1<T, Boolean>)(arg)
else arg == other
}
用例:
when (whenArg(givenString)) {
::startsWithHelp -> printHelp()
startsWithFn -> println("Hello, ${givenString.substring(3)}!")
}
优点:
- 可以在
when
分支中使用功能 - 非常容易理解
- 只需要一个简单的包装函数
- Functions can be used in
when
branches - Very easy to understand
- Requires only one simple wrapper function
缺点:
- 容易忘记包装参数
- 错误类型的功能可能会在分支条件下意外使用
注意:此解决方案和下一个解决方案允许使用功能组合,可以通过以下扩展名来创建这些功能:
Note: this solution and the next one allows using functions combination, which can be created by these extensions:
infix fun <T> ((T) -> Boolean).and(other: ((T) -> Boolean)) = { it: T -> invoke(it) && other(it) }
infix fun <T> ((T) -> Boolean).or(other: ((T) -> Boolean)) = { it: T -> invoke(it) || other(it) }
operator fun <T> ((T) -> Boolean).not() = { it: T -> !invoke(it) }
基于DSL的when
模拟
在Kotlin中,DSL不仅可以用于构建对象,还可以用于创建自定义程序流结构.
DSL-based when
analog
In Kotlin DSL can be used not only for building objects but for creating custom program flow structures as well.
源代码:
@DslMarker
annotation class WhichDsl
// This object is used for preventing client code from creating nested
// branches. You can omit it if you need them, but I highly recommend
// not to do this because nested branches may be confusing.
@WhichDsl
object WhichCase
// R type parameter represents a type of expression result
@WhichDsl
class Which<T, R>(val arg: T) {
// R? is not used here because R can be nullable itself
var result: Holder<R>? = null
inline operator fun ((T) -> Boolean).invoke(code: WhichCase.() -> R) {
if (result == null && invoke(arg)) result = Holder(code(WhichCase))
}
// else analog
inline fun other(code: WhichCase.() -> R) = result?.element ?: code(WhichCase)
}
data class Holder<out T>(val element: T)
inline fun <T, R> which(arg: T, @BuilderInference code: Which<T, R>.() -> R) =
Which<T, R>(arg).code()
声明用例:
which(givenString) {
::startsWithHelp { printHelp() }
startsWithFn { println("Hello, ${givenString.substring(3)}!") }
}
表达用例:
val int = which(givenString) {
::startsWithHelp { 0 }
startsWithFn { 1 }
other { error("Unknown command: $givenString") }
}
优点:
- 可自定义的语法
- 可以添加新功能
- 可以添加仅适用于某些参数类型的功能
- 可以启用嵌套分支
- 没有样板
- 在编译时检查函数类型
- 易于阅读
缺点:
- 需要一些帮助程序类和功能
- 实验性
@BuilderInference
的用法(可以用用于表达式的显式类型声明和用于语句的单独方法代替) - 学习新的语法可能需要一些时间
- Requires a few helper classes and functions
- Usage of experimental
@BuilderInference
(it can be replaced with explicit type declaration for expressions and separate method for statements) - New syntax learning may take some time
扩展示例:
inline fun <R> Which<*, R>.orThrow(message: () -> String) =
other { throw NoWhenBranchMatchedException(message()) }
val <R> Which<*, R>.orThrow get() = orThrow { "No branch matches to $arg" }
inline fun <T, R> Which<T, R>.branch(condition: (T) -> Boolean, code: WhichCase.() -> R) =
condition(code)
inline fun <T, R> Which<T, R>.case(value: T, code: WhichCase.() -> R) =
branch({ it == value }, code)
fun <T : CharSequence, R> Which<T, R>.regex(regex: Regex, code: WhichCase.() -> R) =
branch({ regex.matches(it) }, code)
fun <T : Comparable<T>, R> Which<T, R>.range(range: ClosedRange<T>, code: WhichCase.() -> R) =
branch({ it in range }, code)
inline fun <T> Which<T, *>.sideBranch(condition: (T) -> Boolean, code: WhichCase.() -> Unit) {
if (condition(arg)) code(WhichCase)
}
fun <T> Which<T, *>.sideCase(value: T, code: WhichCase.() -> Unit) =
sideBranch({ it == value }, code)
inline fun <R> Which<*, R>.dropResult(condition: WhichCase.(R) -> Boolean = { _ -> true }) {
result?.let { (element) ->
if (WhichCase.condition(element)) result = null
}
}
inline fun <T, R> Which<T, R>.subWhich(condition: (T) -> Boolean, code: Which<T, R>.() -> R) =
branch(condition) {
which([email protected], code)
}
这篇关于在Kotlin`when`语句(或其他分支构造)中将函数或lambda作为条件包括在内的最简洁方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!