2010-12-11 3 views
99

Согласно this question, система типа Scala - Turing complete. Какие ресурсы доступны, чтобы позволить новичкам воспользоваться преимуществами программирования на уровне типа?Программные ресурсы типа Scala

Вот ресурсы, которые я нашел до сих пор:

Эти ресурсы велики, но я чувствую, что я пропускаю основы и, следовательно, не имеют прочной основы для построения. Например, где есть введение в определения типов? Какие операции я могу выполнять по типам?

Есть ли хорошие вводные ресурсы?

+5

Сообщество wiki? –

+0

Лично я считаю, что кто-то, кто хочет заниматься программированием на уровне уровня в Scala, уже знает, как сделать программирование в Scala вполне разумным. Даже если это означает, что я не понимаю ни слова тех статей, которые вы связали с :-) –

ответ

133

Обзор

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

Парадигмы

Существуют две основные парадигмы в программировании типа уровня: «объектно-ориентированный» и «функциональный». Большинство примеров, связанных с этим, следуют объектно-ориентированной парадигме.

Хороший, довольно простой пример программирования типа уровня в объектно-ориентированной парадигмы можно найти в apocalisp-х implementation of the lambda calculus, реплицируются здесь:

// Abstract trait 
trait Lambda { 
    type subst[U <: Lambda] <: Lambda 
    type apply[U <: Lambda] <: Lambda 
    type eval <: Lambda 
} 

// Implementations 
trait App[S <: Lambda, T <: Lambda] extends Lambda { 
    type subst[U <: Lambda] = App[S#subst[U], T#subst[U]] 
    type apply[U] = Nothing 
    type eval = S#eval#apply[T] 
} 

trait Lam[T <: Lambda] extends Lambda { 
    type subst[U <: Lambda] = Lam[T] 
    type apply[U <: Lambda] = T#subst[U]#eval 
    type eval = Lam[T] 
} 

trait X extends Lambda { 
    type subst[U <: Lambda] = U 
    type apply[U] = Lambda 
    type eval = X 
} 

Как можно видеть на примере, объектно-ориентированный парадигма для программирования на уровне типа поступает следующим образом:

  • Первый: определение абстрактного признака с различными полями абстрактного типа (см. ниже, для чего абстрактное поле). Это шаблон для гарантии того, что поля определенных типов существуют во всех реализациях без принудительной реализации.В примере лямбда-исчисления это соответствует trait Lambda, что гарантирует существование следующих типов: subst, apply и eval.
  • Далее: определить subtraits, которые расширяют абстрактные черты и реализовать различные абстрактные поля типа
    • Часто эти subtraits будут параметрироваться с аргументами. В примере лямбда-исчислении, подтипы являются trait App extends Lambda, которые спараметрирован с двумя типами (S и T, оба должны быть подтипы Lambda), trait Lam extends Lambda параметризованные с одним типом (T) и trait X extends Lambda (который не является параметризованных).
    • поля типа часто реализуются путем ссылки на параметры типа subtrait и иногда ссылаются на их поля типа с помощью оператора хеша: # (который очень похож на оператор точки: . для значений). В примере примера лямбда-исчисления тип eval реализован следующим образом: type eval = S#eval#apply[T]. Это по существу вызывает тип eval параметра признака S и вызывает apply с параметром T на результат. Примечание. S имеет тип eval, потому что параметр указывает его подтип Lambda. Аналогично, результат eval должен иметь тип apply, поскольку он указан подтипом Lambda, как указано в абстрактном признаке Lambda.

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

Сравнение между программированием значения уровня и программирования типа уровня

  • abstract class
    • значение уровня: abstract class C { val x }
    • уровень типа: trait C { type X }
  • путь зависит типы
    • C.x (ссылки на значение поля/функцию х в объекте C)
    • C#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]
    • значение уровня: Случается в виртуальной машине Java с помощью теста блока во время выполнения (т.е. нет ошибок во время выполнения):
      • в Ессенции не является утверждают: assert(a == b)
    • уровень типа: Случается в компиляторе через typecheck (т.е. без ошибок компилятора):
      • по сути является сравнение типов: например implicitly[A =:= B]
      • A <:< B, компилирует только если A является подтипом B
      • A =:= B, компилирует только если A является подтипом B и B является подтипом A
      • A <%< B, («видимыми как») компилирует только если A можно просмотреть как B (т.е.существует неявное преобразование из A к подтипу B)
      • an example
      • more comparison operators

Преобразование между типами и значения

  • Во многих из экс Ярлыки, типы, определенные через признаки, часто являются абстрактными и запечатаны, и поэтому не могут быть созданы напрямую или через анонимный подкласс. Таким образом, он является общим для использования null в качестве значения заполнителя при выполнении вычисления значения уровня, используя некоторый тип интереса:

    • например val x:A = null, где A типа вы заботитесь о
  • Благодаря типа стирания, параметризованные типы все выглядят одинаково. Кроме того, (как упоминалось выше) значения, с которыми вы работаете, имеют тенденцию быть null, и поэтому настройка типа объекта (например, через оператор соответствия) неэффективна.

Хитрость заключается в использовании неявных функций и значений. Основной случай обычно является неявным значением, а рекурсивный случай обычно является неявной функцией. Действительно, программирование на уровне шрифтов сильно использует имплициты.

Рассмотрим пример (taken from metascala и apocalisp):

sealed trait Nat 
sealed trait _0 extends Nat 
sealed trait Succ[N <: Nat] extends Nat 

Здесь у вас есть Пеано кодирование натуральных чисел. То есть у вас есть тип для каждого неотрицательного целого: специальный тип для 0, а именно _0; и каждое целое число больше нуля имеет тип формы Succ[A], где A - это тип, представляющий меньшее целое число. Например, тип, представляющий 2, будет: Succ[Succ[_0]] (преемник дважды применяется к типу, представляющему ноль).

Мы можем использовать различные натуральные номера для более удобного обращения. Пример:

type _3 = Succ[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 Для реализации, мы будем использовать следующий класс:

class TypeToValue[T, VT](value : VT) { def getValue() = value } 

Как мы увидим ниже, там будет объект, построенный из класса TypeToValue для каждого Nat от _0 до (например) _3, и каждый будет хранить представление соответствующего типа (то есть TypeToValue[_0, Int] сохранит значение 0, TypeToValue[Succ[_0], Int] сохранит значение 1 и т. д.). Примечание. TypeToValue параметризуется двумя типами: T и VT. T соответствует типу, который мы пытаемся присвоить значениям (в нашем примере Nat), а VT соответствует типу присвоенного ему значения (в нашем примере Int).

Теперь мы делаем следующие два неявные определения:

implicit val _0ToInt = new TypeToValue[_0, Int](0) 
implicit def succToInt[P <: Nat](implicit v : TypeToValue[P, Int]) = 
    new TypeToValue[Succ[P], Int](1 + v.getValue()) 

И мы реализуем toInt следующим образом:

def toInt[T <: Nat](v : T)(implicit ttv : TypeToValue[T, Int]) : Int = ttv.getValue() 

Чтобы понять, как toInt работы, давайте рассмотрим, что он делает на пару входов :

val z:_0 = null 
val y:Succ[_0] = null 

Когда мы звоним 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]
  • implicitly[A =:= B]

В качестве альтернативы, вы можете преобразовать тип в значение (как показано выше) и выполнить проверку значений времени. Например. assert(toInt(a) == toInt(b)), где a имеет тип A и b имеет тип B.

Дополнительные ресурсы

Полный набор доступных конструкций можно найти в разделе Типы the scala reference manual (pdf).

Adriaan Moors имеет несколько научных статей о конструкторах типа и связанные с ними темы с примерами из Скале:

Apocalisp - это блог со многими примерами профессионального уровня грамматический определенный член в scala.

  • Type-Level Programming in Scala фантастическая экскурсия некоторого программирования типа уровня, который включает булевы, натуральные числа (как указано выше), двоичные числа, гетерогенные списки и многое другое.
  • More Scala Typehackery это реализация исчисления лямбда выше.

ScalaZ - очень активный проект, предоставляющий функциональность, которая расширяет Scala API, используя различные функции программирования уровня. Это очень интересный проект, который имеет большое значение.

MetaScala - это библиотека уровня уровня для Scala, включая мета-типы для натуральных чисел, булевых единиц, единиц, HList и т. Д. Это проект Jesper Nordenberg (his blog).

Michid (blog) имеет некоторые удивительные примеры программирования типа уровня в Scala (из других ответов):

Debasish Ghosh (blog) имеет некоторые соответствующие должности, а также:

(Я занимался некоторыми исследованиями по этому вопросу, и вот что я узнал. Я еще новичок в этом, поэтому, пожалуйста, указать на какую-либо неточность в этом ответе.)

12

В дополнение к другим ссылкам здесь, есть и мои посты в блоге на уровне типа мета-программирования в Scala:

+0

Просто хотела сказать спасибо за интересный блог; Я слежу за ним некоторое время, и особенно последнее сообщение, упомянутое выше, обострило мое понимание важных свойств, которые должна иметь система типов для объектно-ориентированного языка. Так что спасибо! –

Смежные вопросы