I'm trying to use scalaz's ioeffect IO[E,A] monad for a piece of code that is very effectful.
The code I'm trying to rewrite with IO[E,A] at a high level takes some metadata about a file that's store in a cloud. The code attempts to:
The details of the steps aren't that important, but what I was thinking of doing was to do something along the lines of:
def processShareActivity(fileObject: FileObject): IO[ProcessFileFailure, IndexResponse] = {
for {
file <- downloadFile (fileObject)
text <- extractText (file)
searchFileObject <- IO.point(buildSearchFileObject (fileObject, file, text))
indexedResponse <- indexSearchFileObject (searchFileObject)
} yield indexedResponse
}
def indexSearchFileObject(fileObject: SearchFileObject): IO[IndexFailure, IndexResponse] = ???
def buildSearchFileObject(fileObject: FileObject, file: File, str: String): SearchFileObject = ???
def extractText(file: File): IO[ExtractionFailure, String] = ???
def downloadFile(fileObject: FileObject): IO[DownloadFileFailure, File] = ???
The problem is that an instance of IO[E,A]
and IO[F,B]
do not seem to compose. That is, since for example downloadFile
's IO signature returns DownloadFileFailure
for its error scenario and extractText
returns ExtractionFailure
, those monads can't seem to compose in the for
comprehension.
Is there an easy way for my top level for
comprehension to compose such that it results in a IO[ProcessFileFailure, IndexResponse]
where ProcessFileFailure
is some kind of a wrapping failure object around the different kind of failures that can happen in the submethods?
Unfortunately, you do need a way to unify these errors into a common one:
for example:
sealed trait ProcessFileFailure
object ProcessFileFailure {
case class Index(e: IndexFailure) extends ProcessFileFailure
case class Extraction(e: ExtractionFailure) extends ProcessFileFailure
case class Download(e: DownloadFileFailure) extends ProcessFileFailure
}
And your for-comprehension will become:
for {
file <- downloadFile (fileObject).leftMap(ProcessFileFailure.Download)
text <- extractText (file).leftMap(ProcessFileFailure.Extraction)
searchFileObject <- IO.point(buildSearchFileObject (fileObject, file, text))
indexedResponse <- indexSearchFileObject (searchFileObject).leftMap(ProcessFileFailure.Index)
} yield indexedResponse
Its awkward, but it does have the advantage of being able to store everything that went wrong and exactly the context in which it happened.