Как объявить черты как принимающие неявные «параметры конструктора»? - PullRequest
34 голосов
/ 08 августа 2011

Я разрабатываю иерархию классов, которая состоит из базового класса и нескольких признаков.Базовый класс обеспечивает реализации по умолчанию нескольких методов, и признаки выборочно переопределяют определенные методы через abstract override, чтобы действовать как составные черты / mixins.

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

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

Суть проблемы заключается в том, что я не могу предоставить аргументы конструктора для свойства, чтобы они могли быть помечены как неявные.Ссылка на неявный параметр в реализации метода не компилируется с ожидаемым сообщением «не удалось найти неявное значение»;Я пытался «распространить» неявное с этапа конструирования (где на практике оно всегда находится в области видимости) до того, чтобы оно было доступно внутри метода через

implicit val e = implicitly[ClassName]

, но (как, несомненно, многие из вас ожидают) что определение не удалось с тем же сообщением.

Кажется, что проблема здесь в том, что я не могу убедить компилятор пометить сигнатуру самой черты флагом implicit ClassName, и заставитьвызывающие (то есть те, кто смешивает черту в объект), чтобы обеспечить неявное.В настоящее время мои абоненты делают это, но компилятор не проверяет на этом уровне.


Есть ли способ пометить черту как требующую определенных последствий, чтобы быть доступнымиво время строительства?

(А если нет, то это просто еще не реализовано или есть более глубокая причина, почему это нецелесообразно?)

Ответы [ 5 ]

16 голосов
/ 08 августа 2011

На самом деле, я хотел этого довольно часто раньше, но просто придумал эту идею. Вы можете перевести

trait T(implicit impl: ClassName) {
  def foo = ... // using impl here
}

до [РЕДАКТИРОВАНИЕ: оригинальная версия не предоставляла доступ к неявным для других методов]

trait T {
  // no need to ever use it outside T
  protected case class ClassNameW(implicit val wrapped: ClassName)

  // normally defined by caller as val implWrap = ClassNameW 
  protected val implWrap: ClassNameW 

  // will have to repeat this when you extend T and need access to the implicit
  import implWrap.wrapped

  def foo = ... // using wrapped here
}
12 голосов
/ 12 мая 2015

Это невозможно.

Но вы можете использовать implicitly и вывод типа Scala, чтобы сделать это как можно более безболезненным.

trait MyTrait {

    protected[this] implicit def e: ClassName

}

, а затем

class MyClass extends MyTrait {

    protected[this] val e = implicitly // or def

}

Сжато, и недаже не требуется писать тип в расширяющем классе.

11 голосов
/ 09 августа 2011

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

Следовательно, вы должны просто иметь объявление абстрактного значения в признаке, так что реализующие классы должны предоставитьскрытое для вас.См. Следующий пример, который правильно компилируется и показывает два способа реализации данной черты:

trait Base[T] {
    val numT: Ordering[T]
}
/* Here we use a context bound, thus cannot specify the name of the implicit
 * and must define the field explicitly.
 */
class Der1[T: Ordering] extends Base[T] {
    val numT = implicitly[Ordering[T]]
    //Type inference cannot figure out the type parameter of implicitly in the previous line
}
/* Here we specify an implicit parameter, but add val, so that it automatically
 * implements the abstract value of the superclass.
 */
class Der2[T](implicit val numT: Ordering[T]) extends Base[T]

Основная идея, которую я показываю, также присутствует в ответе Кнута Арне Ведаа, но я попытался сделать более убедительныйи удобный пример, отбрасывающий использование ненужных функций.

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

0 голосов
/ 17 августа 2011

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

Если у кого-то есть лучшее решение, я 'Буду рад услышать и принять это.

0 голосов
/ 08 августа 2011

Вы можете сделать это так:

abstract class C

trait A { this: C =>
    val i: Int
}    

implicit val n = 3

val a = new C with A {
    val i = implicitly[Int]
}

Но я не уверен, есть ли в этом какой-то смысл - вы также можете явно ссылаться на неявное значение.

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

Возможное решение этой проблемы - добавить новую функцию к уже действующему синтаксису:

trait A {
    implicit val i: Int
}

, где i будет реализован компилятором, если неявный объект находится в области видимости.

...