Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Clojure by

Looking at the new Functional Interface support, I noticed that the check for using a Java lambda was being emitted even when the compiler should know that the expression already supports the Functional Interface (e.g., when the expression is an appropriate reify instance).

To avoid unneeded checks, I changed maybeEmitFIAdapter in Compiler.java to add the following compile-time test. With the change Clojure still builds cleanly and passes all tests. I'm no compiler or ASM expert, so this may not be the best way to do this, but I think something like it should be considered:

		// if(exp instanceof IFn) { emitInvokeDynamic(targetMethod, fnInvokerMethod) }
		expr.emit(C.EXPRESSION, objx, gen);
		// FOLLOWING TEST ADDED:
		if (!(expr.hasJavaClass() && targetClass.isAssignableFrom(expr.getJavaClass()))) {
			gen.dup();
			gen.instanceOf(ifnType);

			// if not instanceof IFn, go to end
			Label endLabel = gen.newLabel();
			gen.ifZCmp(Opcodes.IFEQ, endLabel);

			// else adapt fn invoker method as impl for target method
			emitInvokeDynamicAdapter(gen, targetClass, targetMethod, FnInvokers.class, fnInvokerMethod);

			// end - checkcast that we have the target FI type
			gen.mark(endLabel);
		}
		gen.checkCast(samType);

2 Answers

+2 votes
by
selected by
 
Best answer

Yes, believe me, this is something that we have been discussing for a LONG time, and it has been implemented with every variation at some point.

The big thing is that the compiler does not actually know the type of the expression, it only knows what type is being reported, and that can come from hint type flow which may not actually match the later runtime reality.

A concrete example:

(defn remove [^Collection coll ^Predicate pred]
  (.removeIf coll pred)))

Here, the call to removeIf takes a Predicate and expression type of the pred local will be Predicate. But Clojure treats hints as hints, not a static type signature, and you might pass an actual Predicate, or you might pass an IFn. Having the type check in the emitted code allows both to work transparently.

More subtlely, you might even have provided the type hint just on the pred argument: (.removeIf coll ^Predicate pred) to resolve an overload (not in this case, but there are some like this) even though you know pred is not a Predicate and FI adapting is what you want. One possible way out of the overload case is to use the new param-tags metadata to indicate the preferred overload (^[_ Predicate] Collection/.removeIf coll pred) but that's getting really clever to understand and apply.

We also looked at different behavior for the invocation and let-binding cases to maybe do this for let, but not for invocation. In the end, the only safe thing to do is to actually check at runtime.

The next question is the cost of that check, and in general in my perf testing, I can't find a detectable performance cost to this - you are usually passing functions to something else that is the real cost driver (passing a Predicate to a .filter or a Function to a .map or something like that). Especially on newer JVMs and CPUs, this is all going to optimized and correctly predicted if you happen to be in a loop.

–1 vote
by
by
Is a totally different thing actually.
...