Scala в примерах: различия между версиями
Содержимое удалено Содержимое добавлено
Annayudi (обсуждение | вклад) Добавлен перевод параграфа 8.2 |
Annayudi (обсуждение | вклад) Правка перевода и форматирования Главы 15. |
||
Строка 948:
Неявные(имплицитные) параметры и преобразования – это мощные инструменты для настройки существующих библиотек и для создания высокоуровневых абстракций. Например, давайте начнем с абстрактного класса полугрупп, который поддерживает операцию сложения.
<font size=3><syntaxhighlight lang=Scala>
<code>
}
</syntaxhighlight></font>
А вот подкласс <tt>SemiGroup</tt> — класс <tt>Monoid</tt>, который добавляет поле <tt>unit</tt> (единичный элемент).
<font size=3><syntaxhighlight lang=Scala>
abstract class Monoid[A] extends SemiGroup[A] {
def unit: A
}
</syntaxhighlight></font>
Вот две реализации моноидов:
<font size=3><syntaxhighlight lang=Scala>
object intMonoid extends Monoid[Int] {
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
</syntaxhighlight></font>
Метод суммирования, который работает с произвольными моноидами, может быть написан на чистом Scala следующим образом:
<font size=3><syntaxhighlight lang=Scala>
def sum[A](xs: List[A])(m: Monoid[A]): A =
if (xs.isEmpty) m.unit
else m.add(xs.head, sum(m)(xs.tail)
</syntaxhighlight></font>
Этот метод может быть вызван, например, так:
<font size=3><syntaxhighlight lang=Scala>
sum(List("a", "bc", "def"))(stringMonoid)
sum(List(1, 2, 3))(intMonoid)
</syntaxhighlight></font>
Все это работает, но выглядит не очень здорово. Проблема в том, что реализации моноида должны быть переданы в код, который их использует. Порой мы можем захотеть, чтобы система могла вычислить нужные аргументы автоматически, аналогично тому, как это делается при выводе типов аргументов. Это то, чего помогают добиться неявные параметры.
=== Неявные параметры: основы ===
В Scala 2 появилось новое ключевое слово '''<tt>implicit
<font size=3><syntaxhighlight lang=Scala>
[‘(’ implicit Param {‘,’ Param} ‘)’]</code>
</syntaxhighlight></font>
Если ключевое слово присутствует, то оно делает все параметры в списке неявными. Например, в следующей версии метода sum параметр m является неявным.
<font size=3><syntaxhighlight lang=Scala>
</syntaxhighlight></font>
Как можно заметить из примера, возможно комбинировать обычные и неявные параметры. Однако, для метода или конструктора может быть только один неявный параметр, и он должен быть последним.
Ключевое слово '''<tt>implicit</tt>''' может также быть использовано как модификатор в определениях и объявлениях. Примеры:
<font size=3><syntaxhighlight lang=Scala>
implicit object intMonoid extends Monoid[Int] {
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
</syntaxhighlight></font>
Основная идея неявных параметров — это то, что аргументы для них могут быть выведены из вызова метода. Если аргументы, отвечающие за секцию неявного параметра отсутствуют, тогда они выводятся компилятором Scala.
Действительные аргументы, которые могут быть переданы в качестве неявного параметра, — это все идентификаторы <tt>X</tt>, которые доступны в точке вызова метода без префикса и которые помечены как неявные.
Если существует несколько аргументов, подходящих по типу к неявному параметру, компилятор Scala выберет наиболее точный (специфичный), используя стандартные правила разрешения статической перегрузки. Допустим, вызывается
<font size=3><syntaxhighlight lang=Scala>
sum(List(1, 2, 3))</code>
</syntaxhighlight></font>
в контексте, где доступны <tt>stringMonoid</tt> и <tt>intMonoid</tt>. Мы знаем, что формальный параметр метода <tt>sum</tt> должен быть типа <tt>Int</tt>. Единственное значение, подходящее к типу формального параметра <tt>Monoid[Int]</tt>, это <tt>intMonoid</tt>, поэтому этот объект и будет передан как неявный параметр.
Это обсуждение также показывает, что неявный параметр выводится после того, как выведен какой-нибудь типовой аргумент.
=== Неявные преобразования ===
Пусть у вас есть выражение ''E'' типа ''T'', хотя ожидается тип ''S''. ''T'' не приводится к ''S'' ни одним из предопределенных преобразований. Тогда компилятор Scala попытается применить последнее средство: неявное преобразование ''I(E)''. Здесь, ''I''
Неявные преобразования также могут быть применены в селекторах членов. Получив ''E.x'', где ''x'' — не член типа ''E'', компилятор Scala постарается добавить неявное преобразование ''I(E).x'' так, чтобы ''x'' был членом ''I(E)''.
Вот пример неявного преобразования функции, которая преобразует целые числа в экземпляры класса <tt>scala.Ordered</tt>:
<font size=3><syntaxhighlight lang=Scala>
implicit def int2ordered(x: Int): Ordered[Int] = new Ordered[Int] {
def compare(y: Int): Int =
if (x < y) -1
else if (x > y) 1
else 0
}
</syntaxhighlight></font>
=== View Bounds ===
View bounds
<font size=3><syntaxhighlight lang=Scala>
}
</syntaxhighlight></font>
Ограниченный по видимости параметр типа <tt>[A <% Ordered[A]]</tt> выражает, что сортировка применима для списков таких типов, для которых существует неявное преобразование <tt>A</tt> к <tt>Ordered[A]</tt>. Определение трактуется как укороченный синтаксис для следующей сигнатуры метода с неявным параметром:
<font size=3><syntaxhighlight lang=Scala>
def sort[A](xs: List[A])(implicit c: A => Ordered[A]): List[A] = ...
</syntaxhighlight></font>
(Здесь имя параметра ''c'' выбрано произвольно с учетом того, чтобы оно не конфликтовало с другими именами в программе.)
В качестве более детального примера, рассмотрим метод <tt>merge</tt>, который идет вместе с методом <tt>sort</tt>:
<font size=3><syntaxhighlight lang=Scala>
def merge[A <% Ordered[A]](xs: List[A], ys: List[A]): List[A] =
if (xs.isEmpty) ys
else if (ys.isEmpty) xs
else if (xs.head < ys.head) xs.head :: merge(xs.tail, ys)
else if ys.head :: merge(xs, ys.tail)
</syntaxhighlight></font>
После раскрытия ограничений по видимости и вставки неявных преобразований реализация метода принимает вид:
<font size=3><syntaxhighlight lang=Scala>
def merge[A](xs: List[A], ys: List[A])
(implicit c: A => Ordered[A]): List[A] =
</syntaxhighlight></font>
Последние две строки определения этого метода демонстрируют два различных использования неявного параметра ''c''. Он применяется в преобразовании в условии во второй строке с конца и передается как неявный аргумент в рекурсивный вызов <tt>merge</tt> в последней строке.
= Вывод типов Хиндли-Милнера =
|