Для начала стоит отметить, что неявность здесь не является проблемой - если вы напишете что-то вроде следующего, она потерпит неудачу точно таким же образом:
new RegistryOps(items).getById(1).foreach(e => new RegistryOps(items).remove(e))
Естьспособы делать то, что вы хотите сделать, но они не очень приятные.Одним из них будет десагарция неявного класса, чтобы вы могли получить более специфический тип для значения реестра:
class Registry[T](name: String) {
case class Entry(id: Long, value: T)
}
object testOps {
import scala.collection.mutable
type TestStorage[T] = mutable.Map[Long, T]
class RegistryOps[T, R <: Registry[T]](val self: R)(
implicit storage: TestStorage[T]
) {
def getById(id: Long): Option[R#Entry] =
storage.get(id).map(self.Entry(id, _))
def remove(entry: R#Entry): Unit = storage - entry.id
}
implicit def toRegistryOps[T](s: Registry[T])(
implicit storage: TestStorage[T]
): RegistryOps[T, s.type] = new RegistryOps[T, s.type](s)
}
. Это прекрасно работает, как в используемой вами форме, так и слегкаболее точно:
scala> import testOps._
import testOps._
scala> case class Item(name: String)
defined class Item
scala> val items = new Registry[Item]("elems")
items: Registry[Item] = Registry@69c1ea07
scala> implicit val storage: TestStorage[Item] =
| scala.collection.mutable.Map[Long, Item](
| 1L -> Item("whatever")
| )
storage: testOps.TestStorage[Item] = Map(1 -> Item(whatever))
scala> val resultFor1 = items.getById(1)
resultFor1: Option[items.Entry] = Some(Entry(1,Item(whatever)))
scala> resultFor1.foreach(items.remove)
Обратите внимание, что предполагаемый статический тип resultFor1
- это именно то, что вы ожидаете и хотите.В отличие от решения Registry[T]#Entry
, предложенного в комментарии выше, этот подход запрещает вам брать запись из одного реестра и удалять ее из другого с таким же T
.Предположительно, вы сделали Entry
внутренним классом кейсов именно потому, что хотели избежать подобных вещей.Если вас это не волнует, вам действительно нужно просто продвинуть Entry
в свой собственный класс дел верхнего уровня со своим собственным T
.
В качестве примечания, вы можете подумать, что это сработает просто для написанияследующее:
implicit class RegistryOps[T, R <: Registry[T]](val self: R)(
implicit storage: TestStorage[T]
) {
def getById(id: Long): Option[R#Entry] =
storage.get(id).map(self.Entry(id, _))
def remove(entry: R#Entry): Unit = storage - entry.id
}
Но вы будете неправы, потому что синтетический метод неявного преобразования, который компилятор произведет при десугарации неявного класса, будет использовать более широкий R
, чем вам нужно, и вывернуться в ту же ситуацию, что у вас была без R
.Поэтому вы должны написать свой собственный toRegistryOps
и указать s.type
.
(В качестве сноски я должен сказать, что наличие какого-то изменчивого состояния, которое вы передаете неявно, звучит как абсолютный кошмар, иЯ настоятельно рекомендую не делать ничего удаленно, как то, что вы делаете здесь.)