arjunswaj arjunswaj - 2 months ago 18
Scala Question

Why does this Free Monad Interpreter not resolve String to Id[A]

I've been playing with Free Monads of Cats. I have written a DSL to process CSV Records. The primitive operation is to process CSV Record and I have written a helper

sequence
and
map2
functions for
processCSVRecords
operation myself. I want the return type of the case class to be generic type
R
. Below is the code that I'm using.

import cats.data.Coproduct
import cats.free.Free.inject
import cats.free.{Free, Inject}
import cats.{Id, ~>}
import org.apache.commons.csv.CSVRecord

object ProcessCSVActions {

sealed trait ProcessCSV[A]

case class ProcessCSVRecord[R](csvRecord: CSVRecord) extends ProcessCSV[R]

class ProcessCSVs[F[_]](implicit I: Inject[ProcessCSV, F]) {

private def sequence[S[_], A](fs: Stream[Free[S, A]]): Free[S, Stream[A]] =
fs.reverse.foldLeft[Free[S, Stream[A]]](Free.pure[S, Stream[A]](Stream()))((b, a) => map2(a, b)(_ #:: _))

private def map2[S[_], A, B, C](ra: Free[S, A], rb: Free[S, B])(f: (A, B) => C): Free[S, C] = for {
a <- ra
b <- rb
} yield f(a, b)

def processCSVRecord[R](csvRecord: CSVRecord): Free[F, R] =
inject[ProcessCSV, F](ProcessCSVRecord[R](csvRecord))

def processCSVRecords[R](csvRecords: Stream[CSVRecord]): Free[F, Stream[R]] = {
val res: Stream[Free[F, R]] = for {
csvRecord <- csvRecords
} yield processCSVRecord[R](csvRecord)
sequence[F, R](res)
}


}

object ProcessCSVs {
def apply[F[_]](implicit I: Inject[ProcessCSV, F]): ProcessCSVs[F] = new ProcessCSVs[F]
}

object StringInterpreterOfCSV extends (ProcessCSV ~> Id) {
override def apply[A](fa: ProcessCSV[A]): Id[A] = fa match {
case ProcessCSVRecord(csvRecord) => csvRecord.get(2)
}
}
}


Now when I try to compile the above code, I get the below error for my interpreter:

[scalac-2.11] found : String
[scalac-2.11] required: cats.Id[A]
[scalac-2.11] (which expands to) A
[scalac-2.11] case ProcessCSVRecord(csvRecord) => csvRecord.get(2)
[scalac-2.11] ^
[scalac-2.11] one error found


What is the proper way to handle
Stream
with
Free
?

Edit:

I've found a hack. I'm taking a param of type
R
in the case class.

case class ProcessCSVRecord[R](csvRecord: CSVRecord, a:Option[R]) extends ProcessCSV[R]
def processCSVRecord[R](csvRecord: CSVRecord): Free[F, R] =
inject[ProcessCSV, F](ProcessCSVRecord[R](csvRecord, None))


In the interpreter, I'm explicitly giving the type that matches the result.

object StringInterpreterOfCSV extends (ProcessCSV ~> Id) {
override def apply[A](fa: ProcessCSV[A]): Id[A] = fa match {
case ProcessCSVRecord(csvRecord, _: Option[String]) => csvRecord.get(2)
}
}


The above works, but I wished there was a better solution instead of this hack.

Answer Source

Since CSVRecord has nothing to do with A, the compiler has no evidence that String <:< A. If this compiles, you'll be able to create a ProcessCSV[Int] and pass it to StringInterpreterOfCSV.apply, which doesn't make sense.

If CSVRecord had a type parameter R, and get(2) returned R, then it would work:

case class ProcessCSVRecord[R](csvRecord: CSVRecord[R]) extends ProcessCSV[R]

Otherwise you can csvRecord.get(2).asInstanceOf[A].