And now you have to blow it, because if you don’t blow it, it will not work.
—Quotes of the Great
And hello!
Today we will talk about the implicit language Scala. Who has not yet guessed, we will talk about implicit transformations, parameters, classes, and others like them. All newcomers, especially python lovers with Zenovsky
Explicit is better than Implicit , usually fall into a catatonic stupor at the sight of the underhood magic created in Scala. The whole compiler and the principles in general can hardly be covered for one article, but will the road be mastered?
1. Implicit conversions
And we begin with a relatively simple section of implicit conversions. And a life example.
Vasily wants a car from the manufacturer Renault. The whole family saved money for a long time, but they could not accumulate the entire amount. Only enough money for a new VAZ. And then sharply slam! Renault buys AvtoVAZ. It seems that the manufacturer is now necessary, and there is enough money. This is how implicitly Vasya is now the happy owner of a foreign car.
Now let's try to formalize it as a code:
')
Life examplecase class Vasiliy(auto: Renault) { println("Vasiliy owns "+auto) } case class Renault(isRussian: Boolean = false) case class VAZ(isRussian: Boolean = true) object VAZ { implicit def vaz2renault(vaz: VAZ): Renault = Renault(vaz.isRussian)
As a result of
Family.present
we will see the line
Vasiliy owns Renault(true)
. So Scala helps ordinary people in this difficult life!
If you give a more programmatic example (I use something like this in my project):
Lifeless example case class PermissionsList(permissions: Set[String] = Set("UL")); object PermissionsList { implicit def str2permissions(str: String) = PermissionsList(str.split(";").toSet) implicit def permissions2str(p: PermissionsList) = p.permissions.mkString(";") }
The above code allows you to implicitly bring the lines to the object of access rights and back. This can be convenient when working on the same web, when we just need to glue the necessary
"UL;AL"
string on the client and send it to the server, where it will be converted to our object at the right time.
And here we come to an important point . When and under what conditions will our VAZ
pumpkin turn into Renault, and the string into a PermissionsList object?
In most cases, all the magic of Scala occurs in compile-time (the language is strongly typed). The local compiler is an extremely smart and resourceful creature. As soon as we try to call the exat () method in the VAZ class instance, which never existed there, our compiler indulges in all serious and boils the meta looking for the implicit VAZ conversion to something that exat () can do. In other words,
implicit def a2b(a: A): B
It searches for implicit conversions:
- In the current scope (for example, inside the current object)
- In explicit (
import app.VAZ.vaz2renault
) - or group imports (
import app.VAZ._
) - In the companion convertible object
By the way, besides simply calling a nonexistent method, the compiler will try to convert the object if we try to pass it to a method / function that requires a parameter with a different data type. This is just from the example of Vasily and his family.
1.1 Implicit class
Starting with version 2.10, Implicit classes appeared in Scala, which allow you to conveniently group extensions (to weight methods) for any existing classes. Here is a simple example:
object MySimpleHelper { implicit class StringExtended(str: String) { def sayIt = println(str) def sayItLouderBitch = println(str.toUpperCase +"!!!") } }
As can be seen from the above raw, we have a class declared inside the object that takes a single argument - a string. This string is given to us at the mercy of class methods. And this case is tormented elementary:
import MySimpleHelper._ "oh gosh" sayIt > oh gosh "oh gosh" sayItLouderBitch > OH GOSH!!!
But here there are several limitations that must be kept in mind:
- For implicit classes, you can use only one explicit argument of the constructor, which, in fact, "expands" (let's talk about implicit parameters later)
- Similar classes can only be declared inside objects, traits, other classes.
- In the scope of a class declaration, methods, properties, or objects with the same name cannot exist. If you have a
VAZ
property in an object, for example, then implicit class VAZ
cannot coexist next to it.
Well, in fact, our
StringExtended
will be converted by the compiler to:
class StringExtended(str: String) { def sayIt = println(str) def sayItLouderBitch = println(str.toUpperCase +"!!!") } implicit def String2StringExtended(str: String): StringExtended = new StringExtended(str)
Familiar, is not it?
2. Implicit parameters
Somehow all too simple and you already bored? It's time for a little hardcore! Let's move our brains and climb into the source of the rock:
Cheerless code def minBy[B](f: A => B)(implicit cmp: Ordering[B]): A = {
Povtykali, everything seems to be clear.
Stop, second. But we use minBy like this:
val cities = Seq(new City(population = 100000), new City(500000)) val smallCity = cities.minBy( city => city.population )
And no
cmp: Ordering[B] ( B == Int)
not passed. Although it seems like the code works ... Relax, boy. It `s Magic.
In the imported scope, and specifically in
scala.math.Ordering
exists
here is such a piece of code object Ordering extends LowPriorityOrderingImplicits { ... trait IntOrdering extends Ordering[Int] { def compare(x: Int, y: Int) = if (x < y) -1 else if (x == y) 0 else 1 } implicit object Int extends IntOrdering ... }
Pay attention to the last line - there is an implicit object Int, which has in its arsenal a
compare
method, implemented with the inheritance
Ordering[Int]
IntOrdering
IntOrdering
. Actually, this object is used for comparison, implicitly transmitted to the ill-fated
minBy
.
A strongly simplified example looks like this:
Friendly code implicit val myValue: Int = 5 object Jules { def doesHeLookLikeABitch(answer: String)(implicit times: Int) = { for(x <- 1 to times) println(answer ) } } Jules.doesHeLookLikeABitch("WHAT?") >WHAT? >WHAT? >WHAT? >WHAT? >WHAT?
Of course, no one forbids us to pass implicit parameters by the handles. No, well, suddenly need.
And again, again restrictions, where do without them.
- In the scope of the method call, there must be an object / value marked as implicit, and only one parameter can exist for one data type. Otherwise, the compiler will not understand what needs to be passed to the method.
- As an option, the compiler will search the companion object of our
implicit T
, if it exists, and pull implicit val x: T
. But this is really hard drugs, as for me.
3. View / Context Bounds
If your brain is already melted - relax and drink coffee, and maybe something stronger. I'm going to talk about the latest non-obviousness today.
Suppose that our Vasily, who drives a new car (the one that can
exat()
) has become a successful person, a shorter programmer. And Vasily writes on Scala, and he wanted to HAVE MORE MORE SUGAR ARRGH. Martin thought and said - Okay. And introduced the types and restrictions on them. The same
def f[T](a: T)
3.1 View Bounds
This restriction when declaring a type tells the compiler that an implicit conversion is
true somewhere nearby.
def f[A <% B](a: A) = a.bMethod
Those. there is an implicit conversion from
A
to
B
in the accessible scope. In principle, it is possible to present the record as follows:
def f[A](a: A) (implicit a2b: A => B) = a.bMethod:
A close example to a Russian person looks like this:
class Car { def exat() = println("POEXALI") } class VAZ object VAZ { implicit def vaz2car(v: VAZ): Car = new Car() } def go[A <% Car](a: A) = a.exat() go(new VAZ()) > POEXALI
Wonderful! Life has become beautiful, hair has grown back, the wife has returned, and so what's next on the list.
But Vasily asked for it, and Martin was already unstoppable ... So it appeared
3.2 Context Bounds
This restriction was introduced in Scala 2.8, and, in contrast to View Bounds, it is responsible not for implicit
conversions , but for implicit
parameters , that is,
def f[A : B](a: A) = g(a)
The simplest example would be such a couple:
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b vs def f[A](a: A, b: A)(implicit ord: Ordering[A]) = { import ord._ if (a < b) a else b }
In general, this is a distant hi Haskell and his typeclass pattern.
Instead of an afterword
The road does not end there, you can talk a lot about Scala for a long time. But not all at once, because the main thing is understanding and the desire to understand. With desire will come and awareness of what is happening.
Well, if after reading this article you don’t understand the code of a project that uses implicit conversions, I advise you to close notepad and open a normal IDE, everything is beautifully highlighted there. And my head is already not cooking, I'll go. Thank you all for your attention.