Most Efficient Way to Test Equality of Lambda Expressions

Most efficient way to test equality of lambda expressions

Hmmm... I guess you'd have to parse the tree, checking the node-type and member of each. I'll knock up an example...

using System;
using System.Linq.Expressions;
class Test {
public string Foo { get; set; }
public string Bar { get; set; }
static void Main()
{
bool test1 = FuncTest<Test>.FuncEqual(x => x.Bar, y => y.Bar),
test2 = FuncTest<Test>.FuncEqual(x => x.Foo, y => y.Bar);
}

}
// this only exists to make it easier to call, i.e. so that I can use FuncTest<T> with
// generic-type-inference; if you use the doubly-generic method, you need to specify
// both arguments, which is a pain...
static class FuncTest<TSource>
{
public static bool FuncEqual<TValue>(
Expression<Func<TSource, TValue>> x,
Expression<Func<TSource, TValue>> y)
{
return FuncTest.FuncEqual<TSource, TValue>(x, y);
}
}
static class FuncTest {
public static bool FuncEqual<TSource, TValue>(
Expression<Func<TSource,TValue>> x,
Expression<Func<TSource,TValue>> y)
{
return ExpressionEqual(x, y);
}
private static bool ExpressionEqual(Expression x, Expression y)
{
// deal with the simple cases first...
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if ( x.NodeType != y.NodeType
|| x.Type != y.Type ) return false;

switch (x.NodeType)
{
case ExpressionType.Lambda:
return ExpressionEqual(((LambdaExpression)x).Body, ((LambdaExpression)y).Body);
case ExpressionType.MemberAccess:
MemberExpression mex = (MemberExpression)x, mey = (MemberExpression)y;
return mex.Member == mey.Member; // should really test down-stream expression
default:
throw new NotImplementedException(x.NodeType.ToString());
}
}
}

Is there a way to compare lambdas?

This question could be interpreted relative to the specification or the implementation. Obviously, implementations could change, but you might be willing to rewrite your code when that happens, so I'll answer at both.

It also depends on what you want to do. Are you looking to optimize, or are you looking for ironclad guarantees that two instances are (or are not) the same function? (If the latter, you're going to find yourself at odds with computational physics, in that even problems as simple as asking whether two functions compute the same thing are undecidable.)

From a specification perspective, the language spec promises only that the result of evaluating (not invoking) a lambda expression is an instance of a class implementing the target functional interface. It makes no promises about the identity, or degree of aliasing, of the result. This is by design, to give implementations maximal flexibility to offer better performance (this is how lambdas can be faster than inner classes; we're not tied to the "must create unique instance" constraint that inner classes are.)

So basically, the spec doesn't give you much, except obviously that two lambdas that are reference-equal (==) are going to compute the same function.

From an implementation perspective, you can conclude a little more. There is (currently, may change) a 1:1 relationship between the synthetic classes that implement lambdas, and the capture sites in the program. So two separate bits of code that capture "x -> x + 1" may well be mapped to different classes. But if you evaluate the same lambda at the same capture site, and that lambda is non-capturing, you get the same instance, which can be compared with reference equality.

If your lambdas are serializable, they'll give up their state more easily, in exchange for sacrificing some performance and security (no free lunch.)

One area where it might be practical to tweak the definition of equality is with method references because this would enable them to be used as listeners and be properly unregistered. This is under consideration.

I think what you're trying to get to is: if two lambdas are converted to the same functional interface, are represented by the same behavior function, and have identical captured args, they're the same

Unfortunately, this is both hard to do (for non-serializable lambdas, you can't get at all the components of that) and not enough (because two separately compiled files could convert the same lambda to the same functional interface type, and you wouldn't be able to tell.)

The EG discussed whether to expose enough information to be able to make these judgments, as well as discussing whether lambdas should implement more selective equals/hashCode or more descriptive toString. The conclusion was that we were not willing to pay anything in performance cost to make this information available to the caller (bad tradeoff, punishing 99.99% of users for something that benefits .01%).

A definitive conclusion on toString was not reached but left open to be revisited in the future. However, there were some good arguments made on both sides on this issue; this is not a slam-dunk.

How to test expressions equality

Here is the code for ExpressionEqualityComparer which can show how to do it.

https://source.db4o.com/db4o/trunk/db4o.net/Db4objects.Db4o.Linq/Db4objects.Db4o.Linq/Expressions/

Most efficient way to test equality of lambda expressions

Hmmm... I guess you'd have to parse the tree, checking the node-type and member of each. I'll knock up an example...

using System;
using System.Linq.Expressions;
class Test {
public string Foo { get; set; }
public string Bar { get; set; }
static void Main()
{
bool test1 = FuncTest<Test>.FuncEqual(x => x.Bar, y => y.Bar),
test2 = FuncTest<Test>.FuncEqual(x => x.Foo, y => y.Bar);
}

}
// this only exists to make it easier to call, i.e. so that I can use FuncTest<T> with
// generic-type-inference; if you use the doubly-generic method, you need to specify
// both arguments, which is a pain...
static class FuncTest<TSource>
{
public static bool FuncEqual<TValue>(
Expression<Func<TSource, TValue>> x,
Expression<Func<TSource, TValue>> y)
{
return FuncTest.FuncEqual<TSource, TValue>(x, y);
}
}
static class FuncTest {
public static bool FuncEqual<TSource, TValue>(
Expression<Func<TSource,TValue>> x,
Expression<Func<TSource,TValue>> y)
{
return ExpressionEqual(x, y);
}
private static bool ExpressionEqual(Expression x, Expression y)
{
// deal with the simple cases first...
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if ( x.NodeType != y.NodeType
|| x.Type != y.Type ) return false;

switch (x.NodeType)
{
case ExpressionType.Lambda:
return ExpressionEqual(((LambdaExpression)x).Body, ((LambdaExpression)y).Body);
case ExpressionType.MemberAccess:
MemberExpression mex = (MemberExpression)x, mey = (MemberExpression)y;
return mex.Member == mey.Member; // should really test down-stream expression
default:
throw new NotImplementedException(x.NodeType.ToString());
}
}
}

Java 8: More efficient way of comparing lists of different types?

Your question’s code does not reflect what you describe in the comments. In the comments you say that all names should be present and the size should match, in other words, only the order may be different.

Your code is

List<Person> people = getPeopleFromDatabasePseudoMethod();
List<String> expectedValues = Arrays.asList("john", "joe", "bill");

assertTrue(people.stream().map(person -> person.getName())
.collect(Collectors.toList()).containsAll(expectedValues));

which lacks a test for the size of people, in other words allows duplicates. Further, using containsAll combining two Lists in very inefficient. It’s much better if you use a collection type which reflects you intention, i.e. has no duplicates, does not care about an order and has an efficient lookup:

Set<String> expectedNames=new HashSet<>(expectedValues);
assertTrue(people.stream().map(Person::getName)
.collect(Collectors.toSet()).equals(expectedNames));

with this solution you don’t need to test for the size manually, it is already implied that the sets have the same size if they match, only the order may be different.

There is a solution which does not require collecting the names of persons:

Set<String> expectedNames=new HashSet<>(expectedValues);
assertTrue(people.stream().allMatch(p->expectedNames.remove(p.getName()))
&& expectedNames.isEmpty());

but it only works if expectedNames is a temporary set created out of the static collection of expected names. As soon as you decide to replace your static collection by a Set, the first solution doesn’t require a temporary set and the latter has no advantage over it.



Related Topics



Leave a reply



Submit