⬆️ ⬇️

Scala. Everyone out of the twilight!

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 example
case 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) //   } object Family { def present = { Vasiliy(VAZ()) //  .   ! } } 


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(";") } // case class User(login: String, permissions: PermissionsList) /* somewhere in a galaxy far far away */ User(login = "Vasiliy", permissions = "UL;AL") //       


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:

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:



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
 /** * TraversableOnce.scala: minBy * ,  ,      ,      ,    ,    B    A . ,   B    ,    A,  B  . - . */ def minBy[B](f: A => B)(implicit cmp: Ordering[B]): A = { //   -   ? if (isEmpty) throw new UnsupportedOperationException("empty.minBy") //     var minF: B = null.asInstanceOf[B] var minElem: A = null.asInstanceOf[A] var first = true //    //   for (elem <- self) { //    A,   B val fx = f(elem) if (first || cmp.lt(fx, minF)) { //     -     . //  cmp.lt  true   ,  f: B <   minF: B minElem = elem minF = fx first = false } } minElem } 


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.


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) //  g    B[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.

Source: https://habr.com/ru/post/209850/



All Articles