Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

["Request"] Keep the traverse function #3108

Closed
victorhearnyeates opened this issue Jul 28, 2023 · 6 comments
Closed

["Request"] Keep the traverse function #3108

victorhearnyeates opened this issue Jul 28, 2023 · 6 comments

Comments

@victorhearnyeates
Copy link

What version are you currently using?
1.1.5, just upgraded to 1.2.0
What would you like to see?
I use traverse extensively, and find it much cleaner when manipulating nested monads. I'm not sure why it's being considered a niche API, as it's incredibly powerful as

listOfSomething.traverse { somethingThatReturnsAnOption.toEither { NotFoundError("Couldn't find thing $it") } }

is so much cleaner than

listOfSomething.let<Iterable<String>, Either<NotFoundError, List<Something>>> { l ->
            either {
                l.map {
                    someThatReturnsAnOption.toEither { NotFoundError("Couldn't find something $it") }.bind()
                }
            }
        }

It's a massive draw for arrow, and I'm not sure why it's be removed

@nomisRev
Copy link
Member

Hey @victorhearnyeates,

Thank you for your feedback. The replacement code in ReplaceWith is sadly not as good as we've hoped, and is in my opinion not really a good comparison. More on that here, https://arrow-kt.io/learn/quickstart/migration/#traverse.

Additionally, it's not really being removed but it's been moved to a different library. https://github.com/cashapp/quiver.
We're working together with the Quiver team to make sure all the traverse, and some other APIs are ported there and they support the same multiplatform targets as Arrow does by 2.0.0 is released.

I'm not sure there is a way to configure Gradle to ignore certain deprecations, but if you want to continue using traverse you can be using Quiver when Arrow 2.0.0 is released.

Does that seem like an acceptable solution to you? Thank you for the feedback, it's great appreciated 🙏 Looking forward to your thoughts.

@Zordid
Copy link
Contributor

Zordid commented Aug 2, 2023

I can feel you, @victorhearnyeates, as my initial thoughts were similar. But having spent more time, my code looks so much cleaner now - you cannot rely on the simple "replace with" automatisms. In my code, I am now using the Raise DSL extensively and believe me, it looks better than before without dealing with traverse. Just don't migrate using replace with alone! Have fun!

@NickBurkard
Copy link

I agree with @victorhearnyeates. I don't consider traverse to be a niche API. It's an incredibly powerful, fundamental and commonly used behavior that I'd expect from an FP library. traverse is often one of the first things I teach others to show how useful FP can be. 😄

Given this is my first time participating in discussions about Arrow, I'm missing some context about why these deprecations are occurring. What's the concern with leaving traverse as part of Arrow's core offerings? t.traverse(f) an idiomatic shortcut over t.map(f).sequence, which Arrow seems to provide via either { t.map(f).bind() }.

In a wider discussion for deprecations, it's an extra burden on developers who use Arrow to import an entirely separate ecosystem of dependencies (app.cash.quiver instead of arrow.core) to get fundamental behaviors for arrow.core data types. I can also imagine developers will likely roll their own traverse (and others like zip) if documentation isn't strong enough for pointing to Quiver.

@serras
Copy link
Member

serras commented Aug 23, 2023

Let me give my two cents. As @Zordid mentions, the direct replacements give you really terrible code (for example, you would never use let in such a code, but that's the only replacement that works for every case). In your case, the replacement with the DSL would look something like:

either {
  listOfSomething.map {
    withError({ NotFoundError("Couldn't find thing $it") }) { 
      somethingThatReturnsAnOption.bind() 
    } 
  }
}

or use the provided bindAll with your previous toEither,

either {
  listOfSomething.map {
    somethingThatReturnsAnOption.toEither { NotFoundError("Couldn't find thing $it") }
  }.bindAll()
}

Apart from a few order changes, the main change here is that you need to write either at the top.

There are two reasons why I think map { ...bind() } is desirable over traverse:

  • traverse was provided for a particular set of combinations of types, whereas the "iterate over elements calling bind" pattern is much more general.
    • we've seen request of having traverseIndexed and similar things, we we do not have to provide now,
    • if you define your own error type, you don't need to write N versions of traverse;
  • although traverse is useful, I've always found it a bit "magical" when teaching it. For example: why does traverse on Either return only the first error? We prefer the explicitness of using map or mapOrAccumulate to clarify what's going on.

In addition to those, you may not need the either if you design your code using the Raise DSL from the ground up (which is a heavy investment for already-written code, I'm aware).

@serras
Copy link
Member

serras commented Aug 23, 2023

On the note above, remember that in languages like Haskell or Scala you have a single declaration of traverse: F[G[A]] => G[F[A]] , whereas in Kotlin we just have N^2 overloads of traverse for N given "monadic types".

@serras
Copy link
Member

serras commented May 14, 2024

The final decision was to keep the deprecation for 2.0, Quiver is the way to go if you need those functions.

@serras serras closed this as completed May 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants