Можно ли выполнить сопоставление с образцом общего значения с результатом, соответствующим типу? - PullRequest
4 голосов
/ 10 января 2012

Можно ли выполнить сопоставление с шаблоном, результат которого соответствует параметру типа внешнего метода? Например. Дано:

trait Key[A] {
  def id: Int
  def unapply(k: Key[_]): Boolean = k.id == id // used for Fail2
  def apply(thunk: => A): A = thunk // used for Fail3
}

trait Ev[A] {
  def pull[A1 <: A](key: Key[A1]): Option[A1]
}

trait Test extends Ev[AnyRef] {
  val key1 = new Key[String] { def id = 1 }
  val key2 = new Key[Symbol] { def id = 2 }
}

Существует ли реализация Test (метод pull), который использует сопоставление с шаблоном в аргументе key и возвращает Option[A1] для каждого проверенного ключа, без использования asInstanceOf?

Некоторые жалкие попытки:

class Fails1 extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case `key1` => Some("hallo")
    case `key2` => Some('welt)
  }
}

class Fails2 extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case key1() => Some("hallo")
    case key2() => Some('welt)
  }
}

class Fails3 extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case k @ key1() => Some(k("hallo"))
    case k @ key2() => Some(k('welt))
  }
}

Ничего не работает, очевидно ... Единственное решение - разыграть:

class Ugly extends Test {
  def pull[A1 <: AnyRef](key: Key[A1]): Option[A1] = key match {
    case `key1` => Some("hallo".asInstanceOf[A1])
    case `key2` => Some('welt  .asInstanceOf[A1])
  }
}

val u = new Ugly
u.pull(u.key1)
u.pull(u.key2)

Ответы [ 2 ]

1 голос
/ 10 января 2012

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

abstract class UnErased[A]
implicit case object UnErasedString extends UnErased[String]
implicit case object UnErasedSymbol extends UnErased[Symbol]

class UnErasedTest extends Test {
  def pull[ A1 <: AnyRef ]( key: Key[ A1 ])(implicit unErased: UnErased[A1]): Option[ A1 ] = unErased match {
    case UnErasedString if key1.id == key.id => Some( "hallo" )
    case UnErasedSymbol if key2.id == key.id => Some( 'welt )
    case _ => None
  }
}

val u = new UnErasedTest 
println( u.pull( u.key1 ) )
println( u.pull( u.key2 ) )

Однако это почти эквивалентно определению отдельных подклассов Key. Мне кажется, что следующий метод предпочтительнее , однако он может не работать, если в существующем коде используется Key [String], который нельзя заменить на необходимую KeyString (или слишком много работы для изменения).

trait KeyString extends Key[String]
trait KeySymbol extends Key[Symbol]

trait Test extends Ev[ AnyRef ] {
   val key1 = new KeyString { def id = 1 }
   val key2 = new KeySymbol { def id = 2 }
}

class SubTest extends Test {
  def pull[ A1 <: AnyRef ]( key: Key[ A1 ]): Option[ A1 ] = key match {
    case k: KeyString if key1.id == k.id => Some( "hallo" )
    case k: KeySymbol if key2.id == k.id => Some( 'welt )
    case _ => None
  }
}

val s = new SubTest
println( s.pull( s.key1 ) )
println( s.pull( s.key2 ) )
0 голосов
/ 10 января 2012

Я привожу здесь расширенный пример (который показывает больше моего контекста) на основе подхода закрытых типов ответа Нила Эсси:

trait KeyLike { def id: Int }

trait DispatchCompanion {
  private var cnt = 0
  sealed trait Value
  sealed trait Key[V <: Value] extends KeyLike {
    val id = cnt  // automatic incremental ids
    cnt += 1
  }
}

trait Event[V] {
  def apply(): Option[V] // simple imperative invocation for testing
}

class EventImpl[D <: DispatchCompanion, V <: D#Value](
  disp: Dispatch[D], key: D#Key[V]) extends Event[V] {

  def apply(): Option[V] = disp.pull(key)
}

trait Dispatch[D <: DispatchCompanion] {
  // factory method for events
  protected def event[V <: D#Value](key: D#Key[V]): Event[V] =
    new EventImpl[D, V](this, key)

  def pull[V <: D#Value](key: D#Key[V]): Option[V]
}

Затем следующий сценарий компилируется с не слишком большим количеством беспорядка:

object Test extends DispatchCompanion {
  case class Renamed(before: String, now: String) extends Value
  case class Moved  (before: Int   , now: Int   ) extends Value
  private case object renamedKey extends Key[Renamed]
  private case object movedKey   extends Key[Moved  ]
}
class Test extends Dispatch[Test.type] {
  import Test._

  val renamed = event(renamedKey)
  val moved   = event(movedKey  )

  // some dummy propagation for testing
  protected def pullRenamed: (String, String) = ("doesn't", "matter")
  protected def pullMoved  : (Int   , Int   ) = (3, 4)

  def pull[V <: Value](key: Key[V]): Option[V] = key match {
    case _: renamedKey.type => val p = pullRenamed; Some(Renamed(p._1, p._2))
    case _: movedKey.type   => val p = pullMoved;   Some(Moved(  p._1, p._2))
  }
}

... и дает желаемые результаты:

val t = new Test
t.renamed()
t.moved()

Теперь единственное, что я не понимаю и нахожу уродливым, это то, что мои дела должны иметь форму

case _: keyCaseObject.type =>

и не может быть

case keyCaseObject =>

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

...