What are the uses of implicits and How to use them

2019-04-18 04:27发布

The implicit keyword is a very obscure thing to programmers who come from Java and other languages like C and C++, so knowing about the implicit keyword in Scala is very important. How is implicit used in Scala?

Many may find it a duplicate of other post but it is not that. It is different.

Edit ::

Most of the times the question "What is the usage of implicits in Scala?" is answered in the sense of things like "how to write/use implicit conversions?", "how to use implicit type classes?" and so on.

For new Scala programmers (at least those I know), such answers most of the time put forward the impression that implicits are actually just a "beautifying" tool to cut down things like,

val s = (new RichInt(5)).toString + ":: Well"

to just

val s = 5 + ":: Well"

And most of just treat implicit as nothing more than this shortening tool which they can always avoid if desired.

Some of the programmers with more "bare-metal" philosophy tend to even dislike the existence of implicit which hides such details.

Somehow the important question which is "What is the "use and importance" of implicits in Scala?" has somehow gone ignored and unanswered.

Specially programmers who know Scala enough to understand "how to use implicits" to some extent and are sometimes trouble by the "hidden magic" wonder about the question - "What is so special about implicits that the Scala creators even choose to have implicits?"

I am not very sure whether the OP also thought about these questions while exploring the implicits as a Scala programmer.

And surprisingly, I have not found answers to these questions anywhere easily accessible. One reason is that answering even one of the use cases for the actual "need/benefits" of implicits will require a lot of explanation.

I think these questions are worth the focus of community for helping the new Scala programmers. I have tried to simply explain one of the use cases for the implicits. I hope to see more people (who are more knowledgeable) interested in answering this.

1条回答
【Aperson】
2楼-- · 2019-04-18 05:28

I am answering this question more from the perspective of "what are the uses of implicits (why are implicit required in some situations)" instead of "how to use implicit in some of these situations" (as answer to this can be found by searching on google).

While explaining, I somehow ended up using type and class in an interchangeable way at some places. It should still convey the intended information in a simple way but it may indicate to the mis-conception that type and class are similar. I don't see a way to fix that without doing heavy changes in the answer. Just keep in mind that type and class are not same things.

What implicit actually does.

It is actually very simple, implicit does exactly what the name implies. It marks things as a "go to" instance/value in the corresponding scope, if there is ever a need to look for the "go to" instance of the said type.

So we can say that an implicit instance/value of type A is the "go to" instance of type A whenever there is a need for a "go to" instance of type A.

To mark any instance/value as implicitly ("go to") available in corresponding scope, we need to use implicit keyword.

scala> implicit val i: Int = 5
// i: Int = 5

How do we summon implicit instances/values when needed?

Most direct way to summon an implicit is to use implictly method.

val gotoInt = implicitly[Int]
// gotoInt: Int = 5

Or we can define our methods with implicit parameters to expect the availability of an implicit instance in the scope they are being used in,

def addWithImplictInt(i: Int)(implicit j: Int): Int = i + j

Keep in mind that we could have also defined the same method without specifying implicit parameter,

def addWithImplicitInt(i: Int): Int = {
  val implictInt = implicitly[Int]
  i + implictInt
}

Notice that the first choice with implicit parameter makes it clear to the user that the method expects implicit parameter. Because of this reason, the implicit parameter should be the choice in most cases (exceptions are always there).

Why do we actually use implicit?

This is a bit different from possible ways in which we can use implicit values. We are talking about why do we "actually" need to use them.

The answer is to help the compiler with ascertaining type and help us in writing type-safe code for problems which will otherwise result in run-time type comparisons and we end up loosing all the help which the compiler can provide us with.

Consider the following example,

Lets say we are using a library which has following types defined,

trait LibTrait

case class LibClass1(s: String) extends LibTrait
case class LibClass2(s: String) extends LibTrait
case class LibClass3(s: String) extends LibTrait
case class LibClass4(s: String) extends LibTrait

Considering that this is an open trait, you and anyone else can define their own classes to extend this LibTrait.

case class YourClass1(s: String) extends LibTrait
case class YourClass2(s: String) extends LibTrait

case class OthersClass1(s: String) extends LibTrait
case class OthersClass2(s: String) extends LibTrait

Now, we want to define a method which works with only some of the implementations of LibTrait (only those having some particular properties and thus can perform that special behaviour which you need).

// impl_1
def performMySpecialBehaviour[A <: LibTrait](a): Unit

But, the above will allow everything which extends LibTrait.

One choice is to define methods for all of the "supported" classes. But since you don't control the extension of LibTrait, you can not even do that (it is also not a very elegant choice).

Another choice is to model these "Restrictions" for your method,

trait MyRestriction[A <: LibTrait] {
  def myRestrictedBehaviour(a: A): Unit
}

Now, only subtypes of LibTrait supporting this particular behaviour will be able to come up with an implementation of MyRestriction.

Now, in most simple way, you define your method using this,

// impl_2 
def performMySpecialBehaviour(mr: MyRestriction): Unit

So, now users first have to convert their instances to some implementation of MyRestriction (which will ensure that your restrictions are met).

But looking at the signature of performMySpecialBehaviour you won't see any resemblance to what you actually wanted.

Also, it seems that your restriction are bound to class and not the instances themselves, so we can just progress with type class usage.

// impl_3
def performMySpecialBehaviour[A <: LibTrait](a: A, mr: MyRestriction[A]): Unit

Users can define type-class instance for their class and use your it with your method

object myRestrictionForYourClass1 extends MyRestriction[YourClass1] {
  def myRestrictedBehaviour(a: A): Unit = ???
}

But looking at the signature of performMySpecialBehaviour you won't see any resemblance to what you actually wanted.

But, if you were to use consider the use of implicits, we can bering more clarity to the usage

// impl_4
def performMySpecialBehaviour[A :< LibTrait](a: A)(implicit ev: MyRestriction[A]): Unit

But I can still pass the type class instance as in impl_3. So why implicit?

Yes, that is because the example problem is too simple. Lets add more to it.

Remember that the LibTrait is still open for extension. Lets consider you or someone in your team ended up having with following,

trait YoutTrait extends LibTrait

case class YourTraitClass1(s: String) extends YoutTrait
case class YourTraitClass2(s: String) extends YoutTrait
case class YourTraitClass3(s: String) extends YoutTrait
case class YourTraitClass4(s: String) extends YoutTrait

Notice that YoutTrait is also an open trait.

So, each of these will have their own corresponding instances for MyRestriction,

object myRestrictionForYourTraitClass1 extends MyRestriction[YourTraitClass1] {...}
object myRestrictionForYourTraitClass2 extends MyRestriction[YourTraitClass1] {...}
...
...

And you have this other method, which calls performMySpecialBehaviour

def otherMethod[A <: YoutTrait](a: A): Unit = {
  // do something before
  val mr: MyRestriction[A] = ??????????
  performMySpecialBehaviour(a, mr)
  // do something after
}

Now, how do you pick the MyRestriction instance to provide. The thing is, you can still do it in a round-about way by also providing a Map with Class as key and MyRestriction instance as value for all your types. But it is an ugly hack it won't be effective at compile time.

But if you use the implict based impl_4, your the same method will look like this,

def otherMethod[A <: YoutTrait](a: A)(implicit ev: MyRestriction[A]): Unit = {
  // do something before
  performMySpecialBehaviour(a)
  // do something after
}

And will work as long as the MyRestriction instances for all the subtypes for YoutTrait are in scope. Otherwise the code will fail to compile.

So, if someone adds a new subtype YourTraitClassXX of YoutTrait and forgets to ensure that the MyRestriction[YourTraitClassXX] instance is defined and is made available in the scope where any otherMethod calls are being made, the code will fail to compile.

This is just one example but it should suffice to show "why" and "what" are the actual uses of implicits in Scala

There is so much more to say about implicits but that will make the answer too long; which it already is.

The use case in this example, puts a bound on the parameter type A to have an instance of type class TypeClass[A] in scope. It is called Context Bound and such methods are in general written as,

def restricted[A, B](a: A)(implicit ev: TypeClass[A]): B 

Or,

def restricted[A: TypeClass, B](a: A): B

Note:: If you notice any problem with the answer, please comment. Any feedback is welcome.

查看更多
登录 后发表回答