Согласно этому вопросу система типов Scala завершена по Тьюрингу . Какие ресурсы доступны, чтобы новичок мог воспользоваться преимуществами программирования на уровне типов?
Вот ресурсы, которые я нашел на данный момент:
- Высокое волшебство Даниэля Спивака в Стране Скала
- Apocalisp по программированию Тип уровня в Scala
- Jesper в HList
Эти ресурсы прекрасны, но я чувствую, что мне не хватает основ, и поэтому у меня нет прочной основы, на которой можно было бы строить. Например, где есть введение в определения типов? Какие операции я могу выполнять с типами?
Есть ли хорошие вводные ресурсы?
Ответы:
Обзор
Программирование на уровне типов имеет много общего с традиционным программированием на уровне значений. Однако, в отличие от программирования на уровне значений, где вычисления происходят во время выполнения, в программировании на уровне типов вычисления происходят во время компиляции. Я попытаюсь провести параллели между программированием на уровне значений и программированием на уровне типов.
Парадигмы
В программировании на уровне типов есть две основные парадигмы: «объектно-ориентированная» и «функциональная». Большинство примеров, ссылки на которые приведены здесь, следуют объектно-ориентированной парадигме.
Хороший, довольно простой пример программирования на уровне типов в объектно-ориентированной парадигме можно найти в реализации лямбда-исчисления apocalisp , воспроизведенной здесь:
Как видно из примера, объектно-ориентированная парадигма для программирования на уровне типов действует следующим образом:
trait Lambda
что гарантирует , что следующие типы существуют:subst
,apply
иeval
.trait App extends Lambda
которые параметризованы двумя типами (S
иT
оба должны быть подтипамиLambda
),trait Lam extends Lambda
параметризованы одним типом (T
) иtrait X extends Lambda
(который не параметризован).#
(который очень похож на оператор точки:.
для значений). В чертеApp
примера лямбда - исчисления, типeval
реализуется следующим образом :type eval = S#eval#apply[T]
. По сути, это вызовeval
типа параметра признакаS
и вызовapply
с параметромT
результата. Обратите вниманиеS
: гарантированно будетeval
тип, потому что параметр указывает, что он является подтипомLambda
. Точно так же результатeval
должен иметьapply
тип, поскольку он указан как подтипLambda
, как указано в абстрактной характеристикеLambda
.Функциональная парадигма состоит из определения множества конструкторов параметризованных типов, которые не сгруппированы по признакам.
Сравнение программирования на уровне значений и программирования на уровне типов
abstract class C { val x }
trait C { type X }
C.x
(ссылка на значение поля / функцию x в объекте C)C#x
(ссылка на тип поля x в трейте C)def f(x:X) : Y
type f[x <: X] <: Y
(это называется «конструктором типа» и обычно встречается в абстрактной характеристике)def f(x:X) : Y = x
type f[x <: X] = x
a:A == b:B
implicitly[A =:= B]
assert(a == b)
implicitly[A =:= B]
A <:< B
, компилируется, только еслиA
является подтипомB
A =:= B
, компилируется, только еслиA
является подтипомB
иB
является подтипомA
A <%< B
, ("viewable as") компилируется только в том случае, еслиA
он доступен для просмотраB
(т. е. существует неявное преобразование изA
в подтипB
)Преобразование между типами и значениями
Во многих примерах типы, определенные с помощью признаков, часто являются абстрактными и запечатанными, и поэтому не могут быть созданы напрямую или через анонимный подкласс. Поэтому
null
при вычислении на уровне значений с использованием какого-либо типа интереса обычно используется в качестве значения-заполнителя:val x:A = null
, гдеA
тип, о котором вы заботитесьИз-за стирания типа все параметризованные типы выглядят одинаково. Более того, (как упоминалось выше) все значения, с которыми вы работаете, обычно
null
совпадают, поэтому обусловливание типа объекта (например, посредством оператора соответствия) неэффективно.Уловка заключается в использовании неявных функций и значений. Базовый вариант - это обычно неявное значение, а рекурсивный вариант - обычно неявная функция. В самом деле, программирование на уровне типов сильно использует имплициты.
Рассмотрим этот пример ( взятый из metascala и apocalisp ):
Здесь у вас есть пано-кодирование натуральных чисел. То есть у вас есть тип для каждого неотрицательного целого числа: специальный тип для 0, а именно
_0
; и каждое целое число больше нуля имеет тип формыSucc[A]
, гдеA
- тип, представляющий меньшее целое число. Например, тип, представляющий 2, будет:Succ[Succ[_0]]
(преемник применяется дважды к типу, представляющему ноль).Мы можем использовать псевдонимы для различных натуральных чисел для более удобного использования. Пример:
(Это очень похоже на определение
val
как результата функции.)Теперь предположим, что мы хотим определить функцию уровня значения,
def toInt[T <: Nat](v : T)
которая принимает значение аргументаv
, которое соответствуетNat
и возвращает целое число, представляющее натуральное число, закодированное вv
типе. Например, если у нас есть значениеval x:_3 = null
(null
типаSucc[Succ[Succ[_0]]]
), мы хотелиtoInt(x)
бы вернуть3
.Для реализации
toInt
мы собираемся использовать следующий класс:Как мы увидим ниже, будет объект, созданный из класса
TypeToValue
для каждогоNat
от_0
до (например)_3
, и каждый будет хранить представление значения соответствующего типа (т.е.TypeToValue[_0, Int]
будет хранить значение0
,TypeToValue[Succ[_0], Int]
будет хранить значение1
и т. Д.). Обратите внимание,TypeToValue
параметризуется двумя типами:T
иVT
.T
соответствует типу, которому мы пытаемся присвоить значения (в нашем примереNat
), иVT
соответствует типу значения, которое мы ему присваиваем (в нашем примере,Int
).Теперь мы сделаем следующие два неявных определения:
И реализуем
toInt
так:Чтобы понять, как
toInt
работает, давайте рассмотрим, что он делает на паре входов:Когда мы вызываем
toInt(z)
, компилятор ищет неявный аргументttv
типаTypeToValue[_0, Int]
(посколькуz
имеет тип_0
). Он находит объект_0ToInt
, вызываетgetValue
метод этого объекта и возвращает0
. Важно отметить, что мы не указали программе, какой объект использовать, компилятор нашел его неявно.А теперь рассмотрим
toInt(y)
. На этот раз компилятор ищет неявный аргументttv
типаTypeToValue[Succ[_0], Int]
(посколькуy
имеет типSucc[_0]
). Он находит функциюsuccToInt
, которая может возвращать объект соответствующего типа (TypeToValue[Succ[_0], Int]
), и оценивает ее. Эта функция сама принимает неявный аргумент (v
) типаTypeToValue[_0, Int]
(то есть,TypeToValue
где параметр первого типа имеет на один меньшеSucc[_]
). Компилятор предоставляет_0ToInt
(как это было сделано при оценкеtoInt(z)
выше) иsuccToInt
создает новыйTypeToValue
объект со значением1
. Опять же, важно отметить, что компилятор предоставляет все эти значения неявно, поскольку у нас нет доступа к ним явно.Проверка вашей работы
Есть несколько способов проверить, что ваши вычисления на уровне типов делают то, что вы ожидаете. Вот несколько подходов. Сделайте два типа
A
иB
, которые вы хотите проверить, равны. Затем проверьте, что следующая компиляция:Equal[A, B]
Equal[T1 >: T2 <: T2, T2]
( взято из apocolisp )implicitly[A =:= B]
В качестве альтернативы вы можете преобразовать тип в значение (как показано выше) и выполнить проверку значений во время выполнения. Например
assert(toInt(a) == toInt(b))
, гдеa
есть типа,A
а гдеb
- типаB
.Дополнительные ресурсы
Полный набор доступных конструкций можно найти в разделе типов справочного руководства по scala (pdf) .
У Адриана Мурса есть несколько научных статей о конструкторах типов и связанных темах с примерами из scala:
Apocalisp - это блог с множеством примеров программирования на уровне типов в scala.
ScalaZ - очень активный проект, который предоставляет функциональные возможности, расширяющие Scala API с помощью различных функций программирования на уровне типов. Это очень интересный проект, у которого много поклонников.
MetaScala - это библиотека уровня типов для Scala, включая метатипы для натуральных чисел, логических значений, единиц измерения, HList и т. Д. Это проект Джеспера Норденберга (его блог) .
В Michid (блоге) есть несколько потрясающих примеров программирования на уровне типов в Scala (из другого ответа):
У Дебасиша Гоша (блог) также есть несколько соответствующих сообщений:
(Я провел небольшое исследование по этому вопросу, и вот что я узнал. Я все еще новичок в этом, поэтому, пожалуйста, укажите на любые неточности в этом ответе.)
источник
В дополнение к другим ссылкам здесь есть также мои сообщения в блоге о метапрограммировании на уровне типов в Scala:
источник
Как предложено в Twitter: Shapeless: исследование универсального / политипического программирования на Scala Майлзом Сабином.
источник
источник
Scalaz имеет исходный код, вики и примеры.
источник