本文介绍了在Kotlin`when`语句(或其他分支构造)中将函数或lambda作为条件包括在内的最简洁方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理字符串,并且遇到 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)}!")
}

专家:

  1. 不需要任何其他代码

  1. Doesn't require any additional code

易于理解

缺点:

  1. 样板(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)}!")
}

优点:

  1. 可以在when分支中使用功能
  2. 非常容易理解
  3. 只需要一个简单的包装函数
  1. Functions can be used in when branches
  2. Very easy to understand
  3. Requires only one simple wrapper function

缺点:

  1. 容易忘记包装参数
  2. 错误类型的功能可能会在分支条件下意外使用

注意:此解决方案和下一个解决方案允许使用功能组合,可以通过以下扩展名来创建这些功能:

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") }
}

优点:

  1. 可自定义的语法
  2. 可以添加新功能
  3. 可以添加仅适用于某些参数类型的功能
  4. 可以启用嵌套分支
  5. 没有样板
  6. 在编译时检查函数类型
  7. 易于阅读

缺点:

  1. 需要一些帮助程序类和功能
  2. 实验性@BuilderInference的用法(可以用用于表达式的显式类型声明和用于语句的单独方法代替)
  3. 学习新的语法可能需要一些时间
  1. Requires a few helper classes and functions
  2. Usage of experimental @BuilderInference (it can be replaced with explicit type declaration for expressions and separate method for statements)
  3. 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作为条件包括在内的最简洁方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-27 16:20