Optional: Object wrapper for handling errors

class carriage.Optional

An type for handling special value or exception.

Here is a contacts data constructed with multiple levels dictionary.

>>> contacts = {
...     'John Doe': {
...         'phone': '0911-222-333',
...         'address': {'city': 'hsinchu',
...                     'street': '185 Somewhere St.'}},
...     'Richard Roe': {
...         'phone': '0933-444-555',
...         'address': {'city': None,
...                     'street': None}},
...     'Mark Moe': {
...         'address': None},
...     'Larry Loe': None
... }

If we need a function to get the formatted city name of some contact, we will have a lot of nested if statement for handling None or other unexpected values.

>>> def get_city(name):
...     contact = contacts.get(name)
...     if contact is not None:
...         address = contact.get('address')
...         if address is not None:
...             city = address.get('city')
...             if city is not None:
...                 return f'City: {city}'
...
...     return 'No city available'
>>> get_city('John Doe')
'City: hsinchu'
>>> get_city('Richard Roe')
'No city available'
>>> get_city('Mark Moe')
'No city available'
>>> get_city('Larray Loe')
'No city available'
>>> get_city('Not Existing')
'No city available'

Optional is useful on handling unexpected return values or exceptions and makes the code shorter and more readable.

>>> def getitem_opt(obj, key):
...     """The same as Optional.from_getitem()"""
...     try:
...         return Some(obj[key])
...     except (KeyError, TypeError):
...         return Nothing
...
>>> def get_city2(name):
...     return (getitem_opt(contacts, name)
...             .and_then(lambda contact: getitem_opt(contact, 'address'))
...             .and_then(lambda address: getitem_opt(address, 'city'))
...             .filter(lambda city: city is not None)
...             .map(lambda city: f'City: {city}')
...             .get_or('No city available')
...             )
...
>>> get_city2('John Doe')
'City: hsinchu'
>>> get_city2('Richard Roe')
'No city available'
>>> get_city2('Mark Moe')
'No city available'
>>> get_city2('Larray Loe')
'No city available'
>>> get_city('Not Existing')
'No city available'

Create Optional directly

>>> Some(3)
Some(3)
>>> Nothing
Nothing

Create Optional by calling a function that may throw exception

>>> def divide(a, b):
...     return a / b
>>> Optional.from_call(divide, 2, 4, errors=ZeroDivisionError)
Some(0.5)
>>> Optional.from_call(divide, 2, 0, errors=ZeroDivisionError)
Nothing

Create Optional from a value that may be None or other spectial value.

>>> adict = {'a': 1, 'b': 2, 'c': 3}
>>> Optional.from_value(adict.get('c'), nothing_value=None)
Some(3)
>>> Optional.from_value(adict.get('d'), nothing_value=None)
Nothing
and_then(optional_func)

Return optional_func(value) if it is Some optional_func should return Optional

and_then is useful for chaining functions that return Optional

filter(pred)

Return Nothing if Some doesn’t satisfy the predicate

classmethod from_call(func, *args, errors=(<class 'Exception'>, ), **kwargs)

Create an Optional by calling a function

return Nothing if exception is raised

classmethod from_getattr(obj, attr_name)

Create an Optional by calling obj.attr_name

return Nothing if AttributeError is raised

classmethod from_getitem(obj, key)

Create an Optional by calling obj[key]

return Nothing if KeyError or TypeError is raised

classmethod from_value(value, nothing_value=None)

Create an Optional from a value

return Nothing if value equals to nothing_value

get_or(default)

Get the value if it is Some or get default if it is Nothing

get_or_none()

Get the value if it is Some or get None if it is Nothing

is_nothing()

Check if it is Nothing

is_some()

Check if it is Some

map(func)

Return Some(func(value)) if it is Some

some

Get the value if it is Some or raise AttributeError if it is not

carriage.Nothing