| File: src/multimethods.py | [up] |
1 ################################################################################
2 # File: dist/srcb-0.20050801/src/multimethods.py
3 # Version: $Id: multimethods.py,v 1.3 2005/06/11 08:44:13 vyzo Exp $
4 # Content: multimethods
5 #
6 # Copyright (C) 2005 Dimitris Vyzovits [vyzo@media.mit.edu]
7 #
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 2
11 # of the License, or (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 # MA 02110-1301, USA
22 #
23 #
24 # Note: This file is part of the Peers Project
25 # Note: http://viral.media.mit.edu/peers
26 # Note: Peers Id: multimethod.py,v 1.3 2005/05/21 03:08:46 vyzo Exp
27 ################################################################################
28
29 ##%> (:alias (:module srcb.multimethods) 'multimethods)
30 ##%% Multimethods for python objects
31
32 class multimethod_dispatch( object ):
33 def __init__( self, name ):
34 self.name = name
35 self.methods = {}
36 self.instance_class = None
37
38 def __call__( self, *args ):
39 ## unbound method behavior
40 ## look for static methods first
41 method = self.lookup( args )
42 if method is None:
43 ## instance methods, first arg must be an instance of
44 ## the instance_class
45 if (self.instance_class is not None) \
46 and (len(args) > 0) \
47 and isinstance( args[0], self.instance_class ):
48 method = self.lookup( args[1:] )
49 if method is None:
50 sig = tuple( x.__class__.__name__ for x in args )
51 raise TypeError, "signature mismatch: %s" % (sig,)
52 return method( *args )
53
54 def __repr__( self ):
55 return "<multimethod %s: %s>" % (self.name, self.methods)
56
57 def register( self, method, sig ):
58 if sig in self.methods:
59 raise TypeError, "duplicate signature"
60 self.methods[sig] = method
61
62 def lookup( self, args ):
63 def cmp_type( a, b ):
64 for x in xrange( len( a ) ):
65 if issubclass( a[x], b[x] ): return -1
66 elif issubclass( b[x], a[x] ): return 1
67 else:
68 v = cmp( a[x], b[x] )
69 if v: return v
70 return 0
71
72 ## try perfect match first
73 sig = tuple( x.__class__ for x in args )
74
75 method = self.methods.get( sig )
76 if method is None:
77 ## try partial matches
78 partial = filter( lambda xsig: len(xsig) == len( sig ),
79 self.methods.keys() )
80 ## sort the partial methods by specificity in the number
81 ## of object base-classes to allow contravariance.
82 ## The default cmp does A < B if B is subclass of A
83 ## while it does anything < object
84 ## The latter ensures that for generic object cmp does
85 ## the right thing, but it doesnt for the general contravariance
86 ## case. Hence we sort with cmp_type
87 partial.sort( cmp_type )
88 for xsig in partial:
89 match = True
90 for x in xrange( len( xsig ) ):
91 if not isinstance( args[x], xsig[x] ):
92 match = False
93 break
94 if match:
95 method = self.methods[xsig]
96 break
97
98 return method
99
100 def bind( self, obj ):
101 class bound_method( object ):
102 def __init__( self, dispatch ):
103 self.dispatch = dispatch
104
105 def __call__( self, *args ):
106 method = self.dispatch.lookup( args )
107 if method is None:
108 sig = tuple( x.__class__.__name__ for x in args )
109 raise TypeError, "signature mismatch %s" % (sig,)
110 return method( obj, *args )
111
112 def __repr__( self ):
113 desc = (self.dispatch.instance_class,
114 self.dispatch.name,
115 self.dispatch.methods)
116 return "<bound multimethod %s.%s: %s>" % desc
117
118 self.instance_class = obj.__class__
119 setattr( obj, self.name, bound_method( self ) )
120
121 ##%> (:class multimethods.multimethod)
122 ##%% Decorator class for creating multimethods.
123 ##%% Instances of the class decorate methods with the signature as arguments.
124 ##%% The method dispatch uses best match on the argument types.
125 ##%% <p/>See @srcb.interp.interpreter and @srcb.interp.interpreter.eval
126 ##%% for an example of usage.
127 ##%> (:~see concepts.multimethod)
128 class multimethod( object ):
129 def __init__( self ):
130 self.dispatch = {}
131
132 def __call__( self, *sig ):
133 def register( method ):
134 name = method.__name__
135 dispatch = self.dispatch.get( name )
136 if dispatch is None:
137 dispatch = multimethod_dispatch( name )
138 self.dispatch[name] = dispatch
139
140 dispatch.register( method, sig )
141 return dispatch
142
143 return register
144
145 def bind( self, obj ):
146 for method in self.dispatch.itervalues():
147 method.bind( obj )