在 Kotlin `when` 语句(或其他分支构造)中包含函数或 lambda 作为条件的最简洁方法是什么?
What is the most concise way to include functions or lambdas as conditions in a Kotlin `when` statement (or other branching construct)?
我正在处理字符串,我遇到了 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 最小定义为:
class RegexWhenArgument (val whenArgument: CharSequence) {
operator fun equals(whenEntry: Regex) = whenEntry.matches(whenArgument)
override operator fun equals(whenEntry: Any?) = (whenArgument == whenEntry)
}
(引用结束)
我认为将 arg 传递给 when
然后引用对 arg 类型进行操作的函数会更具可读性。举个例子:
// 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 开发人员会做什么?
您的问题有很多解决方案。我将从简单的开始,然后再转向更复杂的。
没有参数时
when
can also be used as a replacement for an if
-else if
chain. If no
argument is supplied, the branch conditions are simply boolean
expressions, and a branch is executed when its condition is true
用例:
when {
startsWithHelp(givenString) -> printHelp()
startsWithFn(givenString) -> println("Hello, ${givenString.substring(3)}!")
}
优点:
不需要任何额外的代码
通俗易懂
缺点:
- 样板
(givenString)
覆盖 equals 的参数包装器
所需的包装器类似于 RegexWhenArgument
,但不是检查 Regex
,而是调用 (String) -> Boolean
。 String
没什么特别的,所以我就用<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
个分支中使用
- 非常容易理解
- 只需要一个简单的包装函数
缺点:
- 很容易忘记包装参数
- 在分支条件中可能会意外使用错误类型的函数
注意:这个解决方案和下一个允许使用函数组合,可以通过这些扩展创建:
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 不仅可以用于构建对象,还可以用于创建自定义程序流结构。
源代码:
@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
(可以用表达式的显式类型声明和语句的单独方法代替)
- 新语法学习可能需要一些时间
扩展示例:
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(this@subBranch.arg, code)
}
我正在处理字符串,我遇到了 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 最小定义为:
class RegexWhenArgument (val whenArgument: CharSequence) {
operator fun equals(whenEntry: Regex) = whenEntry.matches(whenArgument)
override operator fun equals(whenEntry: Any?) = (whenArgument == whenEntry)
}
(引用结束)
我认为将 arg 传递给 when
然后引用对 arg 类型进行操作的函数会更具可读性。举个例子:
// 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 开发人员会做什么?
您的问题有很多解决方案。我将从简单的开始,然后再转向更复杂的。
没有参数时
when
can also be used as a replacement for anif
-else if
chain. If no argument is supplied, the branch conditions are simply boolean expressions, and a branch is executed when its condition is true
用例:
when {
startsWithHelp(givenString) -> printHelp()
startsWithFn(givenString) -> println("Hello, ${givenString.substring(3)}!")
}
优点:
不需要任何额外的代码
通俗易懂
缺点:
- 样板
(givenString)
覆盖 equals 的参数包装器
所需的包装器类似于 RegexWhenArgument
,但不是检查 Regex
,而是调用 (String) -> Boolean
。 String
没什么特别的,所以我就用<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
个分支中使用 - 非常容易理解
- 只需要一个简单的包装函数
缺点:
- 很容易忘记包装参数
- 在分支条件中可能会意外使用错误类型的函数
注意:这个解决方案和下一个允许使用函数组合,可以通过这些扩展创建:
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 不仅可以用于构建对象,还可以用于创建自定义程序流结构。
源代码:
@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
(可以用表达式的显式类型声明和语句的单独方法代替) - 新语法学习可能需要一些时间
扩展示例:
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(this@subBranch.arg, code)
}