Подтвердить что ты не робот

Как объявить черты как неявные "параметры конструктора"?

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

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

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

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

implicit val e = implicitly[ClassName]

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

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


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

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

4b9b3361

Ответ 1

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

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

to [EDITED: оригинальная версия не предоставила доступ к неявным для других методов]

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
}

Ответ 2

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

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

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]

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

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

Ответ 3

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

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

trait MyTrait {

    protected[this] implicit def e: ClassName

}

а затем

class MyClass extends MyTrait {

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

}

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

Ответ 4

Вы можете сделать это следующим образом:

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 будет реализован компилятором, если неявный был в области видимости.

Ответ 5

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

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