Could not find implicit value for parameter


#1

I suppose I don’t understand type system well.

I can create simple type class like Show tutorial similar the one here - https://engineering.rallyhealth.com/scala/implicits/functional/programming/2018/02/06/implicit-implications-parameters-part-1.html

But I can’t get compiled with following code because I do not know the implementation of plus function in PlusableOps class. Pass Plusable[L].plus(a, b) doesn’t work. How should I fix this?

Thanks

trait Plusable[T] {
  def plus(a: T, b: T): T 
}

object Plusable {

  def apply[T](implicit p: Plusable[T]) = p 

  implicit class PlusableOps[A, L <: List[A]](a: L) {
    def plus(b: L): L = Plusable[L].plus(a, b)
  }

  implicit val listInt: Plusable[List[Int]] = new Plusable[List[Int]] {
    def plus(a: List[Int], b: List[Int]): List[Int] = a ++ b
  }

}

#2

The code sample is saying that you can only plus together lists of Ints. So if you try Plusable[List[Int]].plus(List(1), List(2)), you should get List(1, 2). Or equivalently, try List(1).plus(List(2)).


#3

Thank you for your reply. I understand that I can use

implicit class PlusableOps[Int](a: List[Int]) {
  def plus(a: List[Int]): List[Int] = Plusable[List[Int]].plus(a, b)
}

to plus two list

  import Plusable._
  println(List(1, 2, 3) plus List(4, 5, 6))

How if I want PlusableOps to be more abstract? For instance, at this moment I only have a few type such as List[Int], and List[String] available. But I want PlusableOps abstract enough that it could cover future usage such as List[SomeOtherType]. That’s the reason why the code employs type parameter PlusableOps[A, L <: List[A]](which of course is not correct).

Thank you again for your help.


#4

I just realized that this definition from your code sample:

implicit class PlusableOps[A, L <: List[A]](a: L) {
  def plus(b: L): L = Plusable[L].plus(a, b)
}

Doesn’t look completely right. It should be something more like:

implicit class PlusableOps[A](a1: A)(implicit Plusable: Plusable[A]) {
  def plus(a2: A): A = Plusable.plus(a1, a2)
}

This gives the meaning that you can add the plus extension method to any type as long as that type has a Plusable instance defined.

Anyway, to your question about abstraction, my above definition of PlusableOps is the correct level of abstractness, but the ability to actually plus any two lists comes from defining a Plusable implicit for List[A]. Currently you have an implicit for Plusable[List[Int]], you need to make an instance of Plusable[List[A]]. This is typically done by making the implicit a class or a method so that it can be parameterized by the generic type A.


#5

Changing implicit val listInt: Plusable[List[Int]] ... to

implicit def mkPlusable[A]: Plusable[List[A]] = new Plusable[List[A]] {

    override def plus(a: List[A], b: List[A]) = a ++ b

}

now it can deal with different types of List

    import Plusable._
    println(List(1, 2, 3) plus List(4, 5, 6)) // List(1, 2, 3, 4, 5, 6)
    println(List("j", "o") plus List("h", "n")) // List(j, o, h, n)
    println(List(0.1, 0.2) plus List(0.3, 0.4)) // List(0.1, 0.2, 0.3, 0.4)

Thank you very much for your advice. Really appreciate it!


#6

Good work. I just realized that, because of Scala’s support for Single Abstract Method syntax, the implicit can just be:

object Plusable {
  implicit def list[A]: Plusable[List[A]] = _ ++ _
}

This works because the Plusable trait has only a single method to override, and Scala allows us to do that with lambda syntax when this is the case. And lambda syntax _ ++ _ is just a syntax sugar for (list1, list2) => list1 ++ list2.


#7

I’d like to take this one step further for a small memory optimization. Because the implementation of Plusable[List[A]] doesn’t depend on A, you can re-use the same instance for every List[_].

object Plusable {
  private val listNothing: Plusable[List[Nothing]] = _ ++ _
  implicit def list[A]: Plusable[List[A]] = listNothing.asInstanceOf[Plusable[List[A]]]
}

Plusable[List[_]] doesn’t work, but I don’t know why not.


#8

Ha, that’s nice. Looks like it should be usable in a lot of cases!


#9

So not an experienced scala FP guy, but i try to use isInstanceOf next to never. Is there a reason you could not solve with a typeclass + implicits or the same implicit def with an exhaustive match. Then you don’t have as much runtime care. I realize that the List monad is a simple example, but what happens in other types where the operator may not be the same each time. If you were going to move only to List[_] i think the compiler could help you much more with type classes and implicits or exhaustive matches. Honestly you could handle Seq, Set, or any Iterable that can be consumed.

Just an opinion, as i learn more about this, i force myself to be more and more strict about what i’m allowing through functions. Ultimately with a you could implement a Plusable[F[_]]

I appreciate your question as it makes me rethink these things and then put myself out to the wolves to be proven wrong.

Best,

Jeff


#10

As shawjef said, it’s just a memory optimization :slight_smile: when you have polymorphic implicits in Scala that are generated by methods or classes, a new instance is allocated (at least in theory) each time the typeclass is used. This optimization trades off a little bit of type safety in exchange for saving on some allocation cost. As you can imagine, for heavily-used instances, there can be a lot of allocation savings.


#11

Just a question, how much memory allocation does an implicit def on a typeclass create. I’m talking about a Plusable for a type in which i define all the plusable def’s that are compiler aware and will fail if they can’t produce that type. This is obviously more for people that understand what occurs when you have a typeclass with implicit defs, in this case only one def per type, how much overhead does this create. I’m not defending my side, but i’d like to understand more what i’m trusting the compiler to tell me that i’m doing wrong.


#12

It strictly depends on usage i.e. how much are you using it, and how many different types are you using it with? One thing to be aware of is that the type-casting suggestion is just a nice trick for when you’re looking to actually cut down memory usage. It’s not one of those ‘zero-cost’ techniques like e.g. AnyVal is in some use cases. You would not do this on a regular basis for your instances.