Можно ли в scala shapeless использовать буквальный тип в качестве параметра универсального типа?

Предполагая, что я пишу программу для умножения векторов. Следуя требованиям этой статьи:

https://etrain.github.io/2015/05/28/type-safe-linear-algebra-in-scala

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

import shapeless.Witness

trait Axis extends Serializable

case object UnknownAxis extends Axis

trait KnownAxis[W <: Witness.Lt[Int]] extends Axis {

  def n: Int

  def ++(that: KnownAxis[W]): Unit = {}
}

object KnownAxis {

  val w1 = Witness(1)
  val w2 = Witness(2)

  case class K1(n: Witness.`1`.T) extends KnownAxis[w1.type]
  case class K2(n: Witness.`2`.T) extends KnownAxis[w2.type]

//  K2(2) ++ K1(1) // doesn't compile

  K2(2) ++ K2(2)
}

Пока все хорошо, проблема, однако, возникает, когда я пытаюсь обобщить ее для всех n:

  case class KN[W <: Witness.Lt[Int]](n: W#T) extends KnownAxis[W]

  KN(1)

Приведенный выше код вызывает ошибку компиляции:

Axis.scala:36: type mismatch;
 found   : Int(1)
 required: this.T
[ERROR]   KN(1)
[ERROR]      ^
[ERROR] one error found

У меня вопрос: почему Spark не может сосредоточиться на более совершенном типе Witness.`1`.T, вместо этого он использует тип Int? Что нужно для того, чтобы переопределить это поведение, чтобы можно было успешно определить класс case KN?

ОБНОВЛЕНИЕ 1: ответ был перемещен к новому вопросу:

При использовании синглтона функция типа Scala shapeless, как заставить компилятор использовать узкий / одноэлементный тип в качестве неявного параметра?


person tribbloid    schedule 02.03.2020    source источник


Ответы (2)


Неудивительно, что Scala не может вывести W заданный W#T, потому что, вообще говоря, два разных W могут иметь одинаковые W#T. Не для типов-свидетелей, но компилятор не обрабатывает их специально.

Я ожидал, что сработает (и не уверен, почему это не так), так это указать параметр типа:

KN[Witness.`1`](1)
// error: type arguments [scala.this.Any] do not conform to method apply's type parameter bounds [W <: shapeless.this.Witness.Lt[scala.this.Int]]

или более вероятно

KN[w1.type](1)
// error: type mismatch;
// found   : scala.this.Int(1)
// required: .this.T

Что работает:

case class KN[W <: Witness.Lt[Int]](w: W) extends KnownAxis[W] {
  val n = w.value
}

KN(Witness(1))

Кажется, он соответствует требованиям вашего вопроса, но я не знаю, будет ли он работать с остальной частью вашего кода.

Вы также можете рассмотреть эту альтернативу, которая не требует Shapeless в Scala 2.13:

trait Axis extends Serializable

case object UnknownAxis extends Axis

trait KnownAxis[W <: Int with Singleton] extends Axis {

  def n: W

  def ++(that: KnownAxis[W]): Unit = {}
}

case class KN[W <: Int with Singleton](n: W) extends KnownAxis[W]

KN(1)

Для 2.12

import shapeless.syntax.singleton._

...
KN(1.narrow)
person Alexey Romanov    schedule 02.03.2020
comment
Это было очень информативно, интересно, могу ли я сделать .narrow неявным вызовом - person tribbloid; 02.03.2020
comment
Не знаю, но я был бы немного удивлен, если бы вы могли. - person Alexey Romanov; 03.03.2020
comment
С другой стороны, разве Witness.apply это как-то не делает? Вы можете посмотреть его определение. - person Alexey Romanov; 03.03.2020
comment
Нет, не работает: - ‹Только что попробовал, неявная подпись не работает при утином вводе, включая шаблон Lt / Aux - person tribbloid; 03.03.2020
comment
Хорошо, на самом деле неявная подпись «каким-то образом работает» с шаблоном магнита, но Witness.Lt не является достаточно сильным ограничением. Я обновил свой вопрос, чтобы узнать, доступен ли обход в 2.12. - person tribbloid; 03.03.2020
comment
Поразмыслив, я перенесу обновление в новый вопрос, чтобы принять ваш ответ. - person tribbloid; 04.03.2020
comment
Вероятно, вы имели в виду KN[Witness.`1`.T](1), хотя это тоже не работает. - person Dmytro Mitin; 12.03.2020
comment
Нет, Witness.``1``.T явно не того типа (тогда вы получите n: Witness.``1``.T#T). (Не знаю, как правильно убежать `в комментариях) - person Alexey Romanov; 13.03.2020

Попробуйте параметризовать KnownAxis с помощью одноэлементного подтипа Int

trait KnownAxis[N <: Int] extends Axis {
  def n: N
  def ++(that: KnownAxis[N]): Unit = {}
}

case class K1(n: Witness.`1`.T) extends KnownAxis[Witness.`1`.T]
case class K2(n: Witness.`2`.T) extends KnownAxis[Witness.`2`.T]

case class KN[N <: Int](n: N)(implicit w: Witness.Aux[N]) extends KnownAxis[N]

import shapeless.syntax.singleton._
KN[Witness.`1`.T](1.narrow)

или просто

KN[Witness.`1`.T](1)

Или с абстрактным классом вместо трейта

abstract class KnownAxis[N <: Int](implicit w: Witness.Aux[N]) extends Axis {
  def n: N
  def ++(that: KnownAxis[N]): Unit = {}
}
person Dmytro Mitin    schedule 12.03.2020