2
votes

Just when I thought I understood the basics of Scala's type system... :/

I'm trying to implement a class that reads the contents of a file and outputs a set of records. A record might be a single line, but it could also be a block of bytes, or anything. So what I'm after is a structure that allows the type of Reader to imply the type of the Record, which in turn will imply the correct Parser to use.

This structure works as long as MainApp.records(f) only returns one type of Reader. As soon as it can return more, I get this error:

could not find implicit value for parameter parser

I think the problem lies with the typed trait definitions at the top, but I cannot figure out how to fix the issue...

// Core traits
trait Record[T]
trait Reader[T] extends Iterable[Record[T]]
trait Parser[T] {
  def parse(r: Record[T]): Option[Int]
}

// Concrete implementations
class LineRecord[T] extends Record[T]
class FileReader[T](f:File) extends Reader[T] { 
  val lines = Source.fromFile(f).getLines()
  def iterator: Iterator[LineRecord[T]] =
    new Iterator[LineRecord[T]] {
      def next() = new LineRecord[T]
      def hasNext = lines.hasNext
    }
}
trait TypeA
object TypeA {
    implicit object TypeAParser extends Parser[TypeA] {
        def parse(r: Record[TypeA]): Option[Int] = ???
    }
}
trait TypeB
object TypeB {
    implicit object TypeBParser extends Parser[TypeB] {
        def parse(r: Record[TypeB]): Option[Int] = ???
    }
}

// The "app"
object MainApp {
  def process(f: File) =
    records(f) foreach { r => parse(r) }

  def records(f: File) = {
    if(true)
      new FileReader[TypeA](f)
    else
      new FileReader[TypeB](f)   
  }

  def parse[T](r: Record[T])(implicit parser: Parser[T]): Option[Int] =
      parser.parse(r)
}
4

4 Answers

2
votes

First off you must import the implicit object in order to use them:

import TypeA._
import TypeB._

That's not enough though. It seems like you're trying to apply implicits dynamically. That's not possible; they have to be found compile time.

If you import the objects as above and change the records so that the compiler finds the correct generic it will run fine:

def records(f: File) = new FileReader[TypeA](f)

But then it may not be what you were looking for ;)

1
votes

The problem is that the return type of your records method is basically FileReader[_] (since it can return either FileReader[TypeA] or FileReader[TypeB]), and you don't provide an implicit argument of type Parser[Any]. If you remove the if-expression the return type is inferred to FileReader[TypeA], which works fine. I'm not sure what you're trying to do, but obviously the compiler can't select implicit argument based upon a type that is only known at runtime.

1
votes

1) Using type with implicit inside as type parameter - doesn't bind this implicit to the host type, to do this change objects to the traits and mix them instead of generalizing (type-parametrizing):

  def records(f: File) = {
    if(true)
      new FileReader(f) with TypeA
    else
      new FileReader(f) with TypeB   
  }

2) The parser should be in scope of function that calls parse. So you may try smthg like that:

 def process(f: File) = {
     val reader = records(f);
     import reader._
     reader foreach { r => parse(r) }
  }

PlanB) Simpler alternative is to define type-parameter specific implicit methods inside the AppMain (or some trait mixed in), but it will work only if TypeA/TypeB is known on compile time, so records method can return concrete type:

implicit class TypeAParser(r: Record[TypeA]) {
    def parse: Option[Int] = ???
}

implicit class TypeBParser(r: Record[TypeB]) {
    def parse: Option[Int] = ???
}

def process[T <: TypeAorB](f: File) =
    records[T](f).foreach(_.parse)

def recordsA[T <: TypeAorB](f: File) =  new FileReader[T](f) 
0
votes

Here is, I think, the full set of modifications you need to do to get where I think you want to go.

import scala.io.Source
import java.io.File
import reflect.runtime.universe._

// Core traits
trait Record[+T]
trait Reader[+T] extends Iterable[Record[T]]
trait Parser[-T] {
  def parse(r: Record[T]): Option[Int]
}

// Concrete implementations [unmodified]
class LineRecord[T] extends Record[T]
class FileReader[T](f:File) extends Reader[T] {
  val lines = Source.fromFile(f).getLines()
  def iterator: Iterator[LineRecord[T]] =
    new Iterator[LineRecord[T]] {
      def next() = new LineRecord[T]
      def hasNext = lines.hasNext
    }
}

sealed trait Alternatives
case class TypeA() extends Alternatives
object TypeA {
    implicit object TypeAParser extends Parser[TypeA] {
        def parse(r: Record[TypeA]): Option[Int] = ???
    }
}
case class TypeB() extends Alternatives
object TypeB {
    implicit object TypeBParser extends Parser[TypeB] {
        def parse(r: Record[TypeB]): Option[Int] = ???
    }
}

class ParseAlternator(parserA: Parser[TypeA], parserB: Parser[TypeB]) extends Parser[Alternatives] {
  def parse(r: Record[Alternatives]): Option[Int] = r match {
    case x: Record[TypeA @unchecked] if typeOf[Alternatives] =:= typeOf[TypeA] => parserA.parse(x)
    case x: Record[TypeB @unchecked] if typeOf[Alternatives] =:= typeOf[TypeB] => parserB.parse(x)
  }
}
object ParseAlternator {
  implicit def parseAlternator(implicit parserA: Parser[TypeA], parserB: Parser[TypeB]): Parser[Alternatives] = new ParseAlternator(parserA, parserB)
}

// The "app"
object MainApp {
  import ParseAlternator._
  def process(f: File) =
    records(f) foreach { r => parse(r) }

  def records(f: File): Reader[Alternatives] = {
    if(true)
      new FileReader[TypeA](f)
    else
      new FileReader[TypeB](f)
  }

  def parse[T](r: Record[T])(implicit parser: Parser[T]): Option[Int] =
      parser.parse(r)
}

The gist of it is: all of this would be completely classsical if only your parse instance did not have to pattern-match on a generic type but dealt directly with an Alternative instead.

It's this limitation (inherited from the JVM) that scala can't properly pattern-match on an object of a parametric type that requires the reflection & typeOf usage. Without it, you would just have type alternatives for your content (TypeA, TypeB), which you would add to a sealed trait, and which you would dispatch on, in an implicit that produces a Parser for their supertype.

Of course this isn't the only solution, it's just what I think is the meeting point of what's closest to what you're trying to do, with what's most idiomatic.