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

Implicit value for evidence parameter errors #41

Closed
revprez opened this issue Jun 25, 2016 · 6 comments
Closed

Implicit value for evidence parameter errors #41

revprez opened this issue Jun 25, 2016 · 6 comments

Comments

@revprez
Copy link

revprez commented Jun 25, 2016

Probably a way to handle this, but getting implicit errors when attempting to use kantan asCsvReader and asCsvWriter against generics. I suspect this is either a limitation or there is some niceness I can try with implicits to move forward, but right now my solution is to simply leave the trait methods unimplemented and do so in the implementing case classes.

If anyone has a better way of doing this, I'm all ears.

Code follows errors.

Errors

Information:6/25/16, 5:24 PM - Compilation completed with 6 errors and 0 warnings in 1s 749ms

...

Error:(24, 40) could not find implicit value for evidence parameter of type kantan.csv.RowEncoder[T]
        val writer = out.asCsvWriter[T](',', line.headers)
                                       ^
Error:(24, 40) not enough arguments for method asCsvWriter: (implicit evidence$1: kantan.csv.RowEncoder[T], implicit oa: kantan.csv.CsvOutput[java.io.ByteArrayOutputStream], implicit e: kantan.csv.engine.WriterEngine)kantan.csv.CsvWriter[T].
Unspecified value parameters evidence$1, oa, e.
        val writer = out.asCsvWriter[T](',', line.headers)
                                       ^
Error:(45, 66) could not find implicit value for evidence parameter of type kantan.csv.RowDecoder[T]
        val results = getClass.getResource(source).asCsvReader[T](',', false)
                                                                 ^
Error:(45, 66) not enough arguments for method asCsvReader: (implicit evidence$1: kantan.csv.RowDecoder[T], implicit ia: kantan.csv.CsvInput[java.net.URL], implicit e: kantan.csv.engine.ReaderEngine)kantan.csv.CsvReader[kantan.csv.ReadResult[T]].
Unspecified value parameters evidence$1, ia, e.
        val results = getClass.getResource(source).asCsvReader[T](',', false)
                                                                 ^
Error:(54, 65) could not find implicit value for evidence parameter of type kantan.csv.RowDecoder[T]
        val results : List[ReadResult[T]] = line.readCsv[List,T](sep, header)
                                                                ^
Error:(54, 65) not enough arguments for method readCsv: (implicit evidence$3: kantan.csv.RowDecoder[T], implicit ia: kantan.csv.CsvInput[String], implicit e: kantan.csv.engine.ReaderEngine, implicit cbf: scala.collection.generic.CanBuildFrom[Nothing,kantan.csv.ReadResult[T],List[kantan.csv.ReadResult[T]]])List[kantan.csv.ReadResult[T]].
Unspecified value parameters evidence$3, ia, e...
        val results : List[ReadResult[T]] = line.readCsv[List,T](sep, header)
                                                                ^                                       ^

Code

import java.io.ByteArrayOutputStream

import kantan.csv._
import kantan.csv.ops._
import kantan.csv.generic._
/**
  * Created by revprez on 6/25/16.
  */

trait CsvLine {

    def headers : List[String]
}

trait CsvParsable[T <: CsvLine] {

    def line : T

    def toCsvString(header: Boolean = true) : String = {
        val out : ByteArrayOutputStream = new ByteArrayOutputStream()

        val writer = out.asCsvWriter[T](',', line.headers)
        writer.write(line).close

        val string = new String(out.toByteArray)
        out.close

        header match {
            case true => return string.stripLineEnd
            case false => {
                return string.split("\\r?\\n")(1).stripLineEnd
            }
        }

    }
}

trait CsvParser[T] {

    implicit val codec = scala.io.Codec.ISO8859

    def parseFile(source: String, sep : Char = ',', header : Boolean = true) : Stream[T] = {
        val results = getClass.getResource(source).asCsvReader[T](',', false)
            .toStream
            .filter( _.isSuccess )
            .map(_.get)

        return results
    }

    def parse(line : String, sep : Char = ',', header : Boolean = false) : Option[T] = {
        val results : List[ReadResult[T]] = line.readCsv[List,T](sep, header)

        results match {
            case Nil => None
            case or::ors if (or.isFailure) => None
            case or::ors if (or.isSuccess) => Some(or.get)

        }
    }
}
@nrinaudo
Copy link
Owner

There are a few issues with this code.

The first one, the one that causes compilation to fail, is that readCsv and asCsvReader expect an implicit RowDecoder[T] in scope (and asCsvWriter expects a RowEncoder[T]). You need to provide one.

You have various ways of doing that - have it as an implicit parameters of the methods that call asCsvReader, readCsv and asCsvWriter, for example. It's probably better to have the instances as part of the trait's abstract fields, though.

Sort that out, and you code compiles.

You could also replace filter(_.isSuccess).map(_.get) with collect { Success(a) => a }, it's a little more idiomatic (and calling get, however safe it may seem, is a code smell).

I think you could also replace most of toCsvString with a call to asCsv, which enriches all TraversableOnce[T] instances such that there is an implicit RowEncoder[T] in scope.

Unless I'm mistaken, there is nothing that needs to be fixed in kantan.csv, what you complain about is expected and normal. I'll close this issue - issues are meant for bugs and improvements, and your problem is neither, it's a request for help. If you feel I didn't sufficiently address your problem, you're quite welcome on the gitter channel where we can go through this a bit interactively.

@revprez
Copy link
Author

revprez commented Jun 26, 2016

I'm sure you're right. I need to get up to speed on implicits before I can digest this.

@nrinaudo
Copy link
Owner

It's really not as complicated as you might fear, and it's certainly worth learning (obviously not just for kantan.csv). If you're struggling, ping me on gitter and I'll try to make the time for a useful introduction on the matter.

@revprez
Copy link
Author

revprez commented Jun 26, 2016

Appreciate it. I'm plowing through some stuff right now, avoiding the temptation to try and link it directly to my immediate need. Besides, my work around (simply implement within the case classes I need to [de]serialize) deals with all the scoping issues--I suspect they're scoping issues--I was having, so I can learn at a somewhat leisurely pace.

If I run into trouble, I'll take you up on your kind offer.

@nrinaudo
Copy link
Owner

Fair enough.

I just get a bit bothered by the fact that you're using kantan.csv in a way that forces you to write encoding / decoding code - the whole purpose of it is that you mostly should not, I hate writing such code.

So, quickly then. Kantan.csv relies heavily on a pattern known as type classes. It boils down to implicits and knowing how they're resolved: if you have a type T, and an instance of RowDecoder[T], then given a CSV row (that is, a Seq[String]) you know how to turn it into a T.

This is how, say, asCsvReader works: it expects an implicit RowDecoder[T] in scope, parses the CSV stream and delegates decoding to T to the decoder.

There are various ways to provide an implicit RowDecoder[T]. If T is a concrete type (say, List[Int]), you can implement one and mark is as implicit. In your case though, T is a variable and we know nothing about it - there is no way the compiler will be able to find a RowDecoder[T] for all possible Ts, that is, for all possible types ever. So you need to help it out by providing this instance yourself. Most often, it's done through context bounds, but in your case everything is in a trait (we might want to talk about that to, I think you might be making this much more complex than it needs to be), so that's not a possibility. The simplest way to get things to work would be to rewrite parse's signature as follows:

def parse(line : String, sep : Char = ',', header : Boolean = false)(implicit dt: RowDecoder[T]): Option[T]

T will be know by the time parse is called, so the compiler can work out whether it has a RowDecoder[T] instance in scope (most likely, since you're using the generic module).

@revprez
Copy link
Author

revprez commented Jun 26, 2016

That works, though don't know enough about context to understand why yet. I'll keep studying.

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

2 participants