1

I am learning Context bound in Scala.

In the below code, I am invoking multiplication operator on the integer parameter. But it errors out. 'a' is considered as type parameter; but it is actually not as per my understanding. Can someone please help.

scala> class Sample[T]
defined class Sample

scala> def method[Int:Sample](a:Int) = a * a
<console>:12: error: value * is not a member of type parameter Int
       def method[Int:Sample](a:Int) = a * a

Thanks!

user3103957
  • 636
  • 4
  • 16

3 Answers3

5

Context bounds are syntactic sugar for implicit generic parameters that are parameterized by some type you're using. This concept is also known as "type class". You define some generic trait, such as your Sample[T], and then you provide implicit(!) instances of that trait for various concrete values of T. We call them "type class instances".

Why implicit? It's an implementation detail that Scala uses to achieve the type class mechanism; type classes also exist in e.g. Haskell, but the mechanism itself is a bit different. Anyways, you can then define a method such as your def method which requires a type class instance for some type. And you can do this with context bound syntax, or with a more verbose and more explicit standard syntax for implicit parameters.

Your definition is using the context bound. But there is something wrong with your example, as indicated by the compilation error. Let's first see a proper example that uses the type class concept correctly.

// type class definition:

trait Sample[T] {
  def getSample: T
}

// type class instance(s):

object Sample {
  implicit val sampleInt: Sample[Int] = 
    new Sample[Int] { def getSample = 42 }
}

And now the usage:

import Sample._

// using the context bound syntax
def method1[T : Sample](t: T) = t.getSample

// not using the context bound syntax
def method2(t: T)(implicit ev: Sample[T]) = t.getSample

What we're doing is saying - there is some value t of type T, we don't know much about it, but what we do know is that there is a Sample type class instance available for it. This allows us to do t.getSample.

And now, to finally provide the answer to your problem:

In your code, you are mixing things up. Your T is actually called Int. You intended to use the Int type, but what you did instead is that you named your generic parameter Int. I could have answered this with way less text, but I figured perhaps you would find the bigger picture interesting, rather than just pointing out the mistake.

slouc
  • 9,508
  • 3
  • 16
  • 41
  • Thanks slouc! We need to place type parameters when we use context bound. Am I correct ? – user3103957 Jul 09 '21 at 13:40
  • 1
    I'm not exactly sure what you mean, but if you have a type parameter e.g. `def foo[A](a: A) = ???`, then you can add a context bound like so: `def foo[A : SomeTypeClass](a: A) = ???`, and this is syntax sugar for `def foo[A](a: A)(implicit ev: SomeTypeClass[A]) = ???`. It doesn't make sense to even talk about context bounds if the method is not parameterized by any generic types, e.g. `def foo(a: Int)`. Btw you can use multiple type classes to bound the context, e.g. `def foo[A : Foo : Bar]`. – slouc Jul 09 '21 at 13:45
  • Initially my thought was we could use all type (including generic + user defined + system defined) in context bound. But it looks like, it is not possible. That is: def foo[userDefinedType : anotherUserDefinedType](a: userDefinedType) = ??? – user3103957 Jul 09 '21 at 13:56
  • 1
    Yes of course, all types in the examples from my answer were user-defined. You only have to make sure that `AnotherUserDefinedType` is actually parameterized with another type, e.g. `trait AnotherUserDefinedType[A] { ... }`. Otherwise it makes no sense. Because what context bound tells you in the method definition is, "there is an implicit value of type `AnotherUserDefinedType[UserDefinedType]` available in scope", and it cannot work (and doesn't make sense) if `AnotherUserDefinedType` is not parameterized. – slouc Jul 09 '21 at 14:19
  • Thanks! Much appreciate your valuable time. In another related question, it has been mentioned that view bounds will be deprecated. But I think, we can not achieve everything with the help of context bound alone. In view bound, we can easily declare and define implicits in terms of values, methods and classes. Is there any reason why view bound is being deprecated ? – user3103957 Jul 09 '21 at 14:25
  • 1
    View bounds were a way of saying "A can be viewed as B because there exists an implicit conversion from one to another". Implicit conversions are a controversial concept to say the least, and one should avoid them. This makes view bounds unnecessary. Most examples for view bounds can easily be done with context bounds, the difference is "A can be implicitly turned into B[A]" vs "for this A, there exists an implicit value of type B[A]". – slouc Jul 09 '21 at 15:15
  • Thanks again! Sorry for being naive... I am trying to solve a problem using context bound. For a given string, the function (written using context bound) should return the ASCII sum of the string elements. If it was view bound, a functional literal (String => Int) will easily get the job done.. where as using context bound, it seem challenging. – user3103957 Jul 09 '21 at 16:44
  • View bounds and context bounds are mechanisms for working with implicits. Why do you need implicits to solve that problem? Having an implicit conversion from String to Int in scope means that you might accidentally provide a String where Int is expected, and your program will compile and behave unexpectedly. Implicit conversions are best avoided, and that's the main reason why view bounds are deprecated. – slouc Jul 10 '21 at 13:16
4

The type parameter named Int does not represent concrete integer type scala.Int. Instead it is just a confusing coincidence that the type parameter was given the same name Int as the concrete type. If you give it some other name such as T

def method[T: Sample](a: T): T = a * a

the error message should make more sense. Now we see * is not defined for T since Sample type class does not yet provide such capability. Here is an example of how correct syntactic usage might look usage

trait Sample[T] {
  def mult(a: T, b: T): T
}

def method[T: Sample](a: T): T = implicitly[Sample[T]].mult(a, a)
def method[T](a: T)(implicit ev: Sample[T]): T = ev.mult(a, a)

You could also have a look at Numeric type class which provides such functionality out of the box

def method[T](a: T)(implicit num: Numeric[T]): T = num.times(a, a)
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • Thanks Mario! Your explanation is very clear. – user3103957 Jul 09 '21 at 13:41
  • On a related note, it has been mentioned that view bounds will be depreciated. But I think, we can not achieve everything with the help of context bound alone. Is there any reason why view bound is being deprecated ? – user3103957 Jul 09 '21 at 13:44
2

Your method has a type parameter called Int which shadows the actual Int, just like defining a normal variable would shadow something from an outer scope. The same would happen if you remove the context bound.

What you are probably trying to do is something closer to the following:

trait Sample[A] {
  def someOperation(a1: A, a2: A): A
}

implicit object IntSample extends Sample[Int] {
  override def someOperation(a1: Int, a2: Int): Int = a1 * a2
}

def method[T: Sample](t: T) = implicitly[Sample[T]].someOperation(t, t)

method(4) // compiles and returns 16
//method("4") // doesn't compile, no implicit instance of Sample[String] in scope

You can play around with this code here on Scastie.

stefanobaghino
  • 11,253
  • 4
  • 35
  • 63