1

I have a scenario where I have some objects that need to take in references from each other. The only way I can get this to compile is to use lazy

class A(b:B)
class B(a:A)
lazy val a:A = new A(b)
lazy val b:B = new B(a)

I can do the same thing using some actors, and get it to compile also

    abstract class Message
    case class Message1 extends Message
    case class Message2 extends Message

    class Actor1(otherActor:Actor) extends Actor {
        def act() {
            loop {
                react {
                    case Message1 =>
                        println("received message1")
                        otherActor ! Message2
                    case _ =>
                }
            }
        }
    }

    class Actor2(otherActor:Actor) extends Actor {
        def act() {
            loop {
                react {
                    case Message2 =>
                        println("received message2")
                        otherActor ! Message1
                    case _ =>
                }
            }
        }
    }

    lazy val actor1:Actor = new Actor1(actor2)
    lazy val actor2:Actor = new Actor2(actor1)

However, when I add the following:

    actor1.start
    actor2.start
    actor1 ! Message1

I get the following error:

Exception in thread "main" java.lang.NoClassDefFoundError: com/fictitiousCompany/stackOverflowQuestion/Test Caused by: java.lang.ClassNotFoundException: com.fictitiousCompany.stackOverflowQuestion.Test at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:248)

I'm using the Scala Eclipse Plugin 2.8.1.

Bruce Ferguson
  • 1,851
  • 2
  • 16
  • 21
  • I suspect there is a separate issue about the classpath not being set correctly by Eclipse. You may want to try command line `scala -cp classes com.fictitious.stackOverflowQuestion.Test` where classes points to the generated class files. – huynhjl Jan 19 '11 at 04:02
  • It's just strange that I would be able write some relatively complex code prior to this without any issues using Eclipse. My other code still runs. Maybe it's an Eclipse plugin bug...? – Bruce Ferguson Jan 19 '11 at 04:24
  • I will have to deal with this issue for whole lists of codependent objects later today. – Raphael Jan 19 '11 at 06:53
  • @Raphael. Cool. I actually have a list of codependent objects, but boiled it down to this simplest case. Looking forward to seeing what you come up with. – Bruce Ferguson Jan 19 '11 at 23:11
  • Solved it; is actually an more or less simple generalisation of huynhjl's answer. Does not fit here, though. – Raphael Jan 20 '11 at 10:09

1 Answers1

11

Note that even your smaller example would have issues (in the REPL):

{
class A(b:B)
class B(a:A)
lazy val a:A = new A(b)
lazy val b:B = new B(a)
a
}
// causes stack overflow error

As soon as a needs to be evaluated therefore constructed, it would require B, which requires A. In order for this to work a or b would have to finish being constructed.

Using by-name parameters allows the smaller example to evaluate.

{
class A(b: => B)
class B(a: => A)
lazy val a:A = new A(b)
lazy val b:B = new B(a)
a
}

Note sure if that'll work for your actor example as well.

Edit: by name params worked locally on 2.8.0. I replaced case class with object to get rid of some deprecation warnings and added start methods on actor1, actor2 and kick the whole thing with actor1 ! Message1. Aside from this I haven't used actor before, so I can't comment more. Here is what I tested:

import scala.actors._

abstract class Message
object Message1 extends Message
object Message2 extends Message

class Actor1(otherActor: => Actor) extends Actor {
def act() {
    loop {
    react {
        case Message1 =>
        println("received message1")
        otherActor ! Message2
        case _ =>
    }
    }
}
}

class Actor2(otherActor: => Actor) extends Actor {
def act() {
    loop {
    react {
        case Message2 =>
        println("received message2")
        otherActor ! Message1
        case _ =>
    }
    }
}
}

{
  lazy val actor1:Actor = new Actor1(actor2)
  lazy val actor2:Actor = new Actor2(actor1)
  actor1.start
  actor2.start
  actor1 ! Message1
}

Prints a bunch of:

received message1
received message2
huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Thanks for the tip. The actor example still has the same issue – Bruce Ferguson Jan 19 '11 at 03:35
  • 1
    If by edit you're referring to the "by-name" parameter construct `otherActor: => Actor`, this is because the parameter is not evaluated when the method is called. Instead the expression is saved by the compiler and every time you use `otherActor` in Actor1 or Actor2, the expression is evaluated, then. So when you call `new Actor1(actor2)`, the `actor2` expression is not evaluated at that point and that allows the object to be constructed. by-name parameters are usually used to create custom control abstractions like loops. – huynhjl Jan 19 '11 at 07:39
  • Most likely not in a good way. There is likely to be at least one indirection. Also, you have to be careful if you use something like `new Actor1(new Actor2)` as a new object would be created every time which is not what you're after. I think there's gotta be a better design pattern than passing referenced actors in the constructor. – huynhjl Jan 21 '11 at 03:40
  • @ huynhjl. One thing I've realized with this approach is that object Message1 can't take arguments, which is an issue for the real-world problem I'm trying to solve. Any ideas? – Bruce Ferguson Jan 21 '11 at 05:40
  • @huynhjl. Yes, you are correct, there is a better design involving an actor factory – Bruce Ferguson Feb 06 '11 at 14:21