-
-
Notifications
You must be signed in to change notification settings - Fork 587
Description
Feature Request Type
- Core functionality
- Alteration (enhancement/optimization) of existing feature(s)
- New behavior
Description
Hi again!! I would like to propose the inclusion of some helper methods to strawberry.Maybe
to enhance the use of this type.
I believe that the philosophy which the team is trying to go towards greatly resembles the Rust
implementation of the Option
enum. See here the docs.
I believe that methods to unwrap
,is_some
, is_none
and similar (which can be found in the above link) would greatly benefit strawberry's ease of use when it comes to the Maybe
type.
I have started working on a implementation for it ,but since Im not very familiar with the library although want to start contributing more actively, I will leave here below the initial implementation to gather opinions from the team and whether this inclusions are even desirable.
from typing import Callable, NoReturn, overload, TypeVar, Generic, Any
U = TypeVar("U")
R = TypeVar("R")
def is_some(m: "Maybe[T]") -> bool:
return isinstance(m, Some)
def is_none(m: "Maybe[Any]") -> bool:
return m is None
@overload
def unwrap(m: "Some[T]") -> T: ...
@overload
def unwrap(m: None) -> NoReturn: ...
def unwrap(m):
"""Return contained value; raise ValueError if None."""
if isinstance(m, Some):
return m.value
raise ValueError("Tried to unwrap a missing value (None)")
@overload
def expect(m: "Some[T]", message: str) -> T: ...
@overload
def expect(m: None, message: str) -> NoReturn: ...
def expect(m, message: str):
"""Like unwrap() with a custom error message."""
if isinstance(m, Some):
return m.value
raise ValueError(message)
def unwrap_or(m: "Maybe[T]", default: T) -> T:
"""Return value if present; otherwise the eager default."""
return m.value if isinstance(m, Some) else default
def unwrap_or_else(m: "Maybe[T]", default: Callable[[], T]) -> T:
"""Return value if present; otherwise compute a lazy default()."""
return m.value if isinstance(m, Some) else default()
def unwrap_or_default(m: "Maybe[T]", *, factory: Callable[[], T]) -> T:
"""
Pythonic Option::unwrap_or_default.
Pass a zero-arg factory for T (e.g. int, str, list, dict, or any callable).
"""
return m.value if isinstance(m, Some) else factory()
@overload
def unwrap_unchecked(m: "Some[T]") -> T: ...
@overload
def unwrap_unchecked(m: None) -> T: ...
def unwrap_unchecked(m):
"""
UNSAFE: return value without checking.
Calling on None will raise AttributeError (undefined behavior in Rust terms).
"""
return m.value # type: ignore[attr-defined]
@overload
def map(m: "Some[T]", f: Callable[[T], U]) -> "Some[U]": ...
@overload
def map(m: None, f: Callable[[T], U]) -> None: ...
def map(m, f):
"""Apply f to the value if Some; pass through None."""
if isinstance(m, Some):
return Some(f(m.value))
return None
@overload
def inspect(m: "Some[T]", f: Callable[[T], None]) -> "Some[T]": ...
@overload
def inspect(m: None, f: Callable[[T], None]) -> None: ...
def inspect(m, f):
"""Call f(value) for side effects if Some; return original Maybe."""
if isinstance(m, Some):
f(m.value)
return m
return None
def map_or(m: "Maybe[T]", default: U, f: Callable[[T], U]) -> U:
"""If Some, return f(value); else return eager default."""
return f(m.value) if isinstance(m, Some) else default
def map_or_else(m: "Maybe[T]", default: Callable[[], U], f: Callable[[T], U]) -> U:
"""If Some, return f(value); else compute lazy default()."""
return f(m.value) if isinstance(m, Some) else default()
# I dont really like this approach with the class but couldn't figure out how to better include it
class MaybeMethods(Generic[T]):
__slots__ = ("_m",)
def __init__(self, m: "Maybe[T]") -> None:
self._m = m
# Introspection
def is_some(self) -> bool: return is_some(self._m)
def is_none(self) -> bool: return is_none(self._m)
# unwrapping Maybe
def unwrap(self) -> T: return unwrap(self._m)
def expect(self, message: str) -> T: return expect(self._m, message)
def unwrap_or(self, default: T) -> T: return unwrap_or(self._m, default)
def unwrap_or_else(self, default: Callable[[], T]) -> T: return unwrap_or_else(self._m, default)
def unwrap_or_default(self, *, factory: Callable[[], T]) -> T: return unwrap_or_default(self._m, factory=factory)
def unwrap_unchecked(self) -> T: return unwrap_unchecked(self._m)
# transformations
def map(self, f: Callable[[T], U]) -> "MaybeMethods[U]":
return MaybeMethods(map(self._m, f))
def inspect(self, f: Callable[[T], None]) -> "MaybeMethods[T]":
return MaybeMethods(inspect(self._m, f))
def map_or(self, default: U, f: Callable[[T], U]) -> U:
return map_or(self._m, default, f)
def map_or_else(self, default: Callable[[], U], f: Callable[[T], U]) -> U:
return map_or_else(self._m, default, f)
def methods(m: "Maybe[T]") -> MaybeMethods[T]:
return MaybeMethods(m)
__all__ += [
"is_some",
"is_none",
"unwrap",
"expect",
"unwrap_or",
"unwrap_or_else",
"unwrap_or_default",
"unwrap_unchecked",
"map",
"inspect",
"map_or",
"map_or_else",
"MaybeMethods",
"methods",
]
I believe this could perhaps be added to the strawberry.types.maybe
file although it may increase the complexity there.
Would love to hear your opinions here!!
Ty again!