1

The last statement line below produces an error: "type mismatch; found: TestExpandableWithLibrary.this.library.type (with underlying type org.typeclass.Library) required: V"

This is where I am trying to do an implicit conversion. The preceding line where I simply use the typeclass function does work fine.

Any ideas on how to fix it?

package org.typeclass

///////////////////////////////////////////////////////////////////////////////
// the domain objects

case class Book(bookName: String)

case class Library(libraryName: String, books: Set[Book])

object Library {
  def apply(libraryName: String, bookNames: String*): Library =
    Library(libraryName, bookNames.map(Book(_)).toSet)
}

case class TreeClass(nodeName: String, children: Seq[TreeClass])

///////////////////////////////////////////////////////////////////////////////
// the typeclass definition

trait Expandable[T, V, R] {
  def expandWith(template: T, values: V): R
}

object Expandable {

  def expandWithF[T, V, R](template: T, values: V)(implicit ev: Expandable[T, V, R]): R =
    ev.expandWith(template, values)

  implicit class ExpandableItem[T, V, R](val template: T) extends AnyVal {
    def expandWithM(values: V)(implicit ev: Expandable[T, V, R]): R =
      ev.expandWith(template, values)
  }
}

///////////////////////////////////////////////////////////////////////////////
// a typeclass implementation

object ExpandableImpls {

  implicit object ExpandableTreeClass extends Expandable[TreeClass, Library, TreeClass] {
    def expandWith(template: TreeClass, library: Library): TreeClass = {
      val parentName = s"${template.nodeName}.${library.libraryName}"
      val children = library.books.map(book => TreeClass(s"${parentName}.${book.bookName}", Seq.empty)).toSeq
      TreeClass(parentName, children)
    }
  }

}

//@RunWith(classOf[JUnitRunner])
class TestExpandableWithLibrary /*extends FlatSpec*/ {

  import Expandable._
  import ExpandableImpls._

  val library = Library("test", "black", "white")
  val root = TreeClass("parent", Seq.empty)

  val useF = expandWithF(root, library) // this works

  val useM = root.expandWithM(library) // this doesn't work!

}

1 Answer 1

4

The problem is just that you need to put the second two type parameters on the implicit class on the extension method—as it is they'll be inferred to be Nothing, since they're not referred to in the constructor arguments.

implicit class ExpandableItem[T](val template: T) extends AnyVal {
  def expandWithM[V, R](values: V)(implicit ev: Expandable[T, V, R]): R =
    ev.expandWith(template, values)
}

This should work just fine.

Sign up to request clarification or add additional context in comments.

3 Comments

Not to be churlish, but perhaps somewhere along the line ideally the compiler could have given me some clues as to where I was going adrift. Type inference = Dragons Be Here!
@satyagraha: I agree that the error could be better. It's a good idea to internalize the rule that if you want a type parameter inferred, it should appear in a non-generic position in the arguments, though.
Spotted this tool which might have helped: blog.cppcabrera.com/posts/scala-wart-remover.html (the offending statement would have needed to be commented out to allow compilation to complete of course)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.