Проблема перевода ограничений типа Scala в Java - PullRequest
1 голос
/ 12 ноября 2019

У меня есть серия 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

Ответы [ 2 ]

2 голосов
/ 12 ноября 2019

Err, я думаю, это работает? Это в основном 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 добьются цели здесь), но это сложно.

0 голосов
/ 12 ноября 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)
}

(при необходимости внесите еще несколько изменений)

...