Статьи

Неявные преобразования в Scala

В продолжение предыдущего поста о перегрузке операторов я собираюсь рассмотреть неявные преобразования и то, как мы можем объединить их с перегрузкой операторов, чтобы сделать действительно полезные вещи, в том числе один способ создания многопараметрического преобразования.

Так что же такое «неявное преобразование», когда оно дома?

Итак, давайте начнем с некоторого основного синтаксиса Scala. Если вы провели какое-то время со Scala, вы, вероятно, заметили, что он позволяет вам делать такие вещи, как:

1
(1 to 4).foreach(println) // print out 1 2 3 4

Вы когда-нибудь задумывались, как это происходит? Давайте сделаем вещи более явными, вы можете переписать приведенный выше код как:

1
2
3
4
val a : Int = 1
val b : Int = 4
val myRange : Range = a to b
myRange.foreach(println)

Scala создает объект Range непосредственно из двух объектов Ints и вызывает метод to.

Так что здесь происходит? Это просто капелька синтаксического сахара для облегчения написания циклов? Это просто ключевое слово как def или val?

Ответы на все это нет, здесь ничего особенного не происходит. to это просто метод, определенный в классе RichInt , который принимает параметр и возвращает объект Range (в частности, подкласс Range, называемый Inclusive ). Вы можете переписать его следующим образом, если действительно хотите:

1
val myRange : Range = a.to(b)

Держитесь, хотя, RichInt может иметь метод «to», но Int, конечно, нет, в вашем примере вы даже явно приводите свои числа к Ints

Что подводит меня к теме этого поста, неявные преобразования. Вот как это делает Scala. Неявные преобразования — это набор методов, которые Scala пытается применить, когда сталкивается с объектом неправильного типа. В примере to есть метод, определенный и включенный по умолчанию, который преобразует Ints в RichInts .

Поэтому, когда Scala видит от 1 до 4, он сначала запускает неявное преобразование 1, преобразуя его из примитива Int в RichInt. Затем он может вызвать метод to для нового объекта RichInt, передав второй параметр Int (4) в качестве параметра.

Хм, думаю, я понимаю, как насчет другого примера?

Конечно. Давайте попробуем улучшить наш класс комплексных чисел, который мы создали в предыдущем посте .

Используя перегрузку операторов, мы смогли поддержать сложение двух комплексных чисел с помощью оператора +. например.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
class Complex(val real : Double, val imag : Double) {
    
  def +(that: Complex) =
            new Complex(this.real + that.real, this.imag + that.imag)
    
  def -(that: Complex) =
            new Complex(this.real - that.real, this.imag - that.imag)
  
  override def toString = real + " + " + imag + "i"
    
}
  
object Complex {
  def main(args : Array[String]) : Unit = {
       var a = new Complex(4.0,5.0)
       var b = new Complex(2.0,3.0)
       println(a)  // 4.0 + 5.0i
       println(a + b)  // 6.0 + 8.0i
       println(a - b)  // 2.0 + 2.0i
  }
}

Но что, если мы хотим поддержать добавление нормального числа к комплексному числу, как бы мы это сделали? Конечно, мы можем перегрузить наш метод «+», чтобы получить аргумент типа Double, то есть что-то вроде…

1
def +(n: Double) = new Complex(this.real + n, this.imag)

Что позволило бы нам сделать …

1
val sum = myComplexNumber + 8.5

… но это сломается, если мы попробуем …

1
val sum = 8.5 + myComplexNumber

Чтобы обойти это, мы могли бы использовать неявное преобразование. Вот как мы его создаем.

1
2
3
4
object ComplexImplicits {
   implicit def Double2Complex(value : Double) =
                                    new Complex(value,0.0)
}

Просто! Хотя вам нужно быть осторожным, чтобы импортировать методы ComplexImplicits, прежде чем их можно будет использовать. Вам необходимо убедиться, что вы добавили следующее в начало вашего файла (даже если ваш объект Implicits находится в том же файле)…

1
import ComplexImplicits._

И эта проблема решена, теперь вы можете написать val sum = 8.5 + myComplexNumber, и он будет делать то, что вы ожидаете!

Ницца. Могу ли я что-нибудь еще с ними сделать?

Еще одна вещь, для которой я нашел их полезными, это создание простых способов создания объектов. Было бы неплохо, если бы существовал более простой способ создания одного из наших комплексных чисел, чем с помощью нового Complex (3.0,5.0) . Конечно, вы можете избавиться от нового, сделав его классом-кейсом или реализовав метод apply. Но мы можем сделать лучше, как насчет просто (3.0,5.0)

Круто, но мне нужно какое-то многопараметрическое неявное преобразование, и я не понимаю, как это возможно !?

Дело в том, что обычно (3.0,5.0) создает кортеж. Таким образом, мы можем просто использовать этот кортеж в качестве параметра для нашего неявного преобразования и преобразовать его в комплекс. как мы могли бы сделать это …

1
2
implicit def Tuple2Complex(value : Tuple2[Double,Double]) =
                             new Complex(value._1,value._2);

И вот у нас это есть, простой способ создания экземпляров наших сложных объектов, для справки вот как теперь выглядит весь комплексный код.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import ComplexImplicits._
  
object ComplexImplicits {
  implicit def Double2Complex(value : Double) = new Complex(value,0.0)
  
  implicit def Tuple2Complex(value : Tuple2[Double,Double]) = new Complex(value._1,value._2);
  
}
  
class Complex(val real : Double, val imag : Double) {
    
  def +(that: Complex) : Complex = (this.real + that.real, this.imag + that.imag)
    
  def -(that: Complex) : Complex = (this.real - that.real, this.imag + that.imag)
        
  def unary_~ = Math.sqrt(real * real + imag * imag)
           
  override def toString = real + " + " + imag + "i"
    
}
  
object Complex {
    
  val i = new Complex(0,1);
    
  def main(args : Array[String]) : Unit = {
       var a : Complex = (4.0,5.0)
       var b : Complex = (2.0,3.0)
       println(a)  // 4.0 + 5.0i
       println(a + b)  // 6.0 + 8.0i
       println(a - b)  // 2.0 + 8.0i
       println(~b)  // 3.60555
        
       var c = 4 + b
       println(c)  // 6.0 + 3.0i
       var d = (1.0,1.0) + c 
       println(d)  // 7.0 + 4.0i
         
  }
  
}

Ссылка: неявные преобразования в Scala от нашего партнера JCG