An example Maybe pattern implementation for Python.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
#!/usr/bin/env python # -*- coding: utf-8 -*- NOVALUE = object() class Maybe(object): _has_value = False _value = None def __init__(self, value): if value is not NOVALUE: self._has_value = True self._value = value def __nonzero__(self): return self.has_value @property def has_value(self): return self._has_value @property def value(self): return self._value # optional sugar factories def Some(value): return Maybe(value) def NoValue(): return Maybe(NOVALUE) if __name__ == "__main__": class MaybeSupportingRepository(object): def __init__(self, *args): self._d = dict(args) def get(self, key): return Maybe(self._d.get(key, NOVALUE)) repo = MaybeSupportingRepository( ("a", 1), ("b", 2), ("c", 3) ) key = "x" maybe_v = repo.get(key) if maybe_v: print "There's a value for %s: %s" % (key, maybe_v.value) else: print "There's no value for %s" % key key = "a" maybe_v = repo.get(key) if maybe_v.has_value: print "There's a value for %s: %s" % (key, maybe_v.value) else: print "There's no value for %s" % key
Sometimes you need a way to tell a library's client that an object that was requested is unavailable. There're many ways to achieve this; the most common, in Python, is just returning None; other languages, especially statically typed ones, often return null. Otherwise, an exception can be raised, or the Null Object pattern can be employed, or you might just return a tuple holding something like (has_value, value). Python dictionaries offer even more solutions: just specify a fallback value when calling the get() method, and you're done.
The choice, of course, is up to you and depends on your needs; this solution is an improvement over simply returning None, because such behaviour can often slip through unnoticed, ending up with a "NoneType object has no attribute XXX" exception somewhere else in the application code, or valid values, such as empty collections, might be considered no-values because of a quick check like:
v = container.get("key", None) if v: # do something else: # no value found
- forces return value checking, because the return value is NOT the final value the client needs.
- allows the check above to work in all cases, because the object will evaluate to False only if its value is None. Empty collections will evaluate as True.
Syntactic-sugar factories are provided as well, in order to make your code more readable whenever applicable.
Somebody let me notice that None may actually be a valid value that we want to be returned. Hence it's better to create a new NOVALUE constant.