# Copyright 2021 Zilliz. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Callable, Generic
from typing import Any, TypeVar
A = TypeVar('A', covariant=True)
B = TypeVar('B')
T = TypeVar('T')
class _Reason:
"""
reason for `Empty` value
"""
def __init__(self, x: Any, e: Exception) -> None:
self._value = x
self._exception = e
@property
def value(self):
return self._value
@property
def exception(self):
return self._exception
[docs]class Option(Generic[A]):
"""
Functional-style error handling.
Option[A] = Some(A) or Empty[A]
1. Some(A): just a container for result;
2. Empty[A]: result is empty, because of input error or computation error;
Examples:
>>> a = Some(10)
>>> a.map(lambda x: x/2.0)
Some(5.0)
>>> a.map(lambda x: x/0)
Empty()
>>> b = Empty()
>>> b.map(lambda x: x/2.0)
Empty()
"""
[docs] @staticmethod
def of(x: T):
"""Return a boxed value
Args:
x (T): input value
Returns:
Some(T): boxed value
"""
return Some(x)
[docs] @staticmethod
def empty():
"""Return an empty value
Returns:
Empty: empty value
"""
return Empty()
[docs] def flat_map(self, f: Callable[[A], 'Option[B]']) -> 'Option[B]':
"""Apply boxed version of callable
Args:
f (Callable[[A], Option[B]]): boxed version of callable
Returns:
Option[B]: boxed value
Examples:
>>> Option.of(1).flat_map(lambda x: x+1)
2
>>> Option.empty().flat_map(lambda x: x+1)
Empty()
"""
if isinstance(self, Some):
return f(self._value)
else:
return self
[docs] def map(self, f: Callable[[A], 'B']) -> 'Option[B]':
"""Apply function to value
Args:
f (Callable[[A], B]): unboxed function
Returns:
Option[B]: boxed return value
"""
def wrapper(x):
try:
return Some(f(x))
except Exception as e: # pylint: disable=broad-except
return Empty(x, e)
return self.flat_map(wrapper)
[docs] def is_empty(self):
"""Return True if the value is empty.
"""
return isinstance(self, Empty)
[docs] def is_some(self):
"""Return True if the value is boxed value.
"""
return isinstance(self, Some)
[docs] def get_or_else(self, default):
"""Return unboxed value, or default is the value is empty.
Examples:
>>> Option.of(0).get_or_else(1)
0
>>> Option.empty().get_or_else(1)
1
"""
if self.is_some():
return self.get()
return default
[docs]class Some(Option[A]):
"""
`Some` value for `Option`
"""
[docs] def __init__(self, x: A) -> None:
self._value = x
[docs] def __repr__(self) -> str:
return 'Some({})'.format(self._value)
[docs] def flat_map(self, f: Callable[[A], 'Option[B]']) -> 'Option[B]':
return f(self._value)
[docs] def get(self):
"""Return unboxed value
"""
return self._value
[docs]class Empty(Option[A]):
"""
`Empty` value for `Option`
"""
[docs] def __init__(self, x: Any = None, e: Exception = None) -> None:
self._reason = _Reason(x, e)
[docs] def __repr__(self) -> str:
return 'Empty()'
[docs] def flat_map(self, f: Callable[[A], 'Option[B]']) -> 'Option[B]':
return self
[docs] def get(self):
"""Return the reason of the empty value.
"""
return self._reason
empty = Empty()