Reflection API giving me the same information as `clojure.reflect/type-reflect`

I have a several functions implemented in Clojure which do some reasoning about java classes at run-time. I’m interested in implementing the same thing in Scala, but I don’t know how.

QUESTION: Is there something in Scala similar to the type-reflect function in Clojure? If not, are there ways of getting the same pieces of information using several other functions?

The fundamental function provided by the Clojure system which I’m using is a function named clojure.reflect/type-reflect which takes a class (i.e., an object whose class is java.lang.Class) and it returns several useful pieces of information. See the example below.
The two pieces of information I need are (1) the :flags field which tells me whether the class is public, abstract, interface, final, or some combination of those. Eg., java.lang.Number is public and abstract. and (2) :member gives me a list of methods and other things. and each method has :name and :parameter-types.

One application I use this for is given two classes which are interfaces, I examine the methods to see whether they both have a method of the same name with the same parameter-types. If so, then I know the interfaces are NOT compatible, i.e., it is impossible that there be class which inherits from both interfaces.

(clojure.reflect/type-reflect java.lang.Number)
  ==>
{:bases #{java.lang.Object java.io.Serializable},
 :flags #{:public :abstract},
 :members
 #{{:name byteValue,
    :return-type byte,
    :declaring-class java.lang.Number,
    :parameter-types [],
    :exception-types [],
    :flags #{:public}}
   {:name java.lang.Number,
    :declaring-class java.lang.Number,
    :parameter-types [],
    :exception-types [],
    :flags #{:public}}
   {:name floatValue,
    :return-type float,
    :declaring-class java.lang.Number,
    :parameter-types [],
    :exception-types [],
    :flags #{:public :abstract}}
   {:name longValue,
    :return-type long,
    :declaring-class java.lang.Number,
    :parameter-types [],
    :exception-types [],
    :flags #{:public :abstract}}
   {:name shortValue,
    :return-type short,
    :declaring-class java.lang.Number,
    :parameter-types [],
    :exception-types [],
    :flags #{:public}}
   {:name serialVersionUID,
    :type long,
    :declaring-class java.lang.Number,
    :flags #{:private :static :final}}
   {:name intValue,
    :return-type int,
    :declaring-class java.lang.Number,
    :parameter-types [],
    :exception-types [],
    :flags #{:public :abstract}}
   {:name doubleValue,
    :return-type double,
    :declaring-class java.lang.Number,
    :parameter-types [],
    :exception-types [],
    :flags #{:public :abstract}}}}

Yes and no – yes, you can get that sort of info; no, it’s mostly not Scala.

For this sort of thing, the information is all coming from the JVM, so you generally just drop down to the underlying Java data structure, Class.

If you know explicitly what type you are looking for at code time (less commonly useful), you can get that by simply saying classOf[MyClass].

If you need it as a parameter, you use the ClassTag implicit to fetch it (not tested, and it’s early in the morning, so please forgive if there is a mistake):

def thingy[T](t: T)(implicit tag: ClassTag[T]) = {
  val classOfT: Class[_] = tag.runtimeClass
}

Either way, the Class is basically the main underlying data structure at the JVM level, and has more or less all of the available runtime information. I suspect the Clojure type-reflect is mostly using that under the hood.

Note that there is also a TypeTag concept in Scala, found in an external library. That’s a bit more complicated to use, but provides access to the compile-time type information, which is significantly richer than the erased runtime information. It doesn’t sound like you need that level of power, in which case I recommend not worrying about it, but it is sometimes the right tool for the job.

I don’t know about Clojure, but for Scala (and Java)…

trait A {
  def foo(s: String): Int
}

trait B {
  def foo(s: String): Int
}

class Foo extends A with B {
  override def foo(s: String): Int = s.length
}

@sangamon, that’s really interesting information. Every time I think I understand this problem, I find out something new. For interfaces in java (apparently) it will fail.
Do scala traits correspond to java classes, or is the scala compiler creating on-the-fly classes when it tries to create an instance of Foo?

looks like the Class object has a method called getMethods() which takes no arguments, and returns a collection of method objects. Not sure, yet, whether this is all the methods including the inherited ones, or whether I need to walk up the ancestors and collect them myself.

also it seems the Class also has a method isInterface() which tells me whether the class is an interface, but it is not immediately clear yet how to tell whether it is public, or abstract, or final.

Works for me:

interface A {
    int foo(String s);
}

interface B {
    int foo(String s);
}

class Foo implements A, B {
    @Override
    public int foo(String s) {
        return s.length();
    }
}

again, you seem to be right. what if the foo methods have different return types? is at least that an error?

under which conditions will the compiler prohibit me from inheriting from two interfaces? Does it depend on the java version? Looks like my simplistic understanding is wrong.

It depends. :slight_smile: Method return types can be covariant (in both Java and Scala). So…

trait A {
  def foo(s: String): AnyRef
}

trait B {
  def foo(s: String): String
}

class Foo extends A with B {
  override def foo(s: String): String = s
}

Furthermore, reflection produces information from the bytecode level. And there you may even have two methods with the same name and parameter types but different return types in the same class file.

Reflection is cumbersome and error-prone in Java already and should be avoided wherever possible. With Scala, the mapping from source code constructs to bytecode representation becomes orders of magnitude more fuzzy and complicated, and I have no idea how well documented and stable it is between Scala versions. Whatever you’re trying to accomplish, this doesn’t sound like a promising approach.

Perhaps you can state your actual use case? Not the solution trajectory you have in mind, but tracing even further back, the actual pain point you’d like to attack?

3 Likes

From Class, you can get the Methods. Method has getModifiers(), which returns the Modifiers, and those values tell you this sort of thing. (Although, as @sangamon is pointing out, this is the JVM runtime’s view of things, and doesn’t always map precisely to the Scala-language view, since the semantics of the two languages are a bit different.)

1 Like

I think he is looking for Class.getModifiers.

1 Like

The use case is a bit complicated to explain. But let me give it a try.

Given two objects, c1 and c2, of type java.lang.Class. Define two sets (sets in the mathematic sense not in the scala immutable collections sense) A and B. A is the set of all objects x for which c1.isInstance(x) is true. This means that x has class c1 or a class which is an ancestor of c1. Similarly set B is the set of all object x for which c2.isInstance(x) is true.

I’m trying to know in which cases I can conclude/deduce that A and B are disjoint, and in which cases A is a subset of B.

In the case that c1.isAssignableFrom(c2) is true, then B is a subset of A.

In the case that c1 and c2 are both final classes which are not the same, then A and B are disjoint.

In the case that c1 is an interface which has an incompatible method with c2, then A and B are disjoint.

In the case that c1 and c2 are both interfaces, with all compatible methods, and neither of which includes the other in its ancestors list, then although there may not be currently a class which includes both in its ancestors list, it is possible to load a new class (at run time) which does include them both in the ancestors list. Therefore we CANNOT conclude that A and B are disjoint.

My Clojure code is here. It should be pretty understandable, even for those not familiar with Clojure, because it is reasoning about the Java VM objects, not about anything Clojure specific.

The method returns true, false, or :dont-know.

(defmethod -disjoint? :classes [t1 t2]
  (if (and (class-designator? t1)
           (class-designator? t2))
    (let [c1 (find-class t1)
          c2 (find-class t2)]
      (cond (= c1 c2)
            false

            (isa? c1 c2)
            false

            (isa? c2 c1)
            false

            :else
            (let [ct1 (class-primary-flag t1)
                  ct2 (class-primary-flag t2)]
              (cond
                (or (= :final ct1)
                    (= :final ct2))
                true ;; we have already checked isa? above

                (or (= :interface ct1)
                    (= :interface ct2))
                (not (compatible-members? c1 c2))

                :else
                true))))
    
    :dont-know))

I have the impression that there is no such thing as incompatible methods: you could spin up a JVM class that has such members even if there is no construct in Java or scala that would have such a class as implementation.

I think you may not conclude the sets are disjoint if either is an interface and the other is not final.

so clearly if either is final, the question of disjointness can be answered.
And if both are abstract, it is answerable.

But other questions of interface is perhaps unanserable.

Hi @martijnhoekstra and @sangamon , my student reported that this configuration of java classes refuses to compile because of conflicting methods from interfaces A, B and D.
Here is the code. Does this compile for you, or do you get compilation errors?

If it works for you and not for us, perhaps it depends on the environment somehow. If it fails for you, perhaps we can determine some condition of incompatible interfaces from the example?

Without looking at the code (minimal inline code would be more convenient than a zip download), I fully trust your student’s report. If I switch the return type of A#foo() from AnyRef to String in my previous Scala example, the compiler complains - there’s no covariance relation between String and Int. An equivalent change will break the Java example, as well.

I don’t think it makes sense to derive the underlying rules from anecdotal evidence and trial-and-error, though. I’d rather go to the corresponding language spec or related documentation and extract this information from there - I’m not sure this is going to be straightforward, though.

But that’s only for one language. You can mix jars/class files compiled with arbitrary JVM languages (or even handcrafted class files). So when using reflection, you’ll probably have to consider the rules at bytecode level, which may be more lenient than the various source-level rules - I think that’s what @martijnhoekstra was hinting at.

And then there’s the problem of the mapping between source level language constructs and bytecode. For Java, this is rather straightforward, but still not trivial. For Scala, this certainly gets more convoluted, and I have no idea about Clojure, JRuby,…

Sounds like a tedious piece of work, against a moving target, with no guarantee of a satisfying result, for which I still can’t grasp the underlying motivation. If I really wanted to solve this for some specific JVM-based language, I’d certainly try to avoid reflection like the plague and probably investigate repurposing the language’s compiler infrastructure somehow. Just my 2 cents…

2 Likes

So is it the case that the Java programming language enforces one set of semantics on classes, but the JVM enforces a weaker set of semantics? Is this what allows Scala and other JVM languages to re-interpret the class semantics w.r.t the Java programming language ?

Yes, but I’d have to research the specifics myself. As already mentioned, in bytecode a class can have multiple methods with the same name/parameter types and different return types, for example. (Not sure whether this allows arbitrary types or covariant types only.)

It’s probably a mix between more generic/lenient constraints at the bytecode level and the compiler mapping source level constructs to more elaborate/complicated clusters of bytecode constructs. Both result in a discrepancy between source level features and the view at reflection level.

2 Likes

It looks like I can figure out these attributes as follows.

    val FINAL = 0x0010
    val INTERFACE = 0x0200
    val ABSTRACT = 0x0400
    val PUBLIC = 0x0001
    def isFinal(cl:Class[_]):Boolean = {
      0 != (cl.getModifiers() & FINAL)
    }
    def isInterface(cl:Class[_]):Boolean = {
      0 != (cl.getModifiers() & INTERFACE)
    }
...

Constants and predicates are provided in Modifiers.

1 Like