I’ve been using the following code for some time, but I really don’t understand how it works. There is a class NonLocalExit defined within the definition of def block. And there is also a catch { case NonLocalExit within the same function block.
What I don’t understand is how do recrusive calls to block distinguish between all the different classes of the same name. I.e., suppose that body also calls block. The the class named NonLocalExit within that block is supposedly different than the class named NonLocalExit in the outer call to block, right? Are these two different java classes? Are local class definitions supported by java?
If a piece of application code recursively calls block, say 500 times (supposing there is enough stack space), then how many classes named NonLocalExit are created? this must be done at run-time, as the compiler doesn’t know how many need to be created.
in short, I’m confused about how this works. And whether my confusion has lead to bugs in my code which are waiting to show themselves later.
def block[A](body:(A=>Nothing)=>A):A = {
// CL like block/return, the name of the return() function is provided
// by the caller.
// Usage: block{ ret => ... ret(someValue) ...}
// extending Exception with NoStackTrace prevents throwing the
// exception from computing the stacktrace and storing the information.
// We don't need a stacktrace because the purpose of this exception
// is simply to perform a non-local exit.
import scala.util.control.NoStackTrace
class NonLocalExit(val data:A) extends Exception with NoStackTrace {}
def ret(data:A):Nothing = {
throw new NonLocalExit(data)
}
try{
body(ret)
}
catch{
case nonLocalExit: NonLocalExit => nonLocalExit.data
}
}
I can’t comment this at the language spec/compiler implementation level, but to my understanding, barring specific compiler tricks, there will be one class at the bytecode level per class declaration at the source code level. However, at the source code level the compiler will enforce the perception that for inner class declarations, there will be one class per outer instance.
class Foo {
class Bar // only one Foo$Bar.class
def accept(b: Bar): Unit = ()
def acceptAny(o: AnyRef): Unit =
o match {
case _: Bar => println("Bar")
case _ => println("other")
}
}
val f1 = new Foo
val f2 = new Foo
f1.accept(new f1.Bar) // ok
//f1.accept(new f2.Bar) // doesn't compile
f1.acceptAny(new f1.Bar) // "Bar"
f1.acceptAny(new f2.Bar) // "other"
[EDIT: I guess that the synthetic $outer reference is used to distinguish cases in the #acceptAny() dispatch, but I’m not sure.]
With method-local classes in recursive functions, there’s no “anchor” the compiler can use to tie a class reference to a specific recursion level, and pattern matching boils down to reflection in disguise, anyway, so method-local classes basically behave just like inner classes.
def rabbitHole(count: Int): Unit = {
class FooExc extends RuntimeException
try {
if(count <= 0) throw new FooExc else rabbitHole(count - 1)
}
catch {
case e: FooExc =>
println(e.getClass)
throw e
}
}
rabbitHole(2)
Output:
class de.sangamon.scalausers.MemberClassId$FooExc$1
class de.sangamon.scalausers.MemberClassId$FooExc$1
class de.sangamon.scalausers.MemberClassId$FooExc$1
Exception in thread "main" de.sangamon.scalausers.MemberClassId$FooExc$1
There is only one class for a given full name, unless you are using an unconventional a custom classloader, which you don’t. Therefore, the runtime type is independent of which invocation of the method.
One might wonder whether the compile time type is different, but since there is no way to refer to a type related to a different invocation of the same method, you can only ever refer to the type in its current invocation, so there is only one local type.
I’ve fixed the code to add what I hope is a unique ident to the the recursion level. My tests pass.
Nevertheless, I’d be really happy if someone could tell me whether (1) body in the following fixed code is really a unique ident, (2) whether the eq in the if-expression if nonLocalExit.ident eq body is really a pointer comparison of the function object.
I’m not 100% in Scala when I’m dealing with the function object and when I’m calling the function when I use a function name without parens.
def block[A](body:(A=>Nothing)=>A):A = {
// CL like block/return, the name of the return() function is provided
// by the caller.
// Usage: block{ ret => ... ret(someValue) ...}
// extending Exception with NoStackTrace prevents throwing the
// exception from computing the stacktrace and storing the information.
// We don't need a stacktrace because the purpose of this exception
// is simply to perform a non-local exit.
import scala.util.control.NoStackTrace
class NonLocalExit(val data:A,val ident:(A=>Nothing)=>A) extends Exception with NoStackTrace {}
def ret(data:A):Nothing = {
throw new NonLocalExit(data,body)
}
try{
body(ret)
}
catch{
case nonLocalExit: NonLocalExit if nonLocalExit.ident eq body => nonLocalExit.data
}
}
/* package whatever; // don't place package name! */
import java.util.*;
import java.lang.*;
import java.io.*;
/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
class Greeter {
void greet() {
System.out.println(Arrays.toString(args));
}
}
new Greeter().greet();
new Ideone().foo("me, of course");
}
void foo(String who) {
class Bar {
void baz() {
class Abc {
void xyz() {
System.out.println("foo(" + who + ").Bar.baz().Abc.xyz()");
}
}
new Abc().xyz();
}
}
new Bar().baz();
}
}
Result of execution:
[]
foo(me, of course).Bar.baz().Abc.xyz()
I recommend attaching debugger to your program and experimenting with it. You can also write some automated unit tests.
def makeXyzInstance = {
class Xyz
new Xyz
}
def makeFnInstanceA(y: Int) = (x: Int) => x + y
def makeFnInstanceB = (x: Int) => x + 1
val xyz1 = makeXyzInstance
val xyz2 = makeXyzInstance
// false because we have different instances
xyz1 eq xyz2
xyz1 == xyz2
// true because we have the same type
xyz1.getClass eq xyz2.getClass
xyz1.getClass == xyz2.getClass
val fn1a = makeFnInstanceA(3)
val fn2a = makeFnInstanceA(3)
// false because we have different instances
fn1a eq fn2a
fn1a == fn2a
// true because we have the same type
fn1a.getClass eq fn2a.getClass
fn1a.getClass == fn2a.getClass
val fn1b = makeFnInstanceB
val fn2b = makeFnInstanceB
// weird? returns true. maybe Scala turns functions that aren't closures into singletons?
fn1b eq fn2b
fn1b == fn2b
// true because we have the same type
fn1b.getClass eq fn2b.getClass
fn1b.getClass == fn2b.getClass
If x and y are both reference types then x eq y compares the addresses, i.e. compares object identity.
Yes, I see now. But it is shocking that xyz1.getClass eq xyz2.getClass is true. I understand that it’s true, but in my opinion it is the opposite of what one would naturally assume. For example, if a function returns a local function, then from at the call site those functions are not eq. Local functions have local semantics, but local classes do not.
OK now I know, but it’s still strange. The impression is that it is an implementation trade-off/limitation because of the JVM, i.e. a leaky abstraction.
def f1(n:Int):Int=>Int = {
def f2(m:Int):Int = n+m
f2
}
f1(12) eq f1(12) ;; false
def f3(n:Int): Class[_ <: Any] = {
class Xyz(m:Int)
val x = new Xyz(n)
x.getClass
}
f3(12) eq f3(12) ;; true
Runtime classes are way less powerful than compile time types. If you’re going to use runtime reflection that’s something you have to live with. And unfortunately you also have to be aware that runtime type tests (x.isInstanceOf[Y] or case x: Y =>) work with the erased runtime class. Most of the time you’ll get a warning when you do a type test that is unsafe—like on a parameterized type—but not always.
even when there are parameters involved, you can still get true:
scala 2.13.2> def foo(z: Int) = (x: Int) => x * x
def foo(z: Int): Int => Int
scala 2.13.2> foo(3) eq foo(0)
val res8 : Boolean = true
A way to get false is to actually capture a binding:
scala 2.13.2> def foo(z: Int) = (x: Int) => x * z
def foo(z: Int): Int => Int
scala 2.13.2> foo(3) eq foo(0)
val res9: Boolean = false
The impression is that it is an implementation trade-off/limitation because of the JVM, i.e. a leaky abstraction
Disagree. This is good, not bad. These are highly desirable optimizations for the compiler to be allowed to do.
The same goes for your getClass example, for the same reason. You don’t want the compiler to be forced to allocate extra classes and extra instances unless the language semantics demand it. And the language semantics shouldn’t demand it without a very good reason, because if they don’t, the compiler is then free to generate tighter, faster code.
it is the opposite of what one would naturally assume
According to what principle or precedent would one assume that?
I must admit that the fact that the above example of def foo(z: Int) = (x: Int) => x * x is a good argument against my intuition. I guess I expect functions and classes to close over their environment, but for functions it appears to be only the case sometimes, and for classes it appears to be never.