-
-
Notifications
You must be signed in to change notification settings - Fork 120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding some utility methods to Maybe/Result #1114
Comments
I think |
I'll illustrate with this example: from typing import NamedTuple
class User(NamedTuple):
id: int
nickname: Maybe[str]
first_name: Maybe[str]
last_name: str
joined: datetime
job: Maybe[str]
location: Maybe[int] SetdefaultThere difference with user = user._replace(
status=user.status.setdefault('Hello World')
)
# equivalent to
user = user._replace(
status=user.status.success_type(user.status.value_or('Hello World'))
) OrLike Python's # same behavior as python's `or` operator: returns the first success value, otherwise the second
salutation: Maybe[str] = user.nickname.or_(user.first_name) (The difference with AndI can't think of a super convincing case here, but it'd make sense to have it as a complement to # same behavior as python's `and` operator: returns the first failure value, otherwise the second
work_location: Maybe[str] = user.job.and_(user.location) |
Thanks a lot for the clarifications, I should take a look at the issue slightly later in the morning :) I am happy to implement |
@CucumisSativus Thanks for the feedback! 😉 If you don't mind though, I was actually really looking forward to implementing these, after being given the green light. I'm really interested in diving into edit: reading back I see I didn't make this clear before. How about we each take half of whatever methods get approved to add? |
@ariebovenberg Sorry I rushed a bit and started with |
🎉 |
@sobolevn agree to implement all methods?
Then we can start the work. |
@ariebovenberg you would love to have as much utils as possible, but. There's one very important step here: We really prefer to have functions over methods. For example. We can create The difference is not just semantical. If we add these as methods, we would have to add HKT interfaces on top of that. And make our classes more complex by adding more utility methods. Functions on the other hand only require existing interfaces and do not polute classes with extra methods. |
We store utils here: https://github.com/dry-python/returns/tree/master/returns/methods |
I'm definitely willing to defer to your preference on this matter. But let's see if I can make the case for methods... In the absence of first-class support for currying and composition in Python, I feel methods have an advantage on readability. x.filter(str.isdigit).or_(y).setdefault('foo')
setdefault(or_(filter(x, str.isdigit), y), 'foo') # less readable IMHO For these particular methods:
|
Awesome! 👍
Ok, let's make it the very last item to implement then! We can think some more about different choices.
It is still fine to use it as a function, because we have |
@CucumisSativus probably best if you pick up |
I am happy to pick For result I would see it like this >>> Success(1).zip(Success("str"))
Success(1, "str")
>>>Failure(Exception("a")).zip(Success(1))
Failure(Exception("a")
>>>Failure(Exception("a")).zip(Failure(Exception("b"))
Failure(Exception("a"))
>>>Success(1).zip(Failure(Exception("a"))
Failure(Exception("a")) For IO >>>IO(1).zip(IO(2))
IO((1,2)) With @sobolevn @ariebovenberg what do you think about it? |
@CucumisSativus Interesting! I was thinking about the signature But Makes sense to implement for |
Also a decision to make: if we make
|
any news here? I would love to use those utilities 🤩 |
@djbrown I suppose not 😅 . Feel free to pick this up if you like, I probably won't have time for it the coming months. |
I feel like a lot of these can easily be created with do notation can't they? # Zip
Maybe.do(
(first, second)
for first in Success(1)
for second in Success("Str")
)
# And
Maybe.do(
first and second
for first in Success(5)
for second in Success(9)
)
# Or
Maybe.do(
first or second
for first in Success(5)
for second in Success(9)
) It seems like there's a common pattern here, so maybe a function to lift a function to operate on two monads could be more useful. def map_n(f):
def wrapped(*args):
return Maybe.do(
f(next(arg) for arg in args)
)
return wrapped
map_n(zip)(Some(1), Some(2)) I'm slightly hesitant about adding primitives if they're easy to lift though? What do you all think? (Relatedly, I had a hard time doing any of this in pointfree syntax.. Let me know if anyone can think think of a cleaner approach) And to me filter seems inconsistent: why would it return def get_prices_from_db() -> Maybe[List[int]]:
...
prices = get_prices_from_db()
prices.filter(lambda price: price > 5).map(len) # This should probably return Some(0) instead of Nothing if the data existed In other words, Setdefault seems very promising but I agree it needs a new name. Rust has a similar function that they call |
>>> Some(1).and_(Some(9))
Some(9)
>>> Some(0).and_(Some(9))
Some(9) # it shouldn't matter that 0 is falsey, the `and_` method only cares about Some/Nothing
>>> Some(9.4).filter(float.is_integer)
Nothing
>>> Some(3.0).filter(float.is_integer)
Some(3.0)
>>> Nothing.filter(float.is_integer)
Nothing iterable values aren't special: >>> def has_at_least_3_items(a: list) -> bool:
... return len(a) >= 3
...
>>> Some([5, 3]).filter(has_at_least_3_items)
Nothing
>>> Some([1, 4, 8, 2]).filter(has_at_least_3_items)
Some([1, 4, 8, 2])
>>> Nothing.filter(has_at_least_3_items)
Nothing
|
Ah that makes much more sense. Thanks @ariebovenberg! Definitely agree on the appeal. I was worried about adding methods that act on the value of containers to a container since that seemed superfluous. Now that I know they aren't acting on the value of the containers I see how they're worth it :) Yeah the lazy evaluation was going to be my next question. I imagine that was for performance implications: don't need to calculate or allocate for the condition unless it's met. What do you think about that? I'm leaning towards a value/eager evaluation like your proposal since it's simpler to use. And while we're at it, looks like rust has some similar functions that look nice: like |
Have you considered adding some handy utility functions to
Maybe
andResult
?In using
returns
I'm missing many helpful methods you can find in Rust, for example.Adding a few of these could increase the usability of the containers of
returns
.Related: #1091
Here they are, in my subjective order of usefulness:
Setdefault
Set a value if one is not yet present.
The name
setdefault
would be consistent with the python dict method.(Although it might be good to choose another name to make clear that
the method doesn't mutate.)
Zip
Combine the values from two containers into a tuple, if both are successful.
Filter
Keep a value based on a predicate
And
AND
logic operationOr
OR
logic operationThe text was updated successfully, but these errors were encountered: