While working on a pet project (a solver for a block puzzle game) I needed to implement equals and hash on all my model classes. This mixin did the trick and is ridiculously simple.
mixin Model {
override Bool equals(Obj? that) {
if (this === that) return true;
if (null === that) return false;
if (this.typeof != that.typeof) return false;
return typeof.fields.all |Field field->Bool| {
field.get(this) == field.get(that)
}
}
override Int hash() {
return typeof.fields.reduce(51) |Int base, Field field->Int| {
Int fieldHash := field.get(this)?.hash ?: 0
return base * 37 + fieldHash
}
}
}
This probably doesn't belong in a core class, but could we put this in the docs somewhere?
rfeldmanFri 2 Jul 2010
Relatedly, anyone know how speedy Fantom's reflection is? I know Java's is notoriously slow, but I haven't run any speed tests with Fantom.
If it's not actually slow, I would personally love having something like this as the default implementation of those methods in Obj. It's always seemed to me like this is what you'd want equals() to do by default, and likewise with hash().
ivanFri 2 Jul 2010
rfeldman
Seems the reflection is quite fast, but about 10 times slower than normal call. Just wrote a test:
class SampleData { Str s := ""; Int i := 4 }
class ReflectionTest
{
Void main()
{
count := 100_000_000
data := SampleData()
getInt := measure |->| { count.times { i := data.i } }
reflectInt := measure |->| { count.times { i := data.typeof.field("i").get(data) } }
getStr := measure |->| { count.times { s := data.s } }
reflectStr := measure |->| { count.times { s := data.typeof.field("s").get(data) } }
echo("getInt : $getInt.toMillis
refectInt : $reflectInt.toMillis
getStr : $getStr.toMillis
reflectStr: $reflectStr.toMillis
")
}
Duration measure(|->| func)
{
start := Duration.now
func.call
return Duration.now - start
}
}
ivanFri 2 Jul 2010
qualidafial,
I used the same approach, but sometimes it may be useful to exclude some fields from hash/compare, so I used approach like this (writing by memory):
mixin State
{
abstract Obj?[] state
override Int hash() { state.hash }
override Bool equals(Obj? other)
{
other.typeof.fits(typeof) ?
other->state == state : false
}
}
class MyData : State
{
Int int; List list; Str str;
override Obj?[] state() { [int, list, str] }
}
brianFri 2 Jul 2010
I'd say in general reflection is fast enough for stuff like that unless you have some critical code. Reflection has gotten a lot faster in the newer VMs compared to the old days.
Plus, there are future optimizations that will likely bring it pretty close to statically compiled calls. We can generate code in Java runtime to avoid some of the overhead in Java reflection. Plus eventually, I hope we can use MethodHandles (if 1.7 ever ships and gets popular).
rfeldmanFri 2 Jul 2010
Thoughts on adding something like these to Obj? Is there any downside?
KevinKelleyFri 2 Jul 2010
@rfeldman: fails on graphs, doubly-linked lists, etc., with infinite-loop. But it's a real nice pattern for the same sorts of things where serialization works.
brianFri 2 Jul 2010
Thoughts on adding something like these to Obj? Is there any downside?
They can't go on Obj because Obj has well defined semantics that equals is based on reference equality unless a class explicitly overrides hash/equals.
qualidafialFri 2 Jul 2010
In general my policy is to implement equals and hash only on const classes. For mutable classes you usually want instance equals.
qualidafialFri 2 Jul 2010
Could we put this class somewhere in the examples? This mixin, although not as performant as manually written implementations, is a good jumping off point for fast prototyping. Depending on your application load it could be good enough for deployment.
qualidafial Fri 2 Jul 2010
Fantom rules.
While working on a pet project (a solver for a block puzzle game) I needed to implement
equals
andhash
on all my model classes. This mixin did the trick and is ridiculously simple.This probably doesn't belong in a core class, but could we put this in the docs somewhere?
rfeldman Fri 2 Jul 2010
Relatedly, anyone know how speedy Fantom's reflection is? I know Java's is notoriously slow, but I haven't run any speed tests with Fantom.
If it's not actually slow, I would personally love having something like this as the default implementation of those methods in Obj. It's always seemed to me like this is what you'd want equals() to do by default, and likewise with hash().
ivan Fri 2 Jul 2010
rfeldman
Seems the reflection is quite fast, but about 10 times slower than normal call. Just wrote a test:
Testing code:
ivan Fri 2 Jul 2010
qualidafial,
I used the same approach, but sometimes it may be useful to exclude some fields from hash/compare, so I used approach like this (writing by memory):
brian Fri 2 Jul 2010
I'd say in general reflection is fast enough for stuff like that unless you have some critical code. Reflection has gotten a lot faster in the newer VMs compared to the old days.
Plus, there are future optimizations that will likely bring it pretty close to statically compiled calls. We can generate code in Java runtime to avoid some of the overhead in Java reflection. Plus eventually, I hope we can use MethodHandles (if 1.7 ever ships and gets popular).
rfeldman Fri 2 Jul 2010
Thoughts on adding something like these to
Obj
? Is there any downside?KevinKelley Fri 2 Jul 2010
@rfeldman: fails on graphs, doubly-linked lists, etc., with infinite-loop. But it's a real nice pattern for the same sorts of things where serialization works.
brian Fri 2 Jul 2010
They can't go on Obj because Obj has well defined semantics that equals is based on reference equality unless a class explicitly overrides hash/equals.
qualidafial Fri 2 Jul 2010
In general my policy is to implement
equals
andhash
only onconst
classes. For mutable classes you usually want instance equals.qualidafial Fri 2 Jul 2010
Could we put this class somewhere in the examples? This mixin, although not as performant as manually written implementations, is a good jumping off point for fast prototyping. Depending on your application load it could be good enough for deployment.