diff --git a/DOCS.md b/DOCS.md index ec7f4158e..f4e6d8153 100644 --- a/DOCS.md +++ b/DOCS.md @@ -3186,6 +3186,10 @@ data Expected[T](result: T? = None, error: BaseException? = None): if not self: raise self.error return self.result + def handle(self, err_type, handler: BaseException -> T) -> Expected[T]: + if not self and _coconut.isinstance(self.error, err_type): + return self.__class__(handler(self.error)) + return self ``` `Expected` is primarily used as the return type for [`safe_call`](#safe_call). Generally, the best way to use `Expected` is with [`fmap`](#fmap), which will apply a function to the result if it exists, or otherwise retain the error. If you want to sequence multiple `Expected`-returning operations, `.and_then` should be used instead of `fmap`. @@ -3336,6 +3340,8 @@ def safe_call(f, /, *args, **kwargs): return Expected(error=err) ``` +To define a function that always returns an `Expected` rather than raising any errors, simply decorate it with `@safe_call$`. + ##### Example **Coconut:** diff --git a/__coconut__/__init__.pyi b/__coconut__/__init__.pyi index 804609004..def115f7b 100644 --- a/__coconut__/__init__.pyi +++ b/__coconut__/__init__.pyi @@ -354,6 +354,10 @@ class Expected(_BaseExpected[_T]): if not self: raise self.error return self.result + def handle(self, err_type, handler: BaseException -> T) -> Expected[T]: + if not self and _coconut.isinstance(self.error, err_type): + return self.__class__(handler(self.error)) + return self ''' __slots__ = () _coconut_is_data = True @@ -416,6 +420,9 @@ class Expected(_BaseExpected[_T]): def unwrap(self) -> _T: """Unwrap the result or raise the error.""" ... + def handle(self, err_type: _t.Type[BaseException], handler: _t.Callable[[BaseException], _T]) -> Expected[_T]: + """Recover from the given err_type by calling handler on the error to determine the result.""" + ... _coconut_Expected = Expected diff --git a/coconut/compiler/templates/header.py_template b/coconut/compiler/templates/header.py_template index 0eb8e1417..4edc7ce34 100644 --- a/coconut/compiler/templates/header.py_template +++ b/coconut/compiler/templates/header.py_template @@ -1741,6 +1741,10 @@ class Expected(_coconut.collections.namedtuple("Expected", ("result", "error")){ if not self: raise self.error return self.result + def handle(self, err_type, handler: BaseException -> T) -> Expected[T]: + if not self and _coconut.isinstance(self.error, err_type): + return self.__class__(handler(self.error)) + return self ''' __slots__ = () {is_data_var} = True @@ -1803,6 +1807,11 @@ class Expected(_coconut.collections.namedtuple("Expected", ("result", "error")){ if not self: raise self.error return self.result + def handle(self, err_type, handler): + """Recover from the given err_type by calling handler on the error to determine the result.""" + if not self and _coconut.isinstance(self.error, err_type): + return self.__class__(handler(self.error)) + return self class flip(_coconut_base_callable): """Given a function, return a new function with inverse argument order. If nargs is passed, only the first nargs arguments are reversed.""" @@ -1912,7 +1921,7 @@ def mapreduce(key_value_func, iterable, **kwargs): old_val = collection.get(key, _coconut_sentinel) if old_val is not _coconut_sentinel: if reduce_func is False: - raise ValueError("duplicate key " + repr(key) + " with reduce_func=False") + raise ValueError("mapreduce()/collectby() got duplicate key " + repr(key) + " with reduce_func=False") val = reduce_func(old_val, val) collection[key] = val return collection diff --git a/coconut/tests/src/cocotest/agnostic/suite.coco b/coconut/tests/src/cocotest/agnostic/suite.coco index a13d6c538..64da67027 100644 --- a/coconut/tests/src/cocotest/agnostic/suite.coco +++ b/coconut/tests/src/cocotest/agnostic/suite.coco @@ -1065,6 +1065,8 @@ forward 2""") == 900 haslocobj = hasloc([[1, 2]]) haslocobj |>= .iloc$[0]$[1] assert haslocobj == 2 + assert safe_raise_exc().error `isinstance` Exception + assert safe_raise_exc().handle(Exception, const 10).result == 10 # must come at end assert fibs_calls[0] == 1 diff --git a/coconut/tests/src/cocotest/agnostic/util.coco b/coconut/tests/src/cocotest/agnostic/util.coco index 0b67954b9..efa5b681d 100644 --- a/coconut/tests/src/cocotest/agnostic/util.coco +++ b/coconut/tests/src/cocotest/agnostic/util.coco @@ -1014,6 +1014,10 @@ def minus(a, b) = b - a def raise_exc(): raise Exception("raise_exc") +@safe_call$ +def safe_raise_exc() = + raise_exc() + def does_raise_exc(func): try: return func()