Эта страница документа относится к Scala 3 и может охватывать новые концепции, недоступные в Scala 2. Если не указано явно, все примеры кода на этой странице предполагают, что вы используете Scala 3.
Зависимый тип функции (dependent function type) описывает типы функций, где тип результата может зависеть от значений параметров функции. Концепция зависимых типов и типов зависимых функций является более продвинутой, и обычно с ней сталкиваются только при разработке собственных библиотек или использовании расширенных библиотек.
Зависимые типы методов
Рассмотрим следующий пример гетерогенной базы данных, в которой могут храниться значения разных типов. Ключ содержит информацию о типе соответствующего значения:
trait Key { type Value }
trait DB {
def get(k: Key): Option[k.Value] // зависимый метод
}
Получив ключ, метод get
предоставляет доступ к карте и потенциально возвращает сохраненное значение типа k.Value
.
Мы можем прочитать этот path-dependent type как:
“в зависимости от конкретного типа аргумента k
возвращается соответствующее значение”.
Например, у нас могут быть следующие ключи:
object Name extends Key { type Value = String }
object Age extends Key { type Value = Int }
Вызовы метода get
теперь будут возвращать такие типы:
val db: DB = ...
val res1: Option[String] = db.get(Name)
val res2: Option[Int] = db.get(Age)
Вызов метода db.get(Name)
возвращает значение типа Option[String]
,
а вызов db.get(Age)
возвращает значение типа Option[Int]
.
Тип возвращаемого значения зависит от конкретного типа аргумента, переданного для get
— отсюда и название dependent type.
Зависимые типы функций
Как видно выше, в Scala 2 уже была поддержка зависимых типов методов.
Однако создание значений типа DB
довольно громоздко:
// создание пользователя DB
def user(db: DB): Unit =
db.get(Name) ... db.get(Age)
// создание экземпляра DB и передача его `user`
user(new DB {
def get(k: Key): Option[k.Value] = ... // реализация DB
})
Необходимо вручную создать анонимный внутренний класс DB
, реализующий метод get
.
Для кода, основанного на создании множества различных экземпляров DB
, это очень утомительно.
Трейт DB
имеет только один абстрактный метод get
.
Было бы неплохо использовать в этом месте лямбда-синтаксис?
user { k =>
... // реализация DB
}
На самом деле, в Scala 3 теперь это возможно! Можно определить DB
как зависимый тип функции:
type DB = (k: Key) => Option[k.Value]
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// зависимый тип функции
Учитывая это определение DB
, можно использовать приведенный выше вызов user
.
Подробнее о внутреннем устройстве зависимых типов функций можно прочитать в справочной документации.
Практический пример: числовые выражения
Предположим, что необходимо определить модуль, который абстрагируется от внутреннего представления чисел. Это может быть полезно, например, для реализации библиотек для автоматического дифференцирования.
Начнем с определения модуля для чисел:
trait Nums:
// тип Num оставлен абстрактным
type Num
// некоторые операции над числами
def lit(d: Double): Num
def add(l: Num, r: Num): Num
def mul(l: Num, r: Num): Num
Здесь опускается конкретная реализация
Nums
, но в качестве упражнения можно реализоватьNums
, назначив типNum = Double
и реализуя соответствующие методы.
Программа, использующая числовую абстракцию, теперь имеет следующий тип:
type Prog = (n: Nums) => n.Num => n.Num
val ex: Prog = nums => x => nums.add(nums.lit(0.8), x)
Тип функции, которая вычисляет производную, наподобие ex
:
def derivative(input: Prog): Double
Учитывая удобство зависимых типов функций, вызов этой функции в разных программах прост:
derivative { nums => x => x }
derivative { nums => x => nums.add(nums.lit(0.8), x) }
// ...
Напомним, что та же программа в приведенной выше кодировке будет выглядеть так:
derivative(new Prog {
def apply(nums: Nums)(x: nums.Num): nums.Num = x
})
derivative(new Prog {
def apply(nums: Nums)(x: nums.Num): nums.Num = nums.add(nums.lit(0.8), x)
})
// ...
Комбинация с контекстными функциями
Комбинация методов расширения, контекстных функций и зависимых функций обеспечивает мощный инструмент для разработчиков библиотек. Например, мы можем уточнить нашу библиотеку, как указано выше, следующим образом:
trait NumsDSL extends Nums:
extension (x: Num)
def +(y: Num) = add(x, y)
def *(y: Num) = mul(x, y)
def const(d: Double)(using n: Nums): n.Num = n.lit(d)
type Prog = (n: NumsDSL) ?=> n.Num => n.Num
// ^^^
// prog теперь - контекстная функция,
// которая неявно предполагает NumsDSL в контексте вызова
def derivative(input: Prog): Double = ...
// теперь нам не нужно упоминать Nums в приведенных ниже примерах
derivative { x => const(1.0) + x }
derivative { x => x * x + const(2.0) }
// ...
Contributors to this page:
Contents
- Введение
- Возможности Scala
- Почему Scala 3?
- Почувствуй Scala
- Пример 'Hello, World!'
- REPL
- Переменные и типы данных
- Структуры управления
- Моделирование данных
- Методы
- Функции первого класса
- Одноэлементные объекты
- Коллекции
- Контекстные абстракции
- Верхнеуровневые определения
- Обзор
- Первый взгляд на типы
- Интерполяция строк
- Структуры управления
- Моделирование предметной области
- Инструменты
- Моделирование ООП
- Моделирование ФП
- Методы
- Особенности методов
- Main методы в Scala 3
- Обзор
- Функции
- Анонимные функции
- Параметры функции
- Eta расширение
- Функции высшего порядка
- Собственный map
- Создание метода, возвращающего функцию
- Обзор
- Пакеты и импорт
- Коллекции в Scala
- Типы коллекций
- Методы в коллекциях
- Обзор
- Функциональное программирование
- Что такое функциональное программирование?
- Неизменяемые значения
- Чистые функции
- Функции — это значения
- Функциональная обработка ошибок
- Обзор
- Типы и система типов
- Определение типов
- Параметризованные типы
- Пересечение типов
- Объединение типов
- Алгебраические типы данных
- Вариантность
- Непрозрачные типы
- Структурные типы
- Зависимые типы функций
- Другие типы
- Контекстные абстракции
- Методы расширения
- Параметры контекста
- Контекстные границы
- Given импорты
- Классы типов
- Многостороннее равенство
- Неявное преобразование типов
- Обзор
- Параллелизм
- Scala утилиты
- Сборка и тестирование проектов Scala с помощью Sbt
- Рабочие листы
- Взаимодействие с Java