Take printers. My initial expectation is that each object itself
would be responsible for knowing how to print itself in various
formats. I would handle this using an informal protocol like this:
class Foo(Basic):
def _print_str_(self):
return self.__str__()
def _print_latex_(self):
# return the latex rep
But, in sympy, the printer class has all of these methods for all the
classes that need to be printed. Why is it done this way?
The difficulty is that if I implement a new subclass of basic, I also
have to add methods to the various printer classes. This seems a bit
silly as 1) it really breaks encasulation 2) it spreads the code for
an object all over the code base
This is related to the work I am doing with sympy in the following
way. I have a dagger class (Hermitian conjugate). It would be nice
if all sympy classes could simply declare a _dagger_ method that
returns the actual dagger of self. But, this design seems to go
against the design patterns in sympy.
So, why doesn't sympy use more informal protocols to enforce
encasulation rather than spreading the logic for an object all over
the place?
Cheers,
Brian
See issue 908 for some details. (
http://code.google.com/p/sympy/issues/detail?id=908 )
> The difficulty is that if I implement a new subclass of basic, I also
> have to add methods to the various printer classes. This seems a bit
> silly as 1) it really breaks encasulation 2) it spreads the code for
> an object all over the code base
Well the encapsulation is just different, its an action on the object.
There are a lot of special things that is going on in some of the
printers, such as how each printer would like to represent a paren.
Rather than making all the code know this it is "encapsulated' in the
printer. I believe this is pretty standard in the Model, View,
Controller world.
On the flip side this design does make it much easier to make a new
printer. If I have some special coding environment I don't have to go
through every class and add _print_foo someplace, I can do it all in
one place. Unfortunately there is a bit of a bug in the design, one
can't just overload __str__ with a printer because the default seems
to call that, we should probably make __repr__ the default.
>
> This is related to the work I am doing with sympy in the following
> way. I have a dagger class (Hermitian conjugate). It would be nice
> if all sympy classes could simply declare a _dagger_ method that
> returns the actual dagger of self. But, this design seems to go
> against the design patterns in sympy.
There is precedent for this, for example all the _eval_foo stuff or
the conjugate method.
>
> So, why doesn't sympy use more informal protocols to enforce
> encasulation rather than spreading the logic for an object all over
> the place?
Do you have some examples of these protocols?
-- Andy
> Cheers,
>
> Brian
>
> >
>
I skimmed quickly through some of this and it does sound like there
are some subtle issues with printers - especially when there are
Python builtins (list, dict, tuple) that also have to be handled. I
do think that building the printers as they are now is probably fine
for Sympy built-ins. In that case 1) there are a finite number of
classes that need to be handled and 2) all of them are under the
control of Sympy.
>> The difficulty is that if I implement a new subclass of basic, I also
>> have to add methods to the various printer classes. This seems a bit
>> silly as 1) it really breaks encasulation 2) it spreads the code for
>> an object all over the code base
>
> Well the encapsulation is just different, its an action on the object.
> There are a lot of special things that is going on in some of the
> printers, such as how each printer would like to represent a paren.
> Rather than making all the code know this it is "encapsulated' in the
> printer. I believe this is pretty standard in the Model, View,
> Controller world.
>
> On the flip side this design does make it much easier to make a new
> printer. If I have some special coding environment I don't have to go
> through every class and add _print_foo someplace, I can do it all in
> one place. Unfortunately there is a bit of a bug in the design, one
> can't just overload __str__ with a printer because the default seems
> to call that, we should probably make __repr__ the default.
Yes, and printers should be easy to extend. However, the current
design makes it more difficult for users to create their own Sympy
classes. Let's say I create a sub-class of basic to handle a certain
type of Operator in quantum mechanics, and this lives in my own
project outside of sympy. Currently I have to 1) patch Sympy's
built-in printers so that they know about my Operator class (probably
not going to happen) or 2) subclass Sympy's printer to handle the
printing of my class. This is all fine and dandy until I have to
integrate with someone else's custom Sympy classes that have their own
printers. Then, you are in a nasty mess of multiple inheritance and
circular dependencies between different projects (I need your printer,
you need mine).
Here is a proposal. Keep the Sympy printer as they are today.
However, if a printer encounter's an object that it doesn't know how
to print, it asks the object itself if knows how to print itself. For
instance, the Sympy latex printer could look at my fictitious Operator
class for:
Operator._latex_
And call it to get the latex representation. This would be a trivial
mod of the current printers and would open the door to make it much
easier to extend sympy.
>> So, why doesn't sympy use more informal protocols to enforce
>> encasulation rather than spreading the logic for an object all over
>> the place?
>
>
> Do you have some examples of these protocols?
Sure in Python itself:
__str__
__int__
__float__
etc.
In Sage (which Sympy tries to interoperate with), they use informal
protocols all over the place. Their printing is based on this idea
(_latex_ methods) and so is their coertion system (_pari_, etc).
Indeed and neither 1) or 2) is true. We want sympy to be very easy to
extend and hack on. Thus I like your proposal below.
>
>>> The difficulty is that if I implement a new subclass of basic, I also
>>> have to add methods to the various printer classes. This seems a bit
>>> silly as 1) it really breaks encasulation 2) it spreads the code for
>>> an object all over the code base
>>
>> Well the encapsulation is just different, its an action on the object.
>> There are a lot of special things that is going on in some of the
>> printers, such as how each printer would like to represent a paren.
>> Rather than making all the code know this it is "encapsulated' in the
>> printer. I believe this is pretty standard in the Model, View,
>> Controller world.
>>
>> On the flip side this design does make it much easier to make a new
>> printer. If I have some special coding environment I don't have to go
>> through every class and add _print_foo someplace, I can do it all in
>> one place. Unfortunately there is a bit of a bug in the design, one
>> can't just overload __str__ with a printer because the default seems
>> to call that, we should probably make __repr__ the default.
Let's fix the __str__ bug.
>
> Yes, and printers should be easy to extend. However, the current
> design makes it more difficult for users to create their own Sympy
> classes. Let's say I create a sub-class of basic to handle a certain
> type of Operator in quantum mechanics, and this lives in my own
> project outside of sympy. Currently I have to 1) patch Sympy's
> built-in printers so that they know about my Operator class (probably
> not going to happen) or 2) subclass Sympy's printer to handle the
> printing of my class. This is all fine and dandy until I have to
> integrate with someone else's custom Sympy classes that have their own
> printers. Then, you are in a nasty mess of multiple inheritance and
> circular dependencies between different projects (I need your printer,
> you need mine).
Exactly, that's a real problem. See also a discussion in this thread
(skip the first couple messages):
http://groups.google.com/group/sympy-patches/browse_thread/thread/d3bf166d967451ff/
> Here is a proposal. Keep the Sympy printer as they are today.
> However, if a printer encounter's an object that it doesn't know how
> to print, it asks the object itself if knows how to print itself. For
> instance, the Sympy latex printer could look at my fictitious Operator
> class for:
>
> Operator._latex_
>
> And call it to get the latex representation. This would be a trivial
> mod of the current printers and would open the door to make it much
> easier to extend sympy.
Yes, let's do it.
>
>>> So, why doesn't sympy use more informal protocols to enforce
>>> encasulation rather than spreading the logic for an object all over
>>> the place?
>>
>>
>> Do you have some examples of these protocols?
>
> Sure in Python itself:
>
> __str__
> __int__
> __float__
> etc.
>
> In Sage (which Sympy tries to interoperate with), they use informal
> protocols all over the place. Their printing is based on this idea
> (_latex_ methods) and so is their coertion system (_pari_, etc).
We also use informal protocols all over the place, all the _eval_* methods etc.
Basically the arguments for using Printer class is that complexity is
localized. Being able to add new printers easily is imho very
important. Also, it's very important to be able to add new classes to
sympy and for them to be able to easily integrate with printers. So I
like your proposal:
For most basic/frequent classes that are in sympy (we can discuss what
is "basic/frequent class") use Printers, for everything else use
_latex_, _str_, _repr_, maybe also _ccode_, _pretty_ or something for
every single Printer we have.
Others, what do you think? Basti and Kirill, you implemented most of
the Printer system, I'd like to know your opinions.
Ondrej
OK, gives me a bit more context about the history of all of this.
But, I am definitely in favor of the localizing logic into classes as
much as possible. I will be interested to hear what others think
about this proposal.
I am crazy busy this week, so I won't have any time to hack on sympy.
But I will try to keep up on the discussions.
Brian
Having previously written multi-type pretty-printers of significantly
less complexity, I can chip in that it is fairly hard to actually
localize such logic by type. Inevitably, one type needs to know about
or control another. Encapsulating by output format rather than type
makes a fair bit of sense in this context. Your compromise hits a nice
sweet spot of getting excellent output for all of the builtin stuff
and satisfactory-to-excellent output for new stuff the printer might
not know about.
--
Robert Kern
"I have come to believe that the whole world is an enigma, a harmless
enigma that is made terrible by our own mad attempt to interpret it as
though it had an underlying truth."
-- Umberto Eco
Thanks Robert for the argument. I have the same opinion.
On Tue, Nov 4, 2008 at 7:23 PM, basti <bast...@gmail.com> wrote:
>
> I think it's a good proposal of Brian and I sent a patch with an
> implementation to http://groups.google.com/group/sympy-patches/browse_thread/thread/9f8392a8fec0b3e8
The patch is in.
Brian --- is the current solution sufficient?
Ondrej