简体   繁体   中英

Making Typeclass Instance with `implicit def`?

Given:

scala> trait Resource[A] { def f: String }
defined trait Resource

scala> case class Foo(x: String)
defined class Foo

And then an implicit:

scala> implicit def fooToResource(foo: Foo): Resource[Foo] = 
        new Resource[Foo] { def f = foo.x }

The following works:

scala> implicitly[Resource[Foo]](Foo("foo")).f
res2: String = foo

I defined a function:

scala> def f[A](x: A)(implicit ev: Resource[A]): String = ev.f
f: [A](x: A)(implicit ev: Resource[A])String

However, the following code fails to compile:

scala> f(Foo("foo"))
<console>:17: error: could not find implicit value for parameter ev: Resource[Foo]
       f(Foo("foo"))

Secondly, then I tried:

scala> f2(Foo("bippy"))
<console>:17: error: could not find implicit value for parameter ev: Resource[Foo]
       f2(Foo("bippy"))
        ^

Lastly, I attempted:

scala> def g(foo: Foo)(implicit ev: Resource[Foo]): String = ev.f
g: (foo: Foo)(implicit ev: Resource[Foo])String

scala> g(Foo("5"))
<console>:17: error: could not find implicit value for parameter ev: Resource[Foo]
       g(Foo("5"))
        ^

However, it failed too. How can I fix f ?

Ok with Peter Neyens' answer, this is not a typeclass, this is an implicit conversion, which you should avoid - there should have been some warning, asking that you import scala.language.implicitConversions.

As a complement, here is why the first implicitly works:

Implicitly is just:

def implicitly[T](implicit ev: T): T = e

When you write implicitly[T] without supplying a parameter , it will look for an implicit of type T in scope and return it. However, you call implicitly with a parameter (I believe there is no legitimate reason to do that, ever), so it would just return your parameter, Foo("foo") , an instance of Foo . Except that you explicitly stated that T should be Resource[Foo]. If you had written a type ascription, such as (Foo("foo"): Resource[Foo]) , it would have worked the same way. implicitly is not relevant here.

The point is that Foo("foo") is not of the expected type Resource[Foo] , but just a Foo . The compiler would reject that, except that at this point, the implicit conversion you defined above kicks in, and your Foo instance is transformed into a Resource[Foo] . Then, you can call f .

Next, you call your f(Foo("foo")) . There is an implicit parameter, however this time, you don't supply it. So the compiler looks for one (while it did no such thing the first time), and as there is no such instance, fails.

The implicit def fooToResource is not a type class instance, but does return one if you supply a Foo , that's the reason the following line works :

implicitly[Resource[Foo]](Foo("foo")).f

A solution would be to change the Resource.f function to take a parameter of type A :

trait Resource[A] { 
  def f(a: A): String 
}

You then could define a Resource type class instance for Foo as follows:

case class Foo(x: String)

implicit val fooResource = new Resource[Foo] {
  def f(foo: Foo) = foo.x
}

We can rewrite f to use the changed Resource :

def f[A](a: A)(implicit resA: Resource[A]): String = resA.f(a)

Which does what (I think) you need :

f(Foo("hello world")) // String = hello world

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM