Need some help with `MapK.traverse(..)` method. An...
# arrow
r
Need some help with
MapK.traverse(..)
method. Any code of how to use it?
i
What exactly are you trying to do?
r
I'm trying to write some docs about
MapK
, figuring out what each method is used for. And I just can't hello-world this `traverse`:
Copy code
val map1: MapK<String, Int> = mapOf("one" to 1, "two" to 2).k()

    map1.traverse<String, Int>(
        object : Applicative<String> {
            override fun <A> just(a: A): Kind<String, A> {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }

            override fun <A, B> Kind<String, A>.ap(ff: Kind<String, (A) -> B>): Kind<String, B> {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }
        },
        { f ->

        })
i
Currently, I am writing docs on Traverse and how to use it. Let me help you out here.
r
@Imran/Malic thanks! Keep me in touch.
i
So the most important thing to understand with Traverse is that you want to use a function
(A) -> Kind<G, B>
. Well what does that mean, whenever you want to introduce a new Shape let’s say you want to go from
List<String>
to
Validated<Nil<NumberFormatException, List<Int>>
This would facilitate to safely parse Strings to Int, while accumulating Errors on the way.
You would write something like this:
Copy code
import arrow.core.Either
import arrow.core.Nel
import arrow.core.ValidatedNel
import arrow.core.extensions.nonemptylist.semigroup.semigroup
import arrow.core.extensions.validated.applicative.applicative
import arrow.core.fix
import arrow.core.invalidNel
import arrow.core.k
import arrow.core.validNel

fun parseIntEither(s: String): Either<NumberFormatException, Int> =
  if (s.matches(Regex("-?[0-9]+"))) Either.Right(s.toInt())
  else Either.Left(NumberFormatException("$s is not a valid integer."))

fun parseIntValidated(s: String): ValidatedNel<NumberFormatException, Int> =
  if (s.matches(Regex("-?[0-9]+"))) s.toInt().validNel()
  else NumberFormatException("$s is not a valid integer.").invalidNel()

fun main() {
  //sampleStart
  val validatedList = listOf("1", "22w", "3", "33s").k()
    .traverse(ValidatedNel.applicative(Nel.semigroup<NumberFormatException>()), ::parseIntValidated).fix()
  //sampleEnd
  println(validatedList)
}
Now the key take away is this, whenever you use traverse you need an Applicative<G> thus it needs to have somekind of shape for instance a container wether it is a network call with
IO
So here your
G
has to something else than String, because String is not a container.
B
on the other hand is the output value from
A
which in your case is
Int
. In the case of MapK you would traverse over the Values
A
r
@Imran/Malic thank you for such deep explanation, it became a bit clear for me. I'll try to implement this to MapK.
i
In the case of
::identity
it would collapse what ever is in that
F
and turns an
F<G<A>>
to a `G<F<A>>`: Here is an example with a
List<Option<Int>>
, which yields a `Option<List<Int>>`:
Copy code
import arrow.core.extensions.option.applicative.applicative
import arrow.core.extensions.option.applicative.map
import arrow.core.fix
import arrow.core.identity
import arrow.core.none
import arrow.core.some
import arrow.core.Option

fun main() {
  //sampleStart
  val optionList: Option<List<Int>> =
    listOf(1.some(), 2.some(), 3.some())
      .traverse(Option.applicative(), ::identity).fix().map { it.fix() }

  val emptyList: Option<List<Int>> =
    listOf(1.some(), none(), 3.some())
      .traverse(Option.applicative(), ::identity).map { it.fix() }
  //sampleEnd
  println("optionList = $optionList")
  println("emptyList = $emptyList")
}
And this is what sequence do out of the box.
Copy code
import arrow.core.Option
import arrow.core.extensions.list.traverse.sequence
import arrow.core.extensions.option.applicative.applicative
import arrow.core.none
import arrow.core.some

fun main() {
  //sampleStart
  val optionList =
    listOf(1.some(), 2.some(), 3.some())
      .sequence(Option.applicative())

  val emptyList =
    listOf(1.some(), none(), 3.some())
      .sequence(Option.applicative())
  //sampleEnd
  println("optionList = $optionList")
  println("emptyList = $emptyList")
}
r
can't call
Option.applicative()
for the list example...
i
I am still working on the docs, but if you want to know more you can head on to this PR- still WIP: https://github.com/arrow-kt/arrow/pull/1534
👍 1
show me your snippet?
r
facepalm what's the package of Option.applicative()? extensions?
i
arrow.core.extensions
I also provided the imports in the snippet’s so you can look it up
as long as you are on
0.10.0-Snapshot
r
Yeah, that was pretty newbie mistake 🙂
However your explanation is still valuable, thank you.
So the main idea of traverse is to swap types? Like A<B<C>> -> B<A<C>>?
i
No
r
Ok, let me dig in it then, not trivial for me.
i
In fact, what Traverse provides is to sequence through your container
F
and wrap with in a new container
G
think of it as a less boilerplate version to construct your existing
MapK
within the boundaries of another container
G
. Well obviously you could use fold, but fold has another purpose. It aims to give you that
B
and doesn’t care from which existing container e.g.:
MapK
it is called.
This is just a special case, which many people use
(A<B<C>>) -> B<A<C>>
and here you would use
sequence
feel free to ask me anything 🙂
r
@Imran/Malic thanks!
i
With boilerplate I mean somthing like this:
Copy code
val d = map1.traverse(Option.applicative()) { Some("$it") }
val dBoiler = map1.foldLeft(Some(emptyMap())) { acc: Option<MapK<String, String>>, i: Int -> acc.fold({emptyMap()}, { /*Some logic to retrieve the key of a value and transform it*/ } )}
Generally both try to do the same thing, but I let you judge what makes more sense