Hi!

Nicolas suggested to implement something in Sage that (as he says) was used a
lot in the category code in MuPAD: Conditionally defined methods.

The idea is: One defines a method "foo()" for some class C, but not all
instances of C shall use this method. Only if an instance x fulfills a
certain condition (e.g., if it is immutable or if it has some other
method that will used in "foo()" internally) then x.foo() will use
C.foo; otherwise, x.foo() will use super(C,C).foo.

I think this is an interesting and useful feature, and therefore I want
to draw your attention to it: See #12978, which provides a decorator
@conditionally_defined and needs review.

It would also be nice to be able to use decorators such as 
@conditionally_defined
or @cached_method on "special methods" (__hash__, __len__ etc) of new style
classes. The difficulty is that Python will ignore the content of
__dict__ of an instance. But it is doable---see #12601, which needs
review as well.

Note that using @cached_method on the __hash__ or __len__ method results
in a hash() or len() that is faster than a constant method written in Python
(see the example below).

And it would be nice to be able to combine decorators: In the example
below, we want that the __len__ is a cached method only for immutable
instances. For this, I suggest a change in @cached_method in #15056
(needs review, too): Currently, a cached_method just wraps a function
and pretends to be a method. But wouldn't it be cleaner when it really
wrapped a (bound or unbound) method? With this change, a combination
of @cached_method and @conditionally_defined (as in the example below)
yields a cached method whose underlying uncached method depends on
properties of an instance.

The use of the new decorator is restricted to new style classes, unless
someone can explain to me how to do something like "super(C,C).foo" for
old style classes...

Example:

 sage: from sage.misc.conditionally_defined import conditionally_defined
 sage: class A(object):
 ....:     def __init__(self, n, immutable=False):
 ....:         self.n = n
 ....:         self._immutable = immutable
 ....:     def is_immutable(self):
 ....:         return self._immutable
 ....:     def __len__(self):
 ....:         print "computing length"
 ....:         return self.n
 ....:     def foo(self, x):
 ....:         print "method of A"
 ....:         return -x
 ....:     
 sage: class B(A):
 ....:     @conditionally_defined(lambda x: x.is_immutable())
 ....:     @cached_method
 ....:     def __len__(self):
 ....:         return super(B,self).__len__()
 ....:     @cached_method
 ....:     @conditionally_defined("helper")
 ....:     def foo(self, x):
 ....:         print "method of B using helper"
 ....:         return self.helper - x
 ....:     

Now we create an instance of A, a mutable and an immutable instance of
B, the former with an additional attribute ".helper".

 sage: a = A(5, True)
 sage: b_mut = B(4, False)
 sage: b_mut.helper = 3
 sage: b_imm = B(8, True)

We see that the length of the immutable instance is cached:

 sage: len(a)
 computing length
 5
 sage: len(a)
 computing length
 5
 sage: len(b_mut)
 computing length
 4
 sage: len(b_mut)
 computing length
 4
 sage: len(b_imm)
 computing length
 8
 sage: len(b_imm)  # It is cached and not computed again!
 8

And we see that .foo(x) is a cached method whose underlying implementation
depends on whether or not ".helper" is defined.

 sage: b_mut.foo(2)
 method of B using helper
 1
 sage: b_mut.foo(2)
 1
 sage: b_imm.foo(2)
 method of A
 -2
 sage: b_imm.foo(2)
 -2

AND: We see that a cached special method is very fast, even compared
with a constant function written in Python!

 sage: class C(object):
 ....:     def __len__(self):
 ....:         return 5
 ....:     
 sage: c = C()
 sage: len(c)
 5
 sage: len(b_imm)
 8
 sage: %timeit len(c)
 1000000 loops, best of 3: 571 ns per loop
 sage: %timeit len(b_imm)
 1000000 loops, best of 3: 382 ns per loop

I think that Nicolas' feature request was a very reasonable one, and
thus I'd appreciate some additional pairs of eyes to detect and smoothen
rough edges in the patches at #12601, #15056 and #12978.

Best regards,
Simon


-- 
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 [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sage-devel.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to