Is it possible to match patterns with a common value with a result that matches the type?

Is it possible to match patterns whose result matches an external method type parameter? For instance. Given:

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 } } 

Is there an implementation of Test (its pull method) that uses pattern matching in the key argument and returns Option[A1] for each key tested without using asInstanceOf ?

Some pathetic attempts:

 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)) } } 

Nothing works, obviously ... The only solution is to quit:

 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) 
+4
source share
2 answers

The problem is that pattern matching ignores all erasable types. However, there is a small hidden trick that could be used. The following will retain the type resolution provided by the match for the return type.

 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 ) ) 

This, however, is almost equivalent to simply defining individual subclasses of Key. I believe that the following method is preferable , however, it may not work if the existing code uses Key [String], which you cannot change to the necessary KeyString (or too much work to change).

 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 ) ) 
+1
source

I provide here an extended example (which shows more of my context) based on Neil Essy's closed response type approach:

 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] } 

Then the following script compiles with not too much interference:

 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)) } } 

... and gives the desired results:

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

Now the only thing that I don’t get, and I find ugly, is that my cases should look like

 case _: keyCaseObject.type => 

and cannot be

 case keyCaseObject => 

which I would prefer. Any ideas this limitation comes from?

0
source

Source: https://habr.com/ru/post/1390156/


All Articles