Установление связей между типами и значениями

У меня есть реализации арифметики на уровне типов, способные выполнять некоторую арифметическую проверку времени компиляции, а именно <,>,= двумя способами:

С ними у меня может быть функция getFoo, которую я могу вызывать следующим образом:

getFoo[_2,_3]

Поскольку _2 и _3 являются эквивалентами целочисленных значений 2 и 3 на уровне типов. Теперь в идеале я хотел бы, чтобы моя функция getFoo принимала целочисленные значения в качестве аргументов и пыталась вывести _2 из значения 2.

Мой план состоял в том, чтобы добавить в базовый класс Nat следующую информацию AssociatedInt:

trait Nat {
  val associatedInt: Int
  type AssociatedInt = associatedInt.type
}

Так что последующие типы будут определены как:

type _1 = Succ[_0] {
  override val associatedInt: Int = 1
}

А затем измените подпись getFoo так, чтобы она принимала целое число:

def getFoo(i:Int)(implicit ...)

Исходя из этого, мы будем выполнять арифметические утверждения на уровне типов с типами, связанными с типом AssociatedInt. То есть что-то вроде:

def getFoo(i: Integer)(implicit ev: Nat{type I = i.type } =:= ExpectedType)

Который не работает. То есть:

trait Nat {
  val i: Integer
  type I = i.type
}

type ExpectedType = _1

trait _1 extends Nat {
  override val i: Integer = 1
}

def getFoo(i: Integer)
          (implicit ev: Nat{type I = i.type } =:= ExpectedType)= ???

getFoo(1) //this fails to prove the =:= implicit.

По размышлении, я не должен был этого ожидать. Так как если у нас есть:

val x : Integer = 1
val y : Integer = 1
type X = x.type
type Y = y.type
def foo(implicit ev: X =:= Y) = 123
foo //would fail to compile.

т.е. одноэлементные типы разных «объектов» с одинаковыми значениями различны. (Я предполагаю, что причина в том, что в настоящее время в Scala одноэлементные типы предназначены для объектов и отличаются от буквенный тип)

Итак, с этой справочной информацией я хотел бы знать, есть ли способ добиться того, что я пытаюсь сделать, а именно вывести тип a из связанного значения с помощью других методов.


person Maths noob    schedule 08.06.2018    source источник


Ответы (2)


Ответ, кажется, просто "Нет". Значения существуют во время выполнения. Проверка типов происходит во время компиляции. Эти два временных интервала не пересекаются, время выполнения всегда наступает строго после времени компиляции, поэтому нет возможности распространить информацию о значении назад во времени, чтобы получить какую-то дополнительную информацию о типе.

Обратите внимание, что если порядок — это все, что вам нужно (вы не хотите добавлять или вычитать номера версий), то вы можете просто повторно использовать отношение подтипа следующим образом:

sealed trait Num
class _9 extends Num
class _8 extends _9
class _7 extends _8
class _6 extends _7
class _5 extends _6
class _4 extends _5
class _3 extends _4
class _2 extends _3
class _1 extends _2
class _0 extends _1

trait Version[+Major <: Num, +Minor <: Num]

println(implicitly[Version[_2, _4] =:= Version[_2, _4]])
println(implicitly[Version[_2, _3] <:< Version[_2, _4]])
person Andrey Tyukin    schedule 08.06.2018
comment
Спасибо. хотя ваше замечание о разделении значений и типов справедливо, я думаю, что у нас все еще есть уловки, такие как шаблон Aux и т. д., и это то, что я ищу. - person Maths noob; 08.06.2018
comment
@Mathsnoob Aux просто распаковывает некоторые члены типа в качестве явных аргументов для функций уровня типа, которые сопоставляют типы с другими типами. Это никак не поможет вам сопоставить values с types, это было бы программированием с зависимыми типами, а это требует совершенно другого дизайна всего языка. - person Andrey Tyukin; 08.06.2018
comment
Я получил то, что хотел, я думаю. Использование экземпляров типа оболочки! :П - person Maths noob; 10.06.2018

Хитрость заключается в том, чтобы понять, что значения могут иметь поля типа и что информация о типе доступна во время компиляции. Имея это в виду, мы можем определить:

sealed trait NatEnum{
  type Nat_ <:Nat
}

и определите "значения" перечисления для таких типов, как:

object __0 extends NatEnum{ override type Nat_ = _0 }
object __1 extends NatEnum{ override type Nat_ = _1 }
object __2 extends NatEnum{ override type Nat_ = _2 }
object __3 extends NatEnum{ override type Nat_ = _3 }

и рефакторинг getFoo как показано ниже:

def getFoo(maj: NatEnum, min: NatEnum)(implicit
                     maj_check: FooClient.Major =:= maj.Nat_,
                     min_check: FooClient.Minor <:< min.Nat_
                    ) = FooClient.foo

который мы можем протестировать с помощью:

getFoo(__2,__2) //compiles
getFoo(__1,__0)// doesn't compile

вот обновленная версия списков: простой и строгий

person Maths noob    schedule 10.06.2018
comment
Ну... Да, это, очевидно, работает с одноэлементными объектами, у которых есть постоянные пути. Если значение и путь значения являются постоянными для всех времен, то никакой информации не нужно передавать из будущего, поэтому причинно-следственная связь не нарушается. Честно говоря, я до сих пор не уверен, что это вам даст: что эти значения делают, чего не делают типы, если значения в любом случае являются константами? - person Andrey Tyukin; 10.06.2018
comment
Извините за путаницу. Нет, я просто пытаюсь сделать эту конструкцию более удобной для пользователя. Обычно я стараюсь избегать функций, требующих предоставления явной информации о типе. В новой форме пользователь может работать только с перечислениями, и ему не нужно сильно беспокоиться о реализации лежащего в основе Nat. - person Maths noob; 10.06.2018