scala: переопределить неявный параметр конструктору

#scala

#scala

Вопрос:

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

 def someMethod()(implicit p: List[Int]) {
  // uses p
}

class A()(implicit x: List[Int]) {

  implicit val other = List(3) // doesn't compile

  def go() { // don't want to put implicit inside here since subclasses that override go() have to duplicate that
    someMethod()
  }
}
  

Поведение, которого я хочу, заключается в том, что someMethod() получает неявный параметр, представляющий собой некоторую измененную версию x, которая была неявным параметром класса. Я хочу иметь возможность либо изменять x, не изменяя его для того, что передало его в конструктор A, либо иным образом переопределять его на новое значение по моему выбору. Похоже, оба подхода не работают. То есть в первом случае список не копируется, и компилятор находит неоднозначное неявное значение для последнего случая. Есть ли способ сделать это?

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

Комментарии:

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

2. @Kevin В моем конкретном случае существуют тысячи подклассов, подобных A, и они создаются тысячи раз. Удобно не передавать один и тот же параметр каждому из них. Почему вы говорите, что это не поддерживается?

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

Ответ №1:

Введите другой тип оболочки, просто чтобы устранить неоднозначность:

 //  badly named, choose something domain-specific
case class ListHolder(theList: List[Int])

def someMethod()(implicit holder: ListHolder) {
  val xs = holder.theList
  // uses xs ...
}

class A()(implicit xs: List[Int]) {

  implicit val other = ListHolder(42 :: xs) // compiles

  def go() {
    // xs is never considered for the implicit param to someMethod()
    // because it's now the wrong type
  }
}
  

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

Ответ №2:

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

 object Example {
  class Implication[A,B](val value: A) {
    def apply[C](c: C) = new Implication[C,B](c)
  }
  object Implication {
    def mark[B] = new Implication[Unit,B](())
    implicit def implication_to_value[A,B](i: Implication[A,B]) = i.value
  }

  trait One {}
  trait Two {}
  implicit val x = Implication.mark[One]("Hello")
  implicit val y = Implication.mark[Two]("Hi")

  def testOne(implicit s: Implication[String,One]) = println(s: String)
  def testTwo(implicit s: Implication[String,Two]) = println(s: String)
  def testThree(s: String) = println("String is "   s)

  def main(args: Array[String]) {
    testOne
    testTwo
    testThree(x)
    testThree(y)
  }
}
  

Который работает так, как вы надеетесь:

 scala> Example.main(Array())
Hello
Hi
String is Hello
String is Hi
  

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

Ответ №3:

Эта модификация компилируется. Я изменил x на var:

 class A()(implicit var x: List[Int]) {

  def someMethod()(implicit p: List[Int]) {
    // uses p
  }

  x = List(3) 

  def go() { // don't want to put implicit inside here since subclasses that override go() have to duplicate that
    someMethod()
  }
}