I wonder if this might be useful to add to Arrow i...
# arrow
d
I wonder if this might be useful to add to Arrow instead of Kotlin's
single()
on collections...?
Copy code
fun <E, T> Raise<E>.ensureSingle(
    list: List<T>,
    notFound: () -> E,
    multipleFound: List<T>.() -> E
): T = when {
    list.size == 1 -> list.single()
    list.isEmpty() -> raise(notFound())
    else -> raise(list.multipleFound())
}
k
It would be great if there were an extension library that has a whole lot of ensure variations. It’s probably a good idea to keep arrow-core as lean as possible
c
Actually, we already started prototyping it: https://github.com/arrow-kt/arrow-exact (though there's not much activity there at the moment)
There's #kotools-types that is similar too
d
Yeah, I agree that core should stay slim, another library might be an idea. I didn't really mean wrapping the type like in exact, my use case is getting a result from an sql query that I expect only a single unique record, but the field doesn't have a unique index on it, so in the case I get many I need to short circuit and report and error with some info from those multiple records. It seems so much in the spirit of functional programming and arrow to not just throw one error for both no result and many results but rather distinguish between them and give the option to raise for either of them...
I'm sure there are other examples of things like this in the standard library that could be done better in arrow
1
c
Exact uses value classes, so the type isn't really wrapped. But the goal is to communicate that the check has been done in the type system; with raw values, you risk making the same checks multiple times, since you cannot know if previous callers already did them.
d
I think wrapping types should only be taken as far as needed in the code base... otherwise it just complicates things terribly to start wrapping everything coming from 3rd party libs and serialization...
c
Your version of
ensureSingle
is a bit awkward to use though, because of the two lambdas 😕
d
Any better way of handling this? I need to report something different for each case
c
I don't have anything specific in mind, no 😕 If I had to write this, I would probably go
Copy code
ensure(foo.isNotEmpty()) { NotFoundError() }
ensure(foo.size == 1) { TooManyItems(foo) }
val e = foo[0]
directly in my code, without creating a utility function for it.
d
Yeah, but then why should stdlib have single(), you could write it otherwise too... the utility function conveys exactly what I'm doing, more than those three separate lines do, don't you say? Even fold, recover and a bunch of other Arrow functions have multiple lambdas, and could also technically be done with a few lines of code...
c
With context receivers, we could declare:
Copy code
context(Raise<CollectionIsEmpty>, Raise<CollectionHasTooManyItems>)
fun <T> ensureSingle(list: List<T>): T = when {
    list.size == 1 -> list[0]
    list.isEmpty() -> raise(CollectionIsEmpty)
    else -> raise(CollectionHasTooManyItems(list))
}
and let the user convert the errors to their own types with
recover
or
withError
, but since we would be creating base error types, it should be a companion library and not within Arrow Core itself
1
d
Well, like you said, that's the disadvantage, you save the lambdas to provide your own error types, just to have to convert them with other lambdas at the calling site... I'm not sure what's better in general, but in my use case, it was cleaner to handle them directly in those lambdas. Yours is nicer in general and could be chained with the list as it's reciever. Then it wouldn't be ensureSingle, but rather List<T>.singleOrRaise()...
1
Both have their use and advantage...
1