On Monday, June 9, 2014 1:44:41 PM UTC-7, Erik Massop wrote: > > On Mon, 09 Jun 2014 16:48:09 +0200 > leif <not.r...@online.de <javascript:>> wrote: > > > Volker Braun wrote: > > > On Monday, June 9, 2014 12:54:56 AM UTC+1, vdelecroix wrote: > > > > > > sage: {3, AA(3)} > > > {3, 3} > > > > > > > > > IMHO you should never put sage objects with different parents into an > > > associative container. > > > > Although that's presumably an anti-pythonic attitude, +1. (Provided > > they *won't* have sex with each other that is.) > > IMHO this should be disallowed. All objects that implement __hash__ > should live up to contract of __hash__, which is that a==b implies > hash(a)==hash(b). If this contract is violated, then the object is > not worthy of being called hashable and sage should not pretend that it > is hashable. It is too easy to forget to coerce and get a dict that > violates a==b => d[a]==d[b], especially since this coercion is not > necessary in python and not for ZZ->QQ, ZZ->RR, ZZ->CC, and RR->CC. >
It's a gotcha that has been around from the start: http://www.sagemath.org/doc/developer/coding_in_python.html#the-hash-special-method and as discussed there, it's a compromise that was considered the least bad. The main problem is that if you want, for all integers a,b and distinct primes p,q, that GF(p)(a) == GF(p)(b) implies a == GF(p)(b) and GF(q)(a) == GF(q)(b) implies a == GF(q)(b) *and* you want consistency with hash, then you need hash(a)=hash(a+n*p)=hash(a+m*q), so you need that hash on integers is a constant function. This is incompatible with hash(ZZ(1)) == hash (int(1)) [forced on us by python; quite apart from making objects hashable but with constant hash is useless practically], so you'd have to make sage integers unhashable. You can probably see why that is undesirable. A milder solution would be to just make finite field elements unhashable (or for that matter, nearly all elements of quotient structures). The consistent solution, and one that is mathematically defendable, would be to have a != GF(p)(a) for integers a (i.e., always force explicit coercion for equality to hold). So that is making "==" strict enough to allow hash to meet its requirements, but this turns out to be too problematic for new users and for casual/interactive use [for more serious programming this is fine -- it's what Magma does and users there can live with it, after an adjustment period] > I would propose that instead of __hash__ something like _sage_hash > should be used that implements a hash satisfying > self == other and self.parent() == other.parent() > implies > self._sage_hash() == other._sage_hash(). > Then there could be sage specific associative containers using this > hash. For instance something like this: > > d = MyDict(CoerceTo(complex)) > d[1] = 0 > d[1+I] = 3 > > e = MyDict(CoerceTo(ZZ)) > e[1] = 0 > e[1+I] = 3 # ERROR! > > def sage_hash(x): > #TODO: handle tuples, ... > try: > return x._sage_hash() > except AttributeError: > return hash(x) > > class Shim: > def __init__(self, hash_manager, data): > self.hm = hash_manager > self.data = data > def __hash__(self): > return self.hm.hash(self) > def __eq__(self, other): > return self.hm.equal(self, other) > > class MyDict: > def __init__(self, hash_manager, data = []): > self.hm = hash_manager > self.content = dict() > #TODO initialize with stuff in data > > def __setitem__(self, key, val): > if not self.hm.acceptable(key): > throw "cannot accomodate this kind of key" > self.content[Shim(hm, key)] = val > > class CoerceTo: > def __init__(self, target): > self.target = target > def acceptable(self, data): > try: > data = target(data) > except: > return False > else: > return True > def hash(self, data): > data = target(data) > return sage_hash(data) > def equal(self, other): > return target(self) == target(data) > > > The hash_manager could be lazy about what kinds of objects > it accepts. For instance a MyDict with the manager below would accept > anything with the same parent as the first element: > > class CoerceToFirst: > def __init__(self): > self.inner = None > def acceptable(self, data): > if self.inner is None: > # FIXE: handle python types > self.inner = CoerceTo(data.parent()) > return self.inner.expand(data) > def __getattr__(self, name): > return getattr(self.inner, name) > > It is also possible to have a manager that expands the kinds of keys > that it accept, as long as they are compatible with what was received > before, or one that doesn't check anything and just calls sage_hash, > assuming that the contract is satisfied for all relevant keys. (This > would avoid having to change all cached functions.) > > > Regards, > > Erik Massop > -- You received this message because you are subscribed to the Google Groups "sage-devel" group. To unsubscribe from this group and stop receiving emails from it, send an email to sage-devel+unsubscr...@googlegroups.com. To post to this group, send email to sage-devel@googlegroups.com. Visit this group at http://groups.google.com/group/sage-devel. For more options, visit https://groups.google.com/d/optout.