Matching inner case class

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Matching inner case class

Martin Gamwell Dawids
I am having difficulties matching an inner case class.

Please consider this example:

  class Outer {
    case class Inner
 
    def newInner = Inner
 
    def matching(any: Any) {
      any match {

        case Inner => {
          println("Matched pattern Inner")
        }

        case _ : Outer#Inner => {
          println("Matched type Inner")
        }

        case _ => {
          println("Matched Any")
        }

      }
    }

  }

As you can see I try to match the parameter to matching(), called any, against a pattern match, a type match and a default/Any match.

Now, consider this sample code using the class.

  object CaseInner {
    def main(args : Array[String]) : Unit = {
      val o1, o2 = new Outer
      val i1 = o1.newInner
      val i2a = o2.newInner
      val i2b = new o2.Inner
     
      o1 matching i1
      o1 matching i2a
      o1 matching i2b
    }
  }

The first line
  o1 matching i1
results in
  Matched pattern Inner
being printed to the console

The second line
  o1 matching i2a
results in
  Matched Any
being printed to the console

The third line
  o1 matching i2b
results in
  Matched type Inner
being printed to the console

I am a bit confused.
I would (sort of) expect to run into trouble as Inner is an inner class of Outer and matching on the class name alone could (and apparently does) mean instances of the path-dependent type o1.Inner.

But why do the last to line give different results?

So my questions are:
1) Can anybody explain why the last two lines
     o1 matching i2a
     o1 matching i2b
  give different results?

1) Is there a way to express a pattern match that will match all three lines?

Any help is appreciated.

Regards,
Martin
Reply | Threaded
Open this post in threaded view
|

Re: Matching inner case class

Aaron Harnly-2-2
Hi Martin,

On Nov 28, 2009, at 5:06 PM, Martin Gamwell Dawids wrote:
> So my questions are:
> 1) Can anybody explain why the last two lines
>     o1 matching i2a
>     o1 matching i2b
>  give different results?
>
> 1) Is there a way to express a pattern match that will match all three
> lines?

Case classes with no parameters can behave in subtly tricky ways, and are deprecated. (Produces a warning on my 2.7.7 compile).
Adding a type annotation to your newInner method helps explain what’s happening. I think you expected that this would compile:

   def newInner: Inner = Inner

But that gives a type error. Instead, the correct annotation is:

   def newInner: Inner.type = Inner

Your method was returning the companion object of the case class, not an instance of the case class.
So i1 and i2a were references to the companion object for o1 — which is why i1 matched “case Inner”, while i2a fell through to match Any.
Meanwhile i2b was an instance of o2.Inner, which therefore matched the type.

The straightforward resolution is to add a zero-parameter list to the case class, and fix up the first pattern:

   case class Inner()
   def newInner = Inner()
   ...
       case Inner() => {…}

which produces what I think was your intended result:

Matched pattern Inner
Matched type Inner
Matched type Inner

The type pattern matches all three lines, but was superseded in the first case by the more-specific match to an instance of o1’s Inner.

Hope that helps!
~aaron


Reply | Threaded
Open this post in threaded view
|

Re: Matching inner case class

Martin Gamwell Dawids
Aaron Harnly-2 wrote
Case classes with no parameters can behave in subtly tricky ways, and are deprecated. (Produces a warning on my 2.7.7 compile).

<...snip...>
Your method was returning the companion object of the case class, not an instance of the case class.
So i1 and i2a were references to the companion object for o1 — which is why i1 matched “case Inner”, while i2a fell through to match Any.
Meanwhile i2b was an instance of o2.Inner, which therefore matched the type.
That makes sense, thank you.

So now to what originally puzzled me.

Consider the code rewritten as this: (changes marked with "--->")

      object CaseInner {
        def main(args : Array[String]) : Unit = {
            val o1, o2 = new Outer
            val i1 = o1.newInner
            val i2a = o2.newInner
--->        val i2b = new o2.Inner(Contents(87))

            o1 matching i1
            o1 matching i2a
            o1 matching i2b
        }
      }

--->  case class Contents(i: Int)

      class Outer {
--->    case class Inner(contents: Contents)

--->    def newInner = Inner(Contents(42))

        def matching(any: Any) {
          any match {
--->        case Inner(Contents(i)) => {
--->          println("Matched pattern Inner(i), and i = " + i)
            }

            case inner : Outer#Inner => {
--->          println("Matched type Outer#Inner, and i = " + inner.contents.i)
            }

            case _ => {
              println("Matched Any")
            }
          }
        }
      }


The difference is that Inner now takes one parameter which is itself a case class. Using a pattern match I can easily extract the innermost integer:

      case Inner(Contents(i)) => {
        println("Matched pattern Inner(i), and i = " + i)
      }

However, as I have to resort to the type match to find other Outer's instances of Inner(), it becomes a bit less beautiful:

      case inner : Outer#Inner => {
        println("Matched type Outer#Inner, and i = " + inner.contents.i)
      }

Rather than getting 'i' matched directly, I have to write 'inner.contents.i'.

The question:
Is there a way to express a pattern match that is not restricted to the path-dependent type Outer.Inner but also matches other Outer's Inner instances (so I do not have to resort to a type match when dealing with case class.)

Maybe something like
  case Outer#Inner(i)
  case _#Inner(i)
  case _.Inner(i)
but this is not allowed.

Any help is appreciated.

Regards,
Martin
Reply | Threaded
Open this post in threaded view
|

Re: Matching inner case class

Martin Gamwell Dawids
Martin Gamwell Dawids wrote
Maybe something like
  case Outer#Inner(i)
  case _#Inner(i)
  case _.Inner(i)
but this is not allowed.
Sorry, I meant:

Maybe something like
  case Outer#Inner(Contents(i))
  case _#Inner(Contents(i))
  case _.Inner(Contents(i))
but this is not allowed.

Regards,
Martin
Reply | Threaded
Open this post in threaded view
|

Re: Matching inner case class

Aaron Harnly-2-2
In reply to this post by Martin Gamwell Dawids
Hi Martin,

> The question:
> Is there a way to express a pattern match that is not restricted to the
> path-dependent type Outer.Inner but also matches other Outer's Inner
> instances (so I do not have to resort to a type match when dealing with case
> class.)

I guess the natural follow-up question is: to what end? What’s your use case for having an inner class that you want to extract from as though it is not an inner class?

Clearly either of these two things would work:
1. Make Inner not be an inner class.
2. When matching an o2.Inner, invoke the method on o2, not o1.

I can certainly imagine you might have a good reason for wanting neither #1 nor #2, but maybe a little more context would help illuminate what that is.

cheers,
~aaron



Reply | Threaded
Open this post in threaded view
|

Re: Matching inner case class

Martin Gamwell Dawids
Hi Aaron,

Aaron Harnly-2 wrote
I guess the natural follow-up question is: to what end? What’s your use case for having an inner class that you want to extract from as though it is not an inner class?
The actual example was that I was playing around with actors. I have two actor instances of the same class exchanging messages. The code could look like this:

  case class Message(message: String)

  class Exchanger extends Actor {
    private case class ExchangedMessage(message: Message)

    def otherEnd : Exchanger    

    def act() {
      loop {
        react {
          case Message(message) => {
             // received local message, send message to other end
             otherEnd ! ExchangedMessage(message)
          }
          case ExchangedMessage(message) => {
             // received message from other end, dispatch to someone locally
             receiver ! message
          }

          ...
        }
      }
    }
  }

The idea was to make the inner case class ExchangedMessage invisible outside the Exchanger class.
Maybe I am thinking too much in terms of Java static inner classes.

I could place ExchangedMessage outside the Exchanger class but then it is visible throughout the entire file.
Maybe I could put it inside a local package making it private in that package.

Any other suggestions for how to write this in a Scala way?


And... (for the original question)
Is there a way to match the inner case class disregarding its path-dependent type?

Thank you for your help so far.

/Martin