Muhammad Alkarouri schrieb: > I was having a go at a simple implementation of Maybe in Python when I > stumbled on a case where x.__mul__(y) is defined while x*y is not. > > class Maybe(object): > def __init__(self, obj): > self.o = obj > def __repr__(self): > return 'Maybe(%s)' % object.__getattribute__(self, "o") > def __getattribute__(self, name): > try: > o = object.__getattribute__(self, "o") > r = getattr(o,name) > if callable(r): > f = lambda *x:Maybe(r(*x)) > return f > else: > return Maybe(r) > except: > return Maybe(None) > >>>> x=Maybe(9) >>>> x.__mul__(7) > Maybe(63) >>>> x*7 > > Traceback (most recent call last): > File "<pyshell#83>", line 1, in <module> > x*7 > TypeError: unsupported operand type(s) for *: 'Maybe' and 'int'
Here's how I'd do it. It will not win a beauty contest any time soon, but at least it's a workaround: ----8<--------8<--------8<--------8<--------8<--------8<--------8<---- def meta(lift, lifted, not_lifted=[]): not_lifted = list(not_lifted) + object.__dict__.keys() class MetaMaybe(type): def __new__(meta, mcls, bases, dct): dct.update( (name, lift(name)) for name in set(lifted) - set(not_lifted) ) return type(mcls, bases, dct) return MetaMaybe class Nothing(object): __metaclass__ = meta(lambda name: lambda self, *a, **k: self, ( "__add__", "__sub__", "__mul__", "__div__", "__truediv__", "__floordiv__", "__divmod__", "__radd__", "__rsub__", "__rmul__", "__rdiv__", "__rtruediv__", "__rfloordiv__", "__rdivmod__", "__rshift__", "__lshift__", "__call__", # and so on, for every special method that Nothing knows )) def __new__(cls, value=None): try: # singleton return cls.value except AttributeError: cls.value = super(Nothing, cls).__new__(cls) return cls.value def __str__(self): return "Nothing" __repr__ = __str__ Nothing = Nothing() def just(vcls): def lifter(name): attr = getattr(vcls, name) def lifted(self, *ms): try: return self.lift(attr)(self, *ms) except: return Nothing return lifted class Just(object): __metaclass__ = meta(lifter, vcls.__dict__.keys()) def __new__(cls, value): if value in (Nothing, NotImplemented): return Nothing return super(Just, cls).__new__(cls) def __init__(self, value): self.value = value def __str__(self): return "Just(%s)" % self.value @classmethod def lift(c, f): return lambda *ms:c(f(*(m.value for m in ms))) return Just from collections import defaultdict class TypeDict(defaultdict): def __missing__(self, key): if self.default_factory is None: raise KeyError(key) return self.default_factory(key) class Maybe(object): typemap = TypeDict(just) def __new__(cls, value): return Maybe.typemap[value.__class__](value) def foo(x, y): return x * 2 + y * 3 if __name__ == "__main__": print Maybe(Nothing) print Maybe(1) / Maybe(0) print Maybe(10.) * Maybe(5) / Maybe(2) ** Maybe(3) print Maybe(foo)(Maybe(6), Maybe(10)) print Maybe("hello").upper() print Maybe("hello").startswith(Maybe("h")) print getattr(Maybe("hello"), "startswith")(Maybe("h")) print Maybe(foo)(Maybe("hello! "), Maybe("what? ")) print Maybe(foo)(Maybe("hello! "), Nothing) ----8<--------8<--------8<--------8<--------8<--------8<--------8<---- I haven't tested it very thoroughly, so it's quite possible there are lots of bugs in it, but it is only intended as a demo. As Gabriel Genellina pointed out, the search for special methods is done in the type, so we have to put our own versions there, during type creation in the metaclass' __new__ method. The above code does this for all methods of the wrapped object's type, not just special ones, and "lifts" them to expect Maybe objects instead of "normal" objects. They also wrap their return values into Maybe objects. Maybe is an algebraic data type. The call Maybe(some_value) returns either Nothing, if some_value happens to be Nothing, or else an object of type Just that wraps some_value. More precisely, there is not one type Just, but as many as types of Just-wrapped objects (Just<int>, Just<string>, ...). Just is therefore a kind of parameterized type. It's similar to inheriting from a C++ template parameter type: template <class T> class MyType : public T {...} but not quite, since in C++ the inherited member functions' signatures are unchanged, whereas in the above code "inherited" methods are changed to expect and return Maybe objects. Some things don't work, though, e.g. slicing. But this could be implemented via specialized lifting functions for the __XXXitem__ methods. One thing that does work though, is that ordinary functions can be wrapped as Maybe objects which then "do the same thing" on other Maybe objects that the normal functions do on normal objects, like in the foo-example. So it's quite close to a monadic version, in that it "lifts" objects and functions from one type space into another one, the Maybe space. But compared to a real Monads it's much more pythonic, IMO. HTH, Mick. -- http://mail.python.org/mailman/listinfo/python-list