kotlin-result
kotlin-result copied to clipboard
Implement traverse Inspired by Haskell for Safe Transformations
This pull request introduces a new extension function, traverse, inspired by the traverse function in Haskell's Data.Traversable module. Link : https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Traversable.html#t:Traversable
The goal is to provide a safe and declarative way to transform collections while handling potential errors gracefully.
functional programming, traverse allows us to apply a function that returns a Result (or any monadic type) to each element of a collection and collect the results into a single Result containing the transformed collection. If any transformation fails, the entire operation short-circuits, returning the first error encountered. This is particularly useful for scenarios where we need to ensure all transformations succeed or fail early on the first error.
Thanks for this. I like this idea a lot!
I think we could simplify its implementation, as it shares a lot of structure with fold. It looks to me like traverse could call fold with list::add as the second argument.
@michaelbull "Yes, I know that fold can be used to do the same thing, but I want to write an operator that is more expressive in other functional languages."
I'm not suggesting that fold does the same thing, just that its implementation is largely shared between fold and traverse, so the new traverse function could call fold in its implementation to avoid duplication.
@michaelbull That's a good idea. I've just edited it, please check it again.
Hi @hoangchungk53qx1, apologies for taking so long to get back to you on this.
The more I've studied this implementation, the more I am convinced that the mapResult function on Iterable already solves this problem.
Please can you let me know there's something I am missing with regards to this function in comparison to mapResult?
Hi @hoangchungk53qx1, apologies for taking so long to get back to you on this.
The more I've studied this implementation, the more I am convinced that the
mapResultfunction onIterablealready solves this problem.Please can you let me know there's something I am missing with regards to this function in comparison to
mapResult?
In functional programming, while operators are reusable, their meanings can vary. Additionally, the concept of Traversable should be clearly understood and distinguished from other ideas.
The implementation of mapResult seems identical to traverse as you first proposed it:
mapResult:
public inline fun <V, E, U> Iterable<V>.mapResult(
transform: (V) -> Result<U, E>,
): Result<List<U>, E> {
val values = map { element ->
val transformed = transform(element)
when {
transformed.isOk -> transformed.value
else -> return transformed.asErr()
}
}
return Ok(values)
}
Your traverse:
public fun <V, E, U> Iterable<V>.traverse(
transform: (V) -> Result<U, E>
): Result<List<U>, E> {
val results = mutableListOf<U>()
for (item in this) {
val result = transform(item)
val element = when {
result.isOk -> result.value
else -> return Err(result.error)
}
results.add(element)
}
return Ok(results)
}
The implementation of
mapResultseems identical totraverseas you first proposed it:
mapResult:public inline fun <V, E, U> Iterable<V>.mapResult( transform: (V) -> Result<U, E>, ): Result<List<U>, E> { val values = map { element -> val transformed = transform(element) when { transformed.isOk -> transformed.value else -> return transformed.asErr() } } return Ok(values) }Your
traverse:public fun <V, E, U> Iterable<V>.traverse( transform: (V) -> Result<U, E> ): Result<List<U>, E> { val results = mutableListOf<U>() for (item in this) { val result = transform(item) val element = when { result.isOk -> result.value else -> return Err(result.error) } results.add(element) } return Ok(results) }
Ohh, It seems that I haven't fully read mapResult, because I only gradually converted from Haskell.
mapResult can do this, i will close this MR
👍