Issue
So I'm making regexes for collections (all quantifiers are possessive). It looks like this (keep in mind the example is overly simplified for readability's sake):
val mayBeAPerson: Boolean = "totally not a person"
.toList()
.matches { // this: PatternScope
one { it.isUpperCase() } // execution of lambda could end after this method
moreThan(0) { it.isLetter() }
one { it == ' ' }
lessThan(2) { // this: PatternScope
one { it.isUpperCase() }
one { it == '.' }
one { it == ' ' }
}
one { it.isUpperCase() }
moreThan(0) { it.isLetter() }
}
As you can see, execution of lambda passed to matches
could end after first one
, as the predicate passed to it doesn't match first character in List
. And it indeed does end. However, my solution is the opposite of elegant, as it uses throwing an exception in one
and catching it in matches
.
fun List<Char>.matches(build: PatternScope.() -> Unit) = try {
val scope = PatternScope(iterator())
scope.build() // may throw MatchFailed
!scope.iterator.hasNext()
} catch (_: MatchFailed) {
false
}
class PatternScope(private val iterator: Iterator<Char>) {
inline fun one(predicate: (element: Char) -> Boolean) {
if (!iterator.hasNext() || !predicate(iterator.next())) {
throw MatchFailed("match failed")
}
}
.
. etc
.
}
It totally works, but I can't help but wonder: is there a better way? I do know throwing exceptions like this is just a fancy GOTO
, and I could wrap all the methods of PatternScope
in if
s, like this:
class PatternScope(private val iterator: Iterator<Char>) {
private var matchFailed = false
inline fun one(predicate: (element: Char) -> Boolean) {
if (!matchFailed) {
if (!iterator.hasNext() || !predicate(iterator.next())) {
matchFailed = true
}
}
}
inline fun moreThan(n: Int, predicate: (element: Char) -> Boolean) {
if (!matchFailed) {
// logic
}
}
.
. etc
.
}
Is it more elegant though? Now I'm invoking all the functions in lambda passed to matches
, and I like it even less to be honest.
My real question is: is there even better way to do it? Some magic solution to return from lambda I don't even have real access to? Some non-local returns, but from functions lambda hasn't even see yet?
Can I return from lambda by invoking some function inside its body?
Edit
Just to clarify, let's say we have a lambda:
val lambda: () -> Unit = {
someMethod() // this should return from lambda (in some cases)
someOtherMethod() // this shouldn't be invoked
}
How should the body of someMethod
look like, so that someOtherMethod
does not even execute when the lambda is invoked? Is there any other way but making someMethod
throw an exception and wrapping lambda
in try-catch
block like this:
try {
lambda() // throws
} catch (_: SomeThrowableIdk) { }
I don't see a better way, but please prove me wrong.
Solution
I assume you're actually using @PublishedApi
since you have a private iterator and public inline functions that access it.
Since Kotlin doesn't have checked exceptions, it is against Kotlin convention to throw Exceptions for things that are not actually errors in the program (bugs). Your first approach feels a little hacky for this reason. Since your API has public inline functions, there's no way to totally encapsulate the exceptions. You could switch to non-inline functions and storing the steps in a collection to be run internally, but this is surely more runtime overhead than the inline functions or your second approach with if statements.
Your second approach is more like a typical builder, so I don't see the problem with it. Since your functions are inline, it's not like your compiled code has a bunch of unnecessary function calls. Just if statements. You could however add a helper function to clean up your code at all the sub-functions, though I'm not sure if this can extend to the complexity of your actual class:
class PatternScope(@PublishedApi internal val iterator: Iterator<Char>) {
@PublishedApi internal var matchFailed = false
@PublishedApi internal inline fun nextRequire(require: () -> Boolean) {
matchFailed = matchFailed || !require()
}
inline fun one(predicate: (element: Char) -> Boolean) = nextRequire {
iterator.hasNext() && predicate(iterator.next())
}
}
There's no way to do what you described in your edit. Non-local returns work only with lambdas. To support something like what you describe, Kotlin would need syntax for a special kind of function that has the ability to return from the function that calls it. A function like this would have to have a new kind of signature that also declares the return type of the type of function that is permitted to call it. There simply is no such syntax or function type like that in Kotlin.
Answered By - Tenfour04
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.