2023-02-07 16:59:55 +00:00
|
|
|
from dataclasses import dataclass
|
2023-02-07 22:01:31 +00:00
|
|
|
from functools import partial, wraps
|
2023-02-10 01:46:55 +00:00
|
|
|
from operator import not_
|
|
|
|
from typing import Any, Callable, Concatenate, Generic, Iterator, ParamSpec, Sequence, Tuple, TypeVar
|
2023-02-07 16:59:55 +00:00
|
|
|
|
|
|
|
A = TypeVar('A')
|
|
|
|
B = TypeVar('B')
|
|
|
|
C = TypeVar('C')
|
|
|
|
D = TypeVar('D')
|
|
|
|
P = ParamSpec('P')
|
2023-02-07 22:01:31 +00:00
|
|
|
P1 = ParamSpec('P1')
|
|
|
|
P2 = ParamSpec('P2')
|
2023-02-07 16:59:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Compose
|
2023-02-07 22:01:31 +00:00
|
|
|
def c(f2: Callable[[B], C], f1: Callable[P, B]) -> Callable[P, C]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Compose two functions by passing the output of the second to the input of the first.
|
|
|
|
|
|
|
|
`c(f1, f2)(*args)` is equivalent to `f1(f2(*args))`.
|
|
|
|
|
|
|
|
This can also be thought of as mapping the output of a function using the first
|
|
|
|
parameter as a mapper function.
|
|
|
|
"""
|
2023-02-07 22:01:31 +00:00
|
|
|
@wraps(f1)
|
|
|
|
def inner(*args: P.args, **kwargs: P.kwargs) -> C:
|
|
|
|
return f2(f1(*args, **kwargs))
|
|
|
|
return inner
|
2023-02-07 16:59:55 +00:00
|
|
|
|
|
|
|
# Flip: (A -> B -> C) -> B -> A -> C
|
2023-02-07 22:01:31 +00:00
|
|
|
def flip(f: Callable[P1, Callable[P2, C]]) -> Callable[P2, Callable[P1, C]]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Reverse the order of the first two arguments of a curried function.
|
|
|
|
|
|
|
|
This only works with curried functions, so apply `cur2` or `cur3` before applying
|
|
|
|
`flip` if the arguments you want to flip are not curried.
|
|
|
|
"""
|
2023-02-07 22:01:31 +00:00
|
|
|
@wraps(f)
|
|
|
|
def inner1(*args2: P2.args, **kwargs2: P2.kwargs) -> Callable[P1, C]:
|
|
|
|
@wraps(f)
|
|
|
|
def inner2(*args1: P1.args, **kwargs1: P1.kwargs) -> C:
|
|
|
|
return f(*args1, **kwargs1)(*args2, **kwargs2)
|
|
|
|
return inner2
|
|
|
|
return inner1
|
2023-02-07 16:59:55 +00:00
|
|
|
|
2023-02-10 01:46:55 +00:00
|
|
|
# Identity function!
|
|
|
|
def ident(x: A) -> A:
|
2023-02-10 02:28:44 +00:00
|
|
|
"The identity function. Output is identical to input."
|
2023-02-10 01:46:55 +00:00
|
|
|
return x
|
|
|
|
|
2023-02-10 13:32:25 +00:00
|
|
|
def replace(replace_with: A) -> Callable[..., A]:
|
|
|
|
"""
|
|
|
|
Get a function which always returns a constant value, regardless of input
|
|
|
|
|
|
|
|
The argument `replace_with` is the value the the returned function should always
|
|
|
|
return. The returned function can be used as if having any arity, and will always
|
|
|
|
return the same value originally passed to `replace`.
|
|
|
|
"""
|
|
|
|
def constant(*args: Any, **kwargs: Any) -> A:
|
|
|
|
"Always return a constant value, typically the one passed to `replace`"
|
|
|
|
return replace_with
|
|
|
|
return constant
|
|
|
|
|
2023-02-07 16:59:55 +00:00
|
|
|
# Partial Appliaction shorthand
|
|
|
|
p = partial
|
2023-02-10 02:28:44 +00:00
|
|
|
"An alias for partial application"
|
2023-02-07 16:59:55 +00:00
|
|
|
|
|
|
|
# Two and three-argument currying
|
|
|
|
# Defining these pointfree fucks up the types btw
|
2023-02-07 22:01:31 +00:00
|
|
|
def cur2(f: Callable[Concatenate[A, P], C]) -> Callable[[A], Callable[P, C]]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Perform two-argument currying.
|
|
|
|
|
|
|
|
For example, a function from (A, B) -> C becomes a function A -> B -> C. This can
|
|
|
|
also be though of as simply moving the first argument of a function out front, since
|
|
|
|
it preserves any arguments after the first. That is, a function (A, B, C, kw=D) -> E
|
|
|
|
becomes the function A -> (B, C, kw=D) -> E after being curried using this function.
|
|
|
|
|
|
|
|
Can also be used as an annotation.
|
|
|
|
"""
|
2023-02-07 16:59:55 +00:00
|
|
|
return p(p, f) #type:ignore
|
2023-02-07 22:01:31 +00:00
|
|
|
def cur3(f: Callable[Concatenate[A, B, P], D]) -> Callable[[A], Callable[[B], Callable[P, D]]]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Perform three-argument currying.
|
|
|
|
|
|
|
|
See `cur2` for an explaination of how this works.
|
|
|
|
"""
|
2023-02-07 16:59:55 +00:00
|
|
|
return p(p, p, f) #type:ignore
|
|
|
|
|
|
|
|
# Curried versions of map & filter with stricter types
|
|
|
|
def p_map(f: Callable[[A], B]) -> Callable[[Sequence[A]], Sequence[B]]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"A curried version of the built in `map` function"
|
2023-02-07 16:59:55 +00:00
|
|
|
return partial(map, f) #type: ignore
|
|
|
|
|
|
|
|
def p_filter(f: Callable[[A], bool]) -> Callable[[Sequence[A]], Sequence[A]]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"A curried version of the built in `filter` function"
|
2023-02-07 16:59:55 +00:00
|
|
|
return partial(filter,f) #type: ignore
|
|
|
|
|
|
|
|
# Normal Accessors
|
|
|
|
@cur2
|
|
|
|
def indx(i: int, s: Sequence[A]) -> A:
|
2023-02-10 02:28:44 +00:00
|
|
|
"A curried version of the getitem function"
|
2023-02-07 16:59:55 +00:00
|
|
|
return s[i]
|
2023-02-10 02:28:44 +00:00
|
|
|
|
2023-02-07 16:59:55 +00:00
|
|
|
fst = indx(0)
|
2023-02-10 02:28:44 +00:00
|
|
|
"Get the first element of a tuple/sequence"
|
|
|
|
|
2023-02-07 16:59:55 +00:00
|
|
|
snd = indx(1)
|
2023-02-10 02:28:44 +00:00
|
|
|
"Get the second element of a tuple/sequence"
|
2023-02-07 16:59:55 +00:00
|
|
|
|
|
|
|
# Semantic Editor Combinators
|
|
|
|
class SemEdComb:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
A tool which approximates semantic editor combinators in python.
|
|
|
|
|
|
|
|
Please read
|
|
|
|
https://web.archive.org/web/20221202200001/http://conal.net/blog/posts/semantic-editor-combinators
|
|
|
|
for context.
|
|
|
|
|
|
|
|
Since Python has no infix function composition, using this pattern can get pretty
|
|
|
|
ugly. This class abuses python's ability to override the property accessor (.) in
|
|
|
|
order to approximate semantic editor combinators.
|
|
|
|
|
|
|
|
Using this class, you can write `result.first.pmap(reverse)(myList)` to perform an
|
|
|
|
effect equivalent to `result . first reverse myList` in haskell.
|
|
|
|
|
|
|
|
Unfortunately, due to limitations of Python's type system, this class is largely
|
|
|
|
untyped.
|
|
|
|
"""
|
2023-02-07 16:59:55 +00:00
|
|
|
class Inner():
|
2023-02-10 02:28:44 +00:00
|
|
|
"A chain of semantic editor combinators already paired with a map function"
|
2023-02-07 16:59:55 +00:00
|
|
|
def __init__(self, f: Callable, name: str):
|
|
|
|
self.f = f
|
|
|
|
self.name = name
|
|
|
|
def and_then(self, other: 'SemEdComb.Inner') -> 'SemEdComb.Inner':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Composes this with another `SemEdComb.Inner`"
|
2023-02-07 16:59:55 +00:00
|
|
|
return SemEdComb.Inner(c(other.f, self.f), self.name + ' and ' + other.name)
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f"SemEdComb*({self.name})"
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
return self.f(*args, **kwargs)
|
|
|
|
|
|
|
|
def __init__(self, f: Callable[[Callable],Callable], name: str):
|
|
|
|
self.f = f
|
|
|
|
self.name = name
|
|
|
|
|
|
|
|
def _c(self, next_f: Callable[[Callable], Callable], next_fname: str) -> 'SemEdComb':
|
|
|
|
return SemEdComb(c(self.f, next_f), self.name + next_fname)
|
|
|
|
|
|
|
|
RESULT = cur2(c)
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the result of a function"
|
|
|
|
|
2023-02-07 16:59:55 +00:00
|
|
|
ARG = flip(RESULT)
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the argument of a function"
|
|
|
|
|
2023-02-07 16:59:55 +00:00
|
|
|
ALL = p_map
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map every element of a list"
|
2023-02-07 16:59:55 +00:00
|
|
|
|
|
|
|
@cur3
|
|
|
|
@staticmethod
|
|
|
|
def INDEX(i, f, arr):
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the ith element of a mutable sequence"
|
2023-02-07 16:59:55 +00:00
|
|
|
arr[i] = f(arr[i])
|
|
|
|
return arr
|
|
|
|
|
|
|
|
@cur3
|
|
|
|
@staticmethod
|
|
|
|
def INDEX_TUP(i: int, f: Callable[[Any], Any], tup: Tuple) -> Tuple:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the ith element of an immutable sequence"
|
2023-02-07 16:59:55 +00:00
|
|
|
l = list(tup)
|
|
|
|
l[i] = f(l[i])
|
|
|
|
return (*l,)
|
|
|
|
|
|
|
|
@cur2
|
|
|
|
@staticmethod
|
|
|
|
def FIRST(f: Callable[[A], C], tup: Tuple[A, B]) -> Tuple[C, B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the first element of a two-tuple"
|
2023-02-07 16:59:55 +00:00
|
|
|
return (f(tup[0]), tup[1])
|
|
|
|
|
|
|
|
@cur2
|
|
|
|
@staticmethod
|
|
|
|
def SECOND(f: Callable[[B], C], tup: Tuple[A, B]) -> Tuple[A, C]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the second element of a two-tuple"
|
2023-02-07 16:59:55 +00:00
|
|
|
return (tup[0], f(tup[1]))
|
|
|
|
|
|
|
|
@property
|
|
|
|
def result(self) -> 'SemEdComb':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the result of a function"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self._c(SemEdComb.RESULT, '.result')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def arg(self) -> 'SemEdComb':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the argument of a function"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self._c(SemEdComb.ARG, '.arg')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def all(self) -> 'SemEdComb':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map every element of a list"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self._c(SemEdComb.ALL, '.all')
|
|
|
|
|
|
|
|
def index(self, i) -> 'SemEdComb':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the ith element of a mutable sequence"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self._c(SemEdComb.INDEX(i), f'.index({i})')
|
|
|
|
|
|
|
|
def index_tup(self, i) -> 'SemEdComb':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the ith element of an immutable sequence"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self._c(SemEdComb.INDEX_TUP(i), f'.index_tup({i})')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def first(self) -> 'SemEdComb':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the first element of a two-tuple"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self._c(SemEdComb.FIRST, f'.first')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def second(self) -> 'SemEdComb':
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the second element of a two-tuple"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self._c(SemEdComb.SECOND, f'.second')
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return f"SemEdComb({self.name})"
|
|
|
|
|
|
|
|
def pmap(self, mapper):
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Set the mapper function, but don't call it yet
|
|
|
|
|
|
|
|
The name is short for partial map.
|
|
|
|
|
|
|
|
See also: `map`
|
|
|
|
"""
|
2023-02-07 16:59:55 +00:00
|
|
|
return SemEdComb.Inner(self.f(mapper), self.name)
|
|
|
|
|
|
|
|
def map(self, mapper, thing_to_map) -> Callable:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Apply the chain of combinators to a mapper and a mappee"
|
2023-02-07 16:59:55 +00:00
|
|
|
return self.pmap(mapper)(thing_to_map)
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
return self.f(*args, **kwargs)
|
|
|
|
|
2023-02-10 02:28:44 +00:00
|
|
|
# Pre-constructed base semantic editor combinators
|
2023-02-07 16:59:55 +00:00
|
|
|
result = SemEdComb(SemEdComb.RESULT, 'result')
|
|
|
|
arg = SemEdComb(SemEdComb.ARG, 'arg')
|
|
|
|
index = lambda i: SemEdComb(SemEdComb.INDEX(i), f'index({i})')
|
|
|
|
index_tup = lambda i: SemEdComb(SemEdComb.INDEX_TUP(i), f'index_tup({i})')
|
|
|
|
first = SemEdComb(SemEdComb.FIRST, 'first')
|
|
|
|
second = SemEdComb(SemEdComb.SECOND, 'second')
|
2023-02-07 22:01:46 +00:00
|
|
|
all_ = SemEdComb(SemEdComb.ALL, 'all')
|
2023-02-07 16:59:55 +00:00
|
|
|
|
|
|
|
# Tail call optimizing recursion
|
|
|
|
@dataclass
|
|
|
|
class Recur(Generic[P]):
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Indicate that the function this is returned from should be called again with new args.
|
|
|
|
|
|
|
|
Exclusively used with `tco_rec()`
|
|
|
|
"""
|
2023-02-07 16:59:55 +00:00
|
|
|
def __init__(self, *args: P.args, **kwargs: P.kwargs):
|
|
|
|
self.args = args
|
|
|
|
self.kwargs = kwargs
|
|
|
|
|
|
|
|
@dataclass(frozen = True)
|
|
|
|
class Return(Generic[B]):
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Indicate that the function this is returned from should return this value
|
|
|
|
|
|
|
|
Exclusively used with `tco_rec()`
|
|
|
|
"""
|
2023-02-07 16:59:55 +00:00
|
|
|
val: B
|
|
|
|
|
2023-02-07 22:01:31 +00:00
|
|
|
def tco_rec(f: Callable[P, Recur[P] | Return[B]]) -> Callable[P, B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Run a tail-recursive function in a mannor which will not overflow the stack.
|
|
|
|
|
|
|
|
Wraps a function in a loop which transforms its return type. The function is expected
|
|
|
|
to return an instance of `Recur` rather than calling itself to recur. The arguments
|
|
|
|
passed to the returned `Recur` instance become the arguments to the next iteration of
|
|
|
|
the function call. When the function is ready to return for real, it should return an
|
|
|
|
instance of `Return`.
|
|
|
|
|
|
|
|
The function will be transformed by `tco_rec` to look as if it is a normal function.
|
|
|
|
"""
|
2023-02-07 22:01:31 +00:00
|
|
|
@wraps(f)
|
|
|
|
def tco_loop(*args: P.args, **kwargs: P.kwargs) -> B:
|
|
|
|
while True:
|
|
|
|
match f(*args, **kwargs):
|
|
|
|
case Recur(args=args, kwargs=kwargs): #type:ignore
|
|
|
|
pass
|
|
|
|
case Return(val=val)|val:
|
|
|
|
return val #type:ignore
|
2023-02-09 02:32:24 +00:00
|
|
|
return tco_loop
|
|
|
|
|
|
|
|
# Options!
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class Some(Generic[A]):
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
The positive part of an optional datatype
|
|
|
|
|
|
|
|
Component of `Option` and counterpart of `None`
|
|
|
|
"""
|
2023-02-09 02:32:24 +00:00
|
|
|
val: A
|
2023-02-10 02:28:44 +00:00
|
|
|
|
2023-02-09 02:32:24 +00:00
|
|
|
Option = Some[A] | None
|
2023-02-10 02:28:44 +00:00
|
|
|
"An Option datatype, aka Maybe"
|
|
|
|
|
2023-02-09 02:32:24 +00:00
|
|
|
def map_opt(f: Callable[[A], B], o: Option[A]) -> Option[B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Map the contents of an optional data type. Has no effect on `None`
|
|
|
|
"""
|
2023-02-09 02:32:24 +00:00
|
|
|
match o:
|
|
|
|
case Some(val):
|
|
|
|
return Some(f(val))
|
|
|
|
case none:
|
|
|
|
return none
|
|
|
|
def bind_opt(f: Callable[[A], Option[B]], o: Option[A]) -> Option[B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"wow! monads! (aka 'and_then')"
|
2023-02-09 02:32:24 +00:00
|
|
|
match o:
|
|
|
|
case Some(val):
|
|
|
|
return f(val)
|
|
|
|
case none:
|
|
|
|
return none
|
2023-02-10 02:28:44 +00:00
|
|
|
def note(e: B, o: Option[A]) -> 'Result[A, B]':
|
|
|
|
"Convert an `Option` to a `Result` by attaching an error to the `None` variants"
|
2023-02-09 02:44:43 +00:00
|
|
|
match o:
|
|
|
|
case Some(val):
|
|
|
|
return Ok(val)
|
|
|
|
case None:
|
|
|
|
return Err(e)
|
2023-02-09 02:32:24 +00:00
|
|
|
|
|
|
|
# Results!
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class Ok(Generic[A]):
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
The positive part of a result (either) datatype
|
|
|
|
|
|
|
|
Component of `Result` and counterpart of `Err`
|
|
|
|
"""
|
2023-02-09 02:32:24 +00:00
|
|
|
val: A
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
class Err(Generic[B]):
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
The error part of a result (either) datatype
|
|
|
|
|
|
|
|
Component of `Result` and counterpart of `Ok`
|
|
|
|
"""
|
2023-02-09 02:32:24 +00:00
|
|
|
err: B
|
2023-02-10 01:46:55 +00:00
|
|
|
def __bool__(self):
|
|
|
|
return False
|
2023-02-09 02:32:24 +00:00
|
|
|
Result = Ok[A] | Err[B]
|
2023-02-10 02:28:44 +00:00
|
|
|
"A Result datatype, aka Either"
|
2023-02-09 02:32:24 +00:00
|
|
|
def map_res(f: Callable[[A], C], r: Result[A, B]) -> Result[C, B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the success value of a result"
|
2023-02-09 02:32:24 +00:00
|
|
|
match r:
|
|
|
|
case Ok(val):
|
|
|
|
return Ok(f(val))
|
|
|
|
case not_okay:
|
|
|
|
return not_okay
|
|
|
|
def bind_res(f: Callable[[A], Result[C, B]], r: Result[A, B]) -> Result[C, B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Perform an fallible operation for successful results."
|
2023-02-09 02:32:24 +00:00
|
|
|
match r:
|
|
|
|
case Ok(val):
|
|
|
|
return f(val)
|
|
|
|
case not_okay:
|
2023-02-09 02:44:43 +00:00
|
|
|
return not_okay
|
|
|
|
def map_err(f: Callable[[B], C], r: Result[A, B]) -> Result[A, C]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Map the error value of a result"
|
2023-02-09 02:44:43 +00:00
|
|
|
match r:
|
|
|
|
case Err(e):
|
|
|
|
return Err(f(e))
|
|
|
|
case oki_doke:
|
|
|
|
return oki_doke
|
|
|
|
def hush(r: Result[A, Any]) -> Option[A]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"Convert a `Result` to an `Option` by converting any errors to `None`"
|
2023-02-09 02:44:43 +00:00
|
|
|
match r:
|
|
|
|
case Ok(val):
|
|
|
|
return Some(val)
|
|
|
|
case not_okay:
|
2023-02-10 01:46:55 +00:00
|
|
|
return None
|
|
|
|
def try_(handle: Callable[[Exception], B], f: Callable[P, A], *args: P.args, **kwargs: P.kwargs) -> Result[A, B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Try-catch in a function! Attempt to perform and operation, and `Err` on failure
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
handle - A function which handles any exceptions which arise. The return type is
|
|
|
|
what will be wrapped into the resulting `Err`. This is not called if nothing
|
|
|
|
goes wrong.
|
|
|
|
f - The fallible function to try. If this succeeds without raising an error, that
|
|
|
|
value is returned in an `Ok`. If this raises an exception, that exception
|
|
|
|
will be passed to `handle`.
|
|
|
|
args - Will be passed to `f` when it is called.
|
|
|
|
kwargs - Will be passed to `f` when it is called.
|
|
|
|
"""
|
2023-02-10 01:46:55 +00:00
|
|
|
try:
|
|
|
|
return Ok(f(*args, **kwargs))
|
|
|
|
except Exception as e:
|
|
|
|
return Err(handle(e))
|
|
|
|
def unwrap_r(r: Result[A, Any]) -> A:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Assert that a `Result` is `Ok` and return it's value.
|
|
|
|
|
|
|
|
Throws:
|
|
|
|
`AssertionError` - The result was NOT okay. The `AssertionError` will have two
|
|
|
|
arguments: The first is a string to make it more obvious what happened. The
|
|
|
|
second is the error that was stored in the `Err`.
|
|
|
|
"""
|
2023-02-10 01:46:55 +00:00
|
|
|
match r:
|
|
|
|
case Ok(val):
|
|
|
|
return val
|
|
|
|
case Err(e):
|
2023-02-10 02:28:44 +00:00
|
|
|
raise AssertionError(f'Tried to unwrap an error: ', e)
|
2023-02-10 01:46:55 +00:00
|
|
|
def sequence(s: Sequence[Result[A, B]]) -> Result[Iterator[A], B]:
|
2023-02-10 02:28:44 +00:00
|
|
|
"""
|
|
|
|
Convert a list of results into a result of a list.
|
|
|
|
|
|
|
|
If the input sequence contains only `Ok` results, then the output is similarly `Ok`,
|
|
|
|
and contains a list of all the unwrapped values of the `Ok`s. If there are any
|
|
|
|
errors, proccessing of the sequence is immediately stopped, and the first error
|
|
|
|
encountered is returned.
|
|
|
|
"""
|
2023-02-10 01:46:55 +00:00
|
|
|
if all(s):
|
2023-02-10 02:28:44 +00:00
|
|
|
return Ok(map(unwrap_r, s))
|
2023-02-10 01:46:55 +00:00
|
|
|
else:
|
|
|
|
o = next(filter(not_, s))
|
|
|
|
assert isinstance(o, Err)
|
|
|
|
return o
|