Scala: универсальный класс с несколькими конструкторами - PullRequest
3 голосов
/ 22 июля 2011

Я пытаюсь создать общий класс, подобный этому:

class A[T](v: Option[T]) {
  def this(v: T) = this(Some(v))
  def this() = this(None)
  def getV = v 
}

Затем я провожу некоторое тестирование:

scala> new A getV
res21: Option[Nothing] = None
scala> new A(8) getV
res22: Option[Int] = Some(8)

Пока все хорошо. Но как только я пытаюсь вызвать главный конструктор, я получаю это:

scala> new A(Some(8)) getV
<console>:9: error: ambiguous reference to overloaded definition,
both constructor A in class A of type (v: T)A[T]
and  constructor A in class A of type (v: Option[T])A[T]
match argument types (Some[Int])
       new A(Some(8)) getV
       ^

scala> new A(None) getV
<console>:9: error: ambiguous reference to overloaded definition,
both constructor A in class A of type (v: T)A[T]
and  constructor A in class A of type (v: Option[T])A[T]
match argument types (None.type)
       new A(None) getV
       ^

Что такого "неоднозначного" между этими двумя конструкторами? Или (позвольте мне догадаться) это еще одна вещь, которую я не знаю о системе типов Scala? :)

Конечно, если я использую неуниверсальный класс, все работает как положено. Мой B класс работает просто отлично:

class B(v: Option[String]) {
  def this(v: String) = this(Some(v))
  def this() = this(None)
  def getV = v 
}

scala> new B("ABC") getV
res26: Option[String] = Some(ABC)
scala> new B getV
res27: Option[String] = None
scala> new B(Some("ABC")) getV
res28: Option[String] = Some(ABC)
scala> new B(None) getV
res29: Option[String] = None

Ответы [ 2 ]

7 голосов
/ 22 июля 2011

new A(Some(8)) может быть либо:

  • новый экземпляр A[Int] через первичный конструктор,
  • новый экземпляр A[Option[Int]] через альтернативный конструктор.

Вы можете указать тип явно, как new A[Int](Some(8)).

1 голос
/ 22 июля 2011

Проблема уже выявлена. А как насчет решения, которое не требует набора текста?

Решение: неявные преобразования с приоритетом.

Проблема с неявными преобразованиями заключается в том, что вы, вероятно, не хотите писать неявный def everything_is_optional [A] (a: A) = Некоторые (a) потому что это нарушает вашу систему типов для вариантов (в том, что вы получите повышение без предварительного уведомления). Может быть, вы хотите этого, но лично мне нравится, когда система типов сообщает мне, когда я запутался в том, является ли что-то опцией или нет. Так что нам нужна какая-то другая обертка. Вот так:

// Might want to do this for more than just one class, so generalize
class Implicator[A,B](val value: A) {
  def to[C] = this.asInstanceOf[Implicator[A,C]]
}

class X[A](i: Implicator[Option[A],X[A]]) {
  private val v = i.value
  def getV = v
}
trait LowPriorityX {
  implicit def everything_for_x[A](a: A) = new Implicator(Option(a)).to[X[A]]
}
object X extends LowPriorityX {
  implicit def option_for_x[A](oa: Option[A]) = new Implicator(oa).to[X[A]]
}

Теперь мы можем попробовать это (убедитесь, что вы входите в режим :paste, если вы используете REPL, или вводите все это внутри объекта и импортируете объект, так что object X интерпретируется как объект-компаньон для class X:

scala> new X(5)
res0: X[Int] = X@26473f4c

scala> new X(Some(5))
res1: X[Int] = X@1d944379

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

Я почти уверен, что есть схема кодирования типов, которая также будет работать, но у меня не было времени ее закончить, плюс я потерял энтузиазм, как только заметил, что компилятор настаивает на создании и упаковке неявного используемого для ограничения типов в таких схемах, хотя это необходимо только для проверки типов.

...