Search code examples
scalafunctional-programmingscala-cats

Flatten nested Ior's with scala-cats


Working with scala-cat's Ior data type I encountered the following problem:

import cats._
import cats.data._
import cats.implicits._

type Locale = String
type FailureMessage = String
type Failures = NonEmptyList[FailureMessage]

private def listTranslationFiles(): IO[FailureMessage Either Array[File]] = ???

private def analyzeTranslationFiles(fs: Array[File]): Failures Ior Seq[(Locale, File)] = ???

private def readTranslations(): IO[Failures Ior Seq[(Locale, File)]] = for {
    files <- listTranslationFiles()
    fileIor = if(files.isLeft) (NonEmptyList(files.left.get, Nil): Failures).leftIor
              else files.right.get.rightIor
    // fileIor: Ior[Failures, Array[File]]
    analyzed = fileIor.bimap(identity, analyzeTranslationFiles)
    // analyzed: Ior[Failures, Ior[Failures, Seq[(Locale, File)]]]
    result = ??? //  how do I 'flatten' analyzed here?
} yield result

In my last step I need to convert this type:

Ior[Failures, Ior[Failures, Seq[(Locale, File)]]]

into:

Ior[Failures, Seq[(Locale, File)]]

by somehow flattening the Ior (I want to accumulate all left Ior's into the top-level Ior). How can this be achieved?


Solution

  • You can literally just replace the ??? in result = ??? with analyzed.flatten in your code and it will work perfectly, thanks to the flatten syntax method provided by FlatMap and the fact that Ior has a FlatMap instance when the left side is a semigroup (as it is here).

    You can do a little better than that by replacing these two lines:

    analyzed = fileIor.bimap(identity, analyzeTranslationFiles)
    result = analyzed.flatten
    

    With the following:

    result = fileIor.flatMap(analyzeTranslationFiles)
    

    …since any time you're using bimap with identity on the left side, you can replace it with map, and any time you're using map and then flatten, you can replace both with flatMap.