-
-
Notifications
You must be signed in to change notification settings - Fork 71
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
Result is too restrictive, introduce Either type? #355
Comments
I'm okay with adding this feature to PSL, however, i don't think i have the time to implement this myself, PRs are welcome :) |
You might find some inspiration in this version as well |
@veewee Thank you! This lib truly is a gem, however it has built some sort of "functional infrastructure" with Functor, Monad typeclasses etc. I am not sure we can and should recreate something like that in PSL. |
personally, I imagine the Either API in PSL to look like this: final class Either<Tr, Tl> {
// static factories
public static function right<T>(T $val): Either<T, _>;
public static function left<T>(T $val): Either<_, T>;
// Throw MissingValueException if not right
public function getRight(): Tr;
// Same
public function getLeft(): Tl;
public function isRight(): bool;
public function isLeft(): bool;
// could be useful
public function wrapRight(): Result<Tr, MissingValueException>;
public function wrapLeft(): Result<Tl, MissingValueException>;
} |
IMO to complete the API it would be great to have at least those: /** @var callable(Tr):TrNew $map */
public function mapRight(callable $map): Either<Tr|TrNew, Tl>;
/** @var callable(Tl):TlNew $map */
public function mapLeft(callable $map): Either<Tr, Tl|TlNew>;
public function getEither(): Tr|Tl; BTW, by having |
/** @var callable(Tr):Either<TrNew, TlNew> $map */
public function flatMapRight(callable $map): Either<Tr|TrNew, Tl|TlNew>;
/** @var callable(Tl):Either<TrNew, TlNew> $map */
public function flatMapLeft(callable $map): Either<Tr|TrNew, Tl|TlNew>; may also be desirable |
Maybe also a |
I think we should, getEither would return |
The whole point of Either is that you don't know which of the two outcomes will you get beforehand. You can still process its individual "branches" using mapLeft() / mapRight() |
I'm in favor of public function unwrap(): Tr|Tl;
public function unwrapRight(): Tr; // throws otherwise
public function unwrapLeft(): Tr; // throws otherwise
public function unwrapRightOr<T>(T $value): Tr|T; // returns `$value` otherwise
public function unwrapLeftOr<T>(T $value): Tr|T; // returns `$value` otherwise |
rust either type has many other methods that we could also implement ( see: https://docs.rs/either/latest/either/enum.Either.html#method.left_or_else ) public function getRight(): Tr; // throws otherwise
public function getLeft(): Tr; // throws otherwise
public function getRightOr<T>(T $value): Tr|T; // returns `$value` otherwise
public function getLeftOr<T>(T $value): Tl|T; // returns `$value` otherwise
public function getRightOrThen<T>((Closure(): T) $f): Tr|T; // returns `$f` results otherwise
public function getLeftOrThen<T>((Closure(): T) $f): Tl|T; // returns `$f` results otherwise
public function getRightOrElse((Closure(Tl): Tr) $f): Tr; // returns `$f` results otherwise
public function getLeftOrElse((Closure(Tr): Tl) $f): Tl; // returns `$f` results otherwise
public function map<Tm>((Closure(Tr|Tl): Tm) $f): Either<Tm, Tm>;
public function mapRight<Tr2>((Closure(Tr): Tr2) $f): Either<Tr2, Tl>;
public function mapLeft<Tl2>((Closure(Tl): Tl2) $f): Either<Tr, Tl2>; ( note: all methods prefixed with |
example ( stupid, and you shouldn't use Either in this case, but it gives you the idea 😛 ) : function get_organization_owner(Either<Organization, Project> $either): Owner
{
return $either->getLeftOrElse(
fn(Project $project): Organization => $project->getOrganization()
)->getOwner();
}
$owner = get_organization_owner(Either::left($organization));
$owner = get_organization_owner(Either::right($project)); |
@someniatko wdyt? |
@azjezz it totally makes sense to me with those three |
what do you think about the other ones ( |
Hmm, I looked a bit more closely, the |
As to |
If we implement an $either
->unwrapRight()
->orThen(fn() => computeSomething()); I am not sure about |
By the way, if Just summarizing the discussion, the full API for could look like this: // static factories
public static function right<T>(T $val): Either<T, _>;
public static function left<T>(T $val): Either<_, T>;
public function isRight(): bool;
public function isLeft(): bool;
public function map<Tm>((Closure(Tr|Tl): Tm) $f): Either<Tm, Tm>;
public function mapRight<Tr2>((Closure(Tr): Tr2) $f): Either<Tr2, Tl>;
public function mapLeft<Tl2>((Closure(Tl): Tl2) $f): Either<Tr, Tl2>;
public function flatMap<Tmr,Tml>((Closure(Tr|Tl): Either<Tmr,Tml>) $f): Either<Tmr, Tml>;
public function flatMapRight<Tmr,Tml>((Closure(Tr): Either<Tmr,Tml>) $f): Either<Tr|Tmr, Tl|Tml>;
public function flatMapLeft<Tmr,Tml>((Closure(Tl): Either<Tmr,Tml>) $f): Either<Tr|Tmr, Tl|Tml>;
public function proceed<Tm>((Closure(Tr): Tm) $fr, (Closure(Tl): Tm) $fl): Tm;
public function unwrapRight(): Option<Tr>;
public function unwrapLeft(): Option<Tl>;
// could be useful
public function wrapRight(): Result<Tr, MissingValueException>;
public function wrapLeft(): Result<Tl, MissingValueException>; |
@someniatko |
Is someone planning to work on this and has the final interface been decided? |
I dont think anyone is planning on working on this at the moment, so feel free to pick it up. |
I want to type a function/method representing action which may fail, but not from pure technical perspective, rather from the business logic one. For instance, "register new user" action may succeed, or may fail for reasons like "username is already taken" or "password is too short" etc. I don't want to split this action into two steps, a validation method and then the actual action method, because I don't like creating an intermediate state which is invalid (however others may want to do it in two steps — it's just style preference, I made this choice).
This lib has a Result type which looks like it's exactly what I need, but it's unfortunately not: its Failure branch is limited only to exceptions (more correctly, throwables). However this is not how I view the business logic: for me exceptions are, well, exceptional situations: e.g. "database connection failed" or "file could not be read", "remote API returned HTTP 500" etc. Purely technical ones. Or could be business-logic related, but unexpected, e.g. some entity's invariant was violated due to some logic error in the code, or maybe someone edited its state directly in the database etc. This is the other thing though, i don't want any exception-related fluff here: traces, codes etc. Such action "failures" are no more than just normal operation of my system.
Describe the solution you'd like
Ideally I'd like to extend Result failure type to accept not only Exceptions. This is not possible due to possible backwards-compatibility break. Instead, a new type can be introduced:
Either
, withLeft
andRight
variants as inspired by Haskell.Describe alternatives you've considered
I have my own library for this, https://github.com/someniatko/php-result-type, which was inspired by this one: https://github.com/GrahamCampbell/Result-Type, but it has more robust Psalm typing.
The text was updated successfully, but these errors were encountered: