Бесформенный: проверка ограничений типа полиморфных функций

Я работаю над небольшой библиотекой для экономических моделей, которые проверяют единицы объектов, используя типы, например. вместо val apples = 2.0 пишем val apples = GoodsAmount[KG, Apples](2.0). Для создания пачки товаров я пытался использовать HLists из библиотеки shapeless. Это нормально работает, но в некоторых случаях я не могу использовать такой общий код, как я предпочитаю. См., Например, следующая проблема.

Я начинаю с простого кода, который объясняет, что я хочу превратить в бесформенное. Мы создаем два класса, один из которых представляет Km, а другой - Miles. Следует разрешить добавление классов км, но не миль. То, что я использую абстрактный тип T, главным образом мотивировано нашей более сложной библиотекой. И косвенный вызов функции «+» происходит только потому, что нам нужно нечто подобное в бесформенном случае позади.

trait Foo {
  type T
  val v: Double
  def +[B <: Foo](other: B)(implicit ev: this.T =:= other.T) = v + other.v
}

trait _Km 
trait _Miles

case class Km(v: Double)    extends Foo { type T = _Km }
case class Miles(v: Double) extends Foo { type T = _Miles }

object ExampleSimple extends App {
  def add[A <: Foo, B <: Foo](a: A, b: B)(implicit ev: a.T =:= b.T) = { a + b }

  add(Km(1), Km(2))
  // add(Km(1), Miles(2)) /* does not compile as intended */
}

Это работает как задумано. Но необходимо иметь проверку Type Contraint для функции «добавить». Моя попытка распространить это на HLists выглядит так:

object ExampleShapeless extends App {
  import shapeless._

  val l1 = Km(1) :: Km(2) :: HNil
  val l2 = Km(4) :: Km(3) :: HNil

  object add extends Poly1 {
    implicit def caseTuple[A <: Foo] = at[(A,A)] { case (a, b) => a + b }
  }

  (l1 zip l2).map(add)
}

Но это генерирует следующее сообщение об ошибке (с использованием Scala 2.10.2):

[error] /home/fuerst/gitg3m/code/types/src/main/scala/lagom_d/extract.scala:50: Cannot prove that a.T =:= b.T.
[error]     implicit def caseTuple[A <: Foo] = at[(A,A)] { case (a: Foo, b) => a + b }
[error]                                                                          ^
[error] /home/fuerst/gitg3m/code/types/src/main/scala/lagom_d/extract.scala:54: could not find implicit value for parameter mapper: shapeless.Mapper[ExampleShapeless.add.type,shapeless.::[(Km, Km),shapeless.::[(Km, Km),shapeless.HNil]]]
[error]   (l1 zip l2).map(add)

Первая ошибка должна быть исправлена ​​в случае, если я мог бы добавить ограничение типа к функции caseTuple, но, честно говоря, я не понял, как работает функция at и где я мог бы добавить параметр неявного доказательства. И я также не знаю, что я должен сделать, чтобы Картограф нашел свое неявное значение.

Менее общая версия, в которой я заменяю функцию caseTuple на

implicit def caseTuple = at[(Km,Km)] { case (a, b) => a + b }

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

Спасибо, клинке


person Klinke    schedule 27.06.2013    source источник
comment
Вы можете попробовать определить свой Foo следующим образом: trait Foo[T <: Foo] { v: Double; +(t T): T =...}. class Km(val v: Double) extends Foo[Km]. implicit def add[T] = at[(Foo[T], Foo[T])]   -  person senia    schedule 27.06.2013


Ответы (1)


Вы можете потребовать, чтобы члены типа совпадали, добавив параметр типа в регистр:

object add extends Poly1 {
  implicit def caseTuple[_T, A <: Foo { type T = _T }] = at[(A, A)] {
    case (a, b) => a + b
  }
}

Или вы можете использовать экзистенциальный тип, поскольку вас действительно волнует только то, что они одинаковы:

object add extends Poly1 {
  implicit def caseTuple[A <: Foo { type T = _T } forSome { type _T }] =
    at[(A, A)] {
      case (a, b) => a + b
    }
}

Любая версия обеспечит желаемое поведение.

person Travis Brown    schedule 27.06.2013
comment
Спасибо, это отлично работает и в моем более сложном случае ;-) Но у меня все еще есть проблема с отсутствующим неявным значением для Mapper. Я сам попробую решить, но может ты и здесь мне поможешь? - person Klinke; 28.06.2013
comment
Хорошо, нашел решение для упрощенной версии, добавление контекстной привязки для A помогает. Итак, теперь у меня implicit def caseTuple[_t, A <: Foo { type T = _T } <% Foo { type T = _T }] = .... На этот раз решение не так легко перевести в мою полную версию, но, надеюсь, я смогу исправить и новые проблемы. - person Klinke; 28.06.2013
comment
@Klinke: Я не уверен, что понимаю проблему - если я скопирую и вставлю свой add в ваш ExampleShapeless, все будет работать, как ожидалось. - person Travis Brown; 28.06.2013
comment
Я только что протестировал с фактическим бесформенным источником, но все еще получаю эту ошибку компилятора с вашим добавленным объектом: could not find implicit value for parameter mapper: shapeless.Mapper[ExampleShapeless.add.type,shapeless.::[(Km, Km),shapeless.::[(Km, Km),shapeless.HNil]]]. Добавление (implicit ev: A => Foo { type T = _T }) в функции caseTuple тоже помогает в моей библиотеке. - person Klinke; 28.06.2013