Проблема с переводом ограничений типа Scala в Java

У меня есть ряд классов Java, которые действуют как оболочки для классов Java, например. Integer, String, ZonedDateTime и т. Д., И я помещаю их в этот Type<T> интерфейс, где T - это то, чем будет фактический базовый тип Java.

Есть еще один класс: final class Field<T, U extends Type<T>>.

Наконец, у меня есть следующий интерфейс конструктора.

class DataBuilder {
    <T, U extends Type<T>> DataBuilder addEntry(Field<T, U> field, T value) {
        return this;
    }
}

Это отлично работает при вызове со стороны Java:

    public static void main(String[] args) {
        Field<String, StringType> field1 = new Field<>();
        Field<Boolean, BooleanType> field2 = new Field<>();

        Map<Field, Object> map = new HashMap<>();
        map.put(field1, "abc");
        map.put(field2, true);

        DataBuilder dataBuilder = new DataBuilder();
        map.forEach(dataBuilder::addEntry);

        System.out.println("good");
    }

Вызов этого со стороны Scala вызывает некоторую проблему.

object Hello extends App {
  val field1 = new Field[String, StringType]
  val field2 = new Field[java.lang.Boolean, BooleanType]

  val map = Map(
    field1 -> "abc",
    field2 -> boolean2Boolean(true)
  )

  val dataBuilder: DataBuilder = new DataBuilder

  map.foreach { case (key, value) => dataBuilder.addEntry(key, value) }
}

Это дает мне три ошибки:

Error:(14, 50) inferred type arguments [Comparable[_ >: Boolean with String <: Comparable[_ >: Boolean with String <: java.io.Serializable] with java.io.Serializable] with java.io.Serializable,_2] do not conform to method addEntry's type parameter bounds [T,U <: example.Type[T]]
  map.foreach { case (key, value) => dataBuilder.addEntry(key, value) }
Error:(14, 59) type mismatch;
 found   : example.Field[_1,_2] where type _2 >: example.BooleanType with example.StringType <: example.Type[_ >: Boolean with String <: Comparable[_ >: Boolean with String <: java.io.Serializable] with java.io.Serializable], type _1 >: Boolean with String <: Comparable[_ >: Boolean with String <: Comparable[_ >: Boolean with String <: java.io.Serializable] with java.io.Serializable] with java.io.Serializable
 required: example.Field[T,U]
  map.foreach { case (key, value) => dataBuilder.addEntry(key, value) }
Error:(14, 64) type mismatch;
 found   : Comparable[_ >: Boolean with String <: Comparable[_ >: Boolean with String <: Comparable[_ >: Boolean with String <: java.io.Serializable] with java.io.Serializable] with java.io.Serializable] with java.io.Serializable
 required: T
  map.foreach { case (key, value) => dataBuilder.addEntry(key, value) }

Я понимаю, что scala пытается определить наиболее точный тип, пытаясь найти общий тип для всех типов на карте, но есть ли способ не сделать его таким явным, при этом позволяя библиотеке java работать в scala?

См. Демонстрационный код в github: https://github.com/ssgao/java-scala-type-issue


person ssgao    schedule 11.11.2019    source источник
comment
Прохождение Map с Any редко бывает хорошей идеей, когда цель состоит в том, чтобы сохранить типы (как это будет для конструктора, используемого после)   -  person cchantep    schedule 12.11.2019
comment
Причина использования карты для хранения этих пар заключается в возможности повторного использования кода: я также хочу просто зарегистрировать весь Map.   -  person ssgao    schedule 12.11.2019


Ответы (2)


Эээ, я думаю, это работает? Это в основном код Java, за исключением того, что некоторые взломы типов сделаны явными.

// The forSome type is not inferred
// Instead, I think this is where the wonky Comparable[_ >: What with Even <: Is] with This type comes from
val map = Map[Field[T, U] forSome { type T; type U <: Type[T] }, Any](
  field1 -> "abc",
  field2 -> boolean2Boolean(true)
  // field2 -> new AnyRef // works, there's no checking
)
val dataBuilder = new DataBuilder
// we know that key: Field[T, U] forSome { type T; type U <: Type[T] }
// the second match doesn't *do* anything, but it lets us name these
// two types something (here, t, u) and use them as type arguments to addEntry
map.foreach { case (key, value) => key match {
  case key: Field[t, u] => dataBuilder.addEntry[t, u](key, value.asInstanceOf[t])
} }

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

person HTNW    schedule 11.11.2019
comment
Мой компилятор не распознает, что такое Field[t, u]. Typechecker жалуется на использование экзистенциальных типов и этот пост говорит, что это, скорее всего, скоро уйдет ... Есть ли способ обойтись с использованием экзистенциального типа? - person ssgao; 12.11.2019
comment
Переменные типа привязки очень привередливы, но Scala 2.13 отлично справляется с этим кодом. Если вы хотите удалить экзистенциальные данные, поскольку этот код даже не пытается обезопасить себя, просто сделайте тип ключа Map Field[_, _]. Это делает его менее точным, но это нормально, если вам просто все равно. Если бы вам было все равно, вам пришлось бы создать класс-оболочку implicit class FieldOf[T](val field: Field[T, _ <: Type[T]]) extends AnyVal. - person HTNW; 12.11.2019
comment
На самом деле, если задуматься, если замена экзистенциальных элементов будет работать так же, как в Dotty, Field[_, _] фактически окажется тем же, что и тип forSome здесь. Так что не о чем беспокоиться. - person HTNW; 12.11.2019

Вы можете использовать «разнородную карту». Гетерогенная карта - это карта, где ключи и значения могут иметь разные типы. Я не знаю ни одной существующей реализации гетерогенной карты с типобезопасностью, которая бы удовлетворяла вашим потребностям, но вот действительно простая реализация такой. Я думаю, что вам действительно нужна возможность использовать стандартные операции сбора на этой забавной карте, и это должно помочь в этом.

import scala.language.existentials
import scala.collection._

class HMap[K[_], V[_]](underlying: immutable.Map[K[_], V[_]] = immutable.Map.empty[K[_], V[_]])
  extends Iterable[(K[T], V[T]) forSome { type T }]
     with IterableOps[(K[T], V[T]) forSome { type T }, Iterable, HMap[K, V]] {
  // collections boilerplate
  override protected def fromSpecific(coll: IterableOnce[(K[T], V[T]) forSome { type T }]): HMap[K, V]
  = new HMap[K, V](immutable.Map.from(coll))
  override protected def newSpecificBuilder: mutable.Builder[(K[T], V[T]) forSome { type T }, HMap[K, V]]
  = immutable.Map.newBuilder[K[_], V[_]].mapResult(new HMap[K, V](_))
  override def empty: HMap[K, V] = new HMap[K, V]
  override def iterator: Iterator[(K[T], V[T]) forSome { type T }]
  = new Iterator[(K[T], V[T]) forSome { type T }] {
    val underlying = HMap.this.underlying.iterator
    override def hasNext: Boolean = underlying.hasNext
    override def next(): (K[T], V[T]) forSome { type T }
    = underlying.next().asInstanceOf[(K[T], V[T]) forSome { type T}]
  }

  // Mappy operations
  def get[T](k: K[T]): Option[V[T]] = underlying.get(k).map(_.asInstanceOf[V[T]])
  def +[T](kv: (K[T], V[T])): HMap[K, V] = new HMap[K, V](underlying + kv)
  def -[T](k: K[T]): HMap[K, V] = new HMap[K, V](underlying - k)
}
object HMap {
  // Mappy construction
  def apply[K[_], V[_]](elems: (K[T], V[T]) forSome { type T }*): HMap[K, V] = new HMap[K, V](immutable.Map(elems: _*))
}

Теперь все работает довольно аккуратно

type FieldOf[T] = Field[T, _ <: Type[T]]
type Id[T] = T
val map = HMap[FieldOf, Id](
  field1 -> "abc",
  field2 -> boolean2Boolean(true)
  // field2 -> new AnyRef // doesn't work, readable(-ish) error message
)
val dataBuilder = new DataBuilder
map.foreach { case (key, value) => dataBuilder.addEntry(key, value) }

Если экзистенциальные представления доставляют вам дискомфорт, следует заменить (K[T], V[T]) forSome { type T } на Prod[K, V, _], где

case class Prod[K[_], V[_], T](k: K[T], v: V[T])
object Prod {
    implicit def fromTup2[K[_], V[_], T](kv: (K[T], V[T])): Prod[K, V]
    = Prod(kv._1, kv._2)
}

(при необходимости, с некоторыми дополнительными изменениями)

person HTNW    schedule 12.11.2019