Skip to content

Conversation

hoangchungk53qx1
Copy link
Contributor

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.

@michaelbull
Copy link
Owner

michaelbull commented Apr 6, 2025

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.

@hoangchungk53qx1
Copy link
Contributor Author

@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."

@michaelbull
Copy link
Owner

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.

@hoangchungk53qx1
Copy link
Contributor Author

@michaelbull That's a good idea. I've just edited it, please check it again.

@michaelbull michaelbull force-pushed the master branch 2 times, most recently from 9133ba7 to 260d2c2 Compare August 2, 2025 23:25
@michaelbull
Copy link
Owner

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?

@hoangchungk53qx1
Copy link
Contributor Author

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?

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.

@michaelbull
Copy link
Owner

michaelbull commented Aug 4, 2025

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

@hoangchungk53qx1
Copy link
Contributor Author

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

Ohh, It seems that I haven't fully read mapResult, because I only gradually converted from Haskell.

@hoangchungk53qx1
Copy link
Contributor Author

mapResult can do this, i will close this MR

@michaelbull
Copy link
Owner

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants