Skip to content

either Module Reference

do

Bases: Generic[L]

Lift a function with do notation

@do[LeftType]()
def myfn(e: Either[LeftType, int]):
  value = e.unsafe()
  return value + 1

myfn(Left('err')) # Left('err')
myfn(Right(1)) # Right(2)

Source code in haskellian/src/haskellian/either/do_notation.py
class do(Generic[L]):
  """Lift a function with do notation
  ```
  @do[LeftType]()
  def myfn(e: Either[LeftType, int]):
    value = e.unsafe()
    return value + 1

  myfn(Left('err')) # Left('err')
  myfn(Right(1)) # Right(2)
  ```
  """
  @overload
  def __call__(self, fn: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Coroutine[None, None, Either[L, R]]]: # type: ignore
    ...
  @overload
  def __call__(self, fn: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[Either[L, R]]]: # type: ignore
    ...
  @overload
  def __call__(self, fn: Callable[P, R]) -> Callable[P, Either[L, R]]:
    ...

  def __call__(self, fn): # type: ignore
    if iscoroutinefunction(fn):
      @wraps(fn)
      async def _wrapper(*args: P.args, **kwargs: P.kwargs):
        try:
          return Right(await fn(*args, **kwargs))
        except IsLeft as e:
          return Left(e.value)
      return _wrapper
    else:
      @wraps(fn)
      def wrapper(*args: P.args, **kwargs: P.kwargs):
        try:
          return Right(fn(*args, **kwargs))
        except IsLeft as e:
          return Left(e.value)
      return wrapper

safe

Bases: Generic[Err]

A decorator to catch exceptions and return them as Left.

@E.safe[OSError]()
def safe_write(path: str, content: bytes):
  with open(path, 'wb') as f:
    f.write(content)

result = safe_write('file.txt', b'content') # Either[OSError, None]
Source code in haskellian/src/haskellian/either/funcs.py
class safe(Generic[Err]):
  """A decorator to catch exceptions and return them as `Left`.

  ```python
  @E.safe[OSError]()
  def safe_write(path: str, content: bytes):
    with open(path, 'wb') as f:
      f.write(content)

  result = safe_write('file.txt', b'content') # Either[OSError, None]
  ```
  """

  @property
  def exc(self) -> type[Err]:
    return get_args(self.__orig_class__)[0] # type: ignore (yep, typing internals are messed up)

  @overload
  def __call__(self, fn: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Coroutine[None, None, Either[Err, R]]]: # type: ignore
    ...
  @overload
  def __call__(self, fn: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[Either[Err, R]]]: # type: ignore
    ...
  @overload
  def __call__(self, fn: Callable[P, R]) -> Callable[P, Either[Err, R]]:
    ...

  def __call__(self, fn): # type: ignore
    if iscoroutinefunction(fn):
      @wraps(fn)
      async def _wrapper(*args: P.args, **kwargs: P.kwargs):
        try:
          return Right(await fn(*args, **kwargs))
        except Exception as e:
          if isinstance(e, self.exc):
            return Left(e)
          raise e
      return _wrapper
    else:
      @wraps(fn)
      def wrapper(*args: P.args, **kwargs: P.kwargs):
        try:
          return Right(fn(*args, **kwargs))
        except Exception as e:
          if isinstance(e, self.exc):
            return Left(e)
          raise e
      return wrapper

filter(eithers)

Source code in haskellian/src/haskellian/either/funcs.py
def filter(eithers: Iterable[Either[L, R]]) -> Iterable[R]:
  """"""
  for e in eithers:
    match e:
      case Right(value):
        yield value

filter_lefts(eithers)

Source code in haskellian/src/haskellian/either/funcs.py
def filter_lefts(eithers: Iterable[Either[L, R]]) -> Iterable[L]:
  """"""
  for e in eithers:
    match e:
      case Left(err):
        yield err

maybe(x)

Converts a nullable value to Either

Source code in haskellian/src/haskellian/either/funcs.py
def maybe(x: R | None) -> 'Either[None, R]':
  """Converts a nullable value to `Either`"""
  return Left(None) if x is None else Right(x)

sequence(eithers)

List of lefts if any, otherwise list of all rights.

E.sequence([Left(1), Right(2), Right(3), Left(4)]) # Left([1, 4])
E.sequence([Right(2), Right(3)]) # Right([2, 3])
Source code in haskellian/src/haskellian/either/funcs.py
def sequence(eithers: Iterable[Either[L, R]]) -> Either[list[L], list[R]]:
  """List of lefts if any, otherwise list of all rights.

  ```python
  E.sequence([Left(1), Right(2), Right(3), Left(4)]) # Left([1, 4])
  E.sequence([Right(2), Right(3)]) # Right([2, 3])
  ```
  """
  lefts: list[L] = []
  rights: list[R] = []
  for x in eithers:
    x.match(lefts.append, rights.append)
  return Right(rights) if lefts == [] else Left(lefts)

take_while(eithers)

Source code in haskellian/src/haskellian/either/funcs.py
def take_while(eithers: Iterable[Either[L, R]]) -> Iterable[R]:
  """"""
  for e in eithers:
    match e:
      case Right(x):
        yield x
      case _:
        return

is_left(either)

Type guard for Left

Source code in haskellian/src/haskellian/either/narrowing.py
7
8
9
def is_left(either: Either[L, R]) -> TypeGuard[Left[L, R]]:
  """Type guard for `Left`"""
  return either.tag == 'left'

is_right(either)

Type guard for Right

Source code in haskellian/src/haskellian/either/narrowing.py
def is_right(either: Either[L, R]) -> TypeGuard[Right[L, R]]:
  """Type guard for `Right`"""
  return either.tag == 'right'