implicit conversions that add properties to a type, rather than to an instance of a type
Asked Answered
R

2

2

I was reading through some older Scala posts to better understand type classes, and I ran across this one that seemed quite useful, but the example seems to have gotten stale.

Can someone help me figure out the correct way to do what Phillipe intended ? Here is the code

trait Default[T] { def value : T }

implicit object DefaultInt extends Default[Int] {
  def value = 42
}

implicit def listsHaveDefault[T : Default] = new Default[List[T]] {
  def value = implicitly[Default[T]].value :: Nil
}

default[List[List[Int]]]

When copy/pasted and run in REPL, i get this>

    scala> default[List[List[Int]]]
    <console>:18: error: not found: value default
                  default[List[List[Int]]]
                  ^
Ridden answered 10/8, 2015 at 2:39 Comment(0)
L
5

This doesn't have anything to do with the Scala version. If you read @Philippe's answer, you will notice that the default method simply isn't defined anywhere. That will not work in any Scala version.

It should look something like this:

def default[T: Default] = implicitly[Default[T]].value
Lulalulea answered 10/8, 2015 at 3:25 Comment(0)
R
2

Thanks to Jorg for his answer, which, in conjunction with this blog post helped me figure out what is going on here. Hopefully my additional answer will help others who have been confused by this.

My mental picture of type classes is that they are a means by which an author of a library would imbue his/her library with the desireable trait of retroactive extensibility.

On the other hand, there is yet another technique for ad hoc polymorphism: implicits with wrapper classes. You would use this latter technique when you are the consumer of a library which has some type which is missing methods that you find useful.

I am going to try to extend Phillipe's example a bit to illustrate my understanding of how type classes could be used by a library designer. I am not that experienced with Scala... so if anyone notices something that is not correct in my understanding please do correct me ! ;^)

// 'Default' defines a trait that represents the ability to  manufacture
// defaults for a particular type 'T'.
//
trait Default[T] { def value : T }


// Define a default for int.. this is an object default
//
implicit object DefaultInt extends Default[Int] {
      def value = 42
}


// Define a default for Lists that contain instances of a 'T'.
// Interesting that this is method, not an object implicit definition. I
// guess that is to enable the 'on the fly' creation of default values for
// lists of any old type....  So I guess that's why we have 
// a 'method implicit' rather than an 'object implicit' definition.

implicit def listsHaveDefault[T : Default] = new Default[List[T]] {
  def value = implicitly[Default[T]].value :: Nil
}



// Here is the meat of the library... a method to make a message based of
// some arbitrary seed String, where that method is parameterized by 'T',
// a type chosen by the library user.   That 'T' can be 
// types for which implicits are already defined by the base library 
// (T = Int, and List[T]), or an implicit defined by the library user.   
//
// So the library is open for extension.
//
def makeMsg[T](msg: String)(implicit something: Default[T]) : String = {
    msg + something.value
} 

Given the above code, if I can create a message for the type List[List[Int]], or for Int's

makeMsg[List[List[Int]]]("moocow-")
makeMsg[Int]("dogbox")

And I get this result:

res0: String = moocow-List(List(42))
res1: String = dogbox42

If I want to override the default implicit value for a given type, I can do so like this:

makeMsg[Int]("moocow-")(something=new Object with Default[Int] { def value = 33344 }  )

And I get this result:

res3: String = moocow-33344
Ridden answered 10/8, 2015 at 6:48 Comment(2)
This seems like a good explanation to me. Two things you could add: 1) you don't necessarily need an object for DefaulInt, you could also have a value and an anonymous class, e.g. implicit val defInt = new Default[Int] { ... }. What matters is that there's something of type Default[Int] in the environment. 2) the signature of makeMsg could be makeMsg[T : Default], which is syntactic sugar for the implicit arg. The only difference is that now, since you don't have a name for the arg, you need to use implicitly[Default[T]] to retrieve it.Prelate
Thanks, Philippe, for your comment and your original post which inspired my question. re: your last point, I understand how that works now. But I was initially confused by context bounds (the syntactic sugar you mentioned). If anyone else reading this thread is confused about context bounds you might find this post useful: #32284227Ridden

© 2022 - 2024 — McMap. All rights reserved.