Skip to content
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

Handle different shapes/styles of success & failure tuples #27

Closed
seivan opened this issue Apr 17, 2017 · 10 comments
Closed

Handle different shapes/styles of success & failure tuples #27

seivan opened this issue Apr 17, 2017 · 10 comments

Comments

@seivan
Copy link

seivan commented Apr 17, 2017

It would be great if we could capture different types of successes from various third party libraries as well as the failures that can occurs.
Not everything is shaped {:ok, value} and it would be awesome to get to see that normalised to common ones as well as capturing exceptions and treating them as errors.

Edit:

From the looks of it, I'd assume it would be adding multiple version of bind for different 'shapes'

 def bind({:success, value}, func) when is_function(func, 1), do: func.(value)
 def bind(failure = {:failure, _reason}, _func), do: failure

 def bind(value, func) when is_number(value), is_function(func, 1), do: func.(value)
 def bind(failure = {:failure}, _func), do: failure

Just shooting from the hip.

@CrowdHailer
Copy link
Owner

At the moment that their are no plans to accept different shapes of success. There are some reason's which were discussed in another issue that I can't find right now. @bill-mybiz might remember where?

It boils down to that fact that other shapes can't be handled as a result monad an binding becomes ambiguous. It might be helpful if you could be more concrete in what you want to achieve, problems you are facing.

@ghost
Copy link

ghost commented Apr 17, 2017

Hmm, there are a couple aspects to this that come to mind:

  1. Shape of the individual statements inside of OK.with when using the <- extraction operator.
  2. Shape of the final return value of the OK.with block.
  3. Semantics implementation details that are hidden by using the OK.success macro.

As for (1), I remember this thread on EF. I think we also mentioned it briefly elsewhere, and I know we talked about the shape of the error tuple quite a bit, with this issue being the main discussion of that.

As for (2), we recently mentioned this in #5, as I mentioned it would be very useful for integrating with third-party libs - but with regard to the return value not requiring the shape of the ok result monad. Phoenix channels is what comes to mind for me personally.

As for (3), I'm still unsure of the value of this concretely, but it sounds related, since @seivan is talking about different types of successes? I take this to be the implementation details of the success of a third-party call, and so I would agree that it may be fruitful to have a concrete example, with the lib and the shape(s)? ❓

As for the capturing exceptions and treating them as error monads, I definitely think that would be a great feature and I think this is captured in issue #12 "options for with", i.e. pure vs. complete. I'm still unsure of the precise intended verbiage there, but I think that discussion would be best continued there. 👍

@ghost
Copy link

ghost commented Apr 17, 2017

Clarification: I'm unsure of the inherent value of OK.success/failure macros hiding the details of ok/error tuples other than a visual cue inside of an OK.with block.

@seivan
Copy link
Author

seivan commented Apr 18, 2017

@bill-mybiz I didn't quite understand your first point but I'll try to respond to the two other ones.

  1. It's a bit of a pipe-dream to assume that Elixir libraries have the same result "monad" with {:ok, value} or {:error, "bad!"} style. I've seen several different variations of those and it would be amazing if OK could somewhat figure things out.
    I've seen stuff like {:ok} (no return), {:success, value} and even {:success} 
    Here's one example of the most recent issue I had https://github.com/artemeff/exredis#usage-web-docs
    Another one is .eg Enum.fetch that doesn't return a tuple but either :error or the value.
    Does OK work with that?

  2. This is related to my previous point that not all methods that throw end with ! and thus it would be amazing if we could normalize them.

What I'm essentially looking for is an Either<T, Error> monad that allows me to write code like

 static fileprivate func parseYAML(fromFileContent fileContent:FileSystemLoader.FileContent<String>) throws -> Parser.UnfilteredContent {
        return try Either<Error, FileSystemLoader.FileContent<String>>
            .right(fileContent)
            .map { $0.content }
            .map(Yaml.yamlObject)
            .map { FileSystemLoader.FileContent(with: (raw:fileContent.content, processed:$0), from: fileContent.file) }
            .unwrapError()   
    }

I shouldn't have to concern myself of the variations of success or failure patterns that each third party library returns if I want to join them together. I mean, I was hoping this library would take care of that concern for me, but I understand if it's an unreasonable expectation. I'm just looking to see what's possible.

Is that an unreasonable request? My macro-fu isn't so strong, but I would assume there would a way to manage expectations.

@seivan
Copy link
Author

seivan commented Apr 18, 2017

@CrowdHailer The problems I'm facing is the various patterns of results or failure not to mention exceptions from methods that might throw/raise but aren't indicated to do so. Usually those are related to modules that work with third party services (like redis or sql) where it can throw even though the method isn't supposed to because of something underlying breaking.

To quote myself from previous comment:

I shouldn't have to concern myself of the variations of success or failure patterns that each third party library returns if I want to join them together. I mean, I was hoping this library would take care of that concern for me, but I understand if it's an unreasonable expectation. I'm just looking to see what's possible

@seivan
Copy link
Author

seivan commented Apr 18, 2017

Also want to point out that if your value doesn't match the next function in the pipe, you'll get an exception and will have no idea why.

no function clause matching in List.first/1
    (elixir) lib/list.ex:219: List.first(nil)

It would be nice if OK could handle that. So I could have the Railway orienteded path.

@ghost
Copy link

ghost commented Apr 18, 2017

@seivan You've cleared up for me what exactly you're looking for. 😄

This is what I was talking about with point 1, which is describing the expected shape of the happy path result when using the <- operator in OK.with. (Point 2 is actually about what the with block itself is returning, i.e. the last line of your statement similar to native with's do block.)

For your given specific example of returning "OK" as a success, you could do this:

OK.with do
  "OK" <- {:ok, Pi.set}
  value = Pi.get
  ...
end

Notice that third-party lib's return value "OK" signals a happy path result. So I wrapped it in an :ok tuple and used the extractor <-, as this will flow to the error path if it is not "OK". But Pi.get is not a happy path function (it just returns a value), so I simply used the match (=) operator.

That said, I looked through that entire lib and from what I grokked, it isn't amenable to a happy path style of coding. This can be seen from the fact that there are zero native with blocks (or any happy path lib) in their code (including in the tests), and also it uses the native pipe (|>) prodigiously. I would think consumers of that lib are heavily relying on fail-fast strategies.

I shouldn't have to concern myself of the variations of success or failure patterns that each third party library returns if I want to join them together.

I'm not sure how this would be accomplished without someone knowing the specific details on the variations of success/failure. This is, AFAICT, why the :ok/:error flow (which is an either monad itself) became idiomatic in the first place: to standardize the infinite possible variations to a single simple, but powerful, idiom that avoids an exception-based control flow and leverages much of the awesomeness of Erlang/Elixir - atoms, tuples, and pattern matching.

As for catching exceptions, there is a new issue #28 which speaks to this, and also the other issue I mentioned #12 "options for with". As I mentioned there, I think it may possibly be nice syntactic sugar for OK.with wrapped in a single try...rescue block.

@seivan seivan changed the title Handle different shapes of success and failures. Handle different shapes/styles of success and failures. Apr 18, 2017
@seivan seivan changed the title Handle different shapes/styles of success and failures. Handle different shapes/styles of success & failure tuples Apr 18, 2017
@CrowdHailer
Copy link
Owner

closing as I don't think that it is write for this library to start handling other tuple shapes. particularly as if a user is finding that is the case they can always fall back to native with

@seivan
Copy link
Author

seivan commented Sep 5, 2017

@CrowdHailer I still think there is room, especially when dealing with a lot of third party code that follow different standards (e.x Redix) but since I can't articulate it properly and too busy to invest time in that, I'm ok with closing this ticket. OK works perfectly fine for now and I jump out to "native" when needed. But I still think the monad Either or Result has a perfect fit for my workflow and I'd wish to see this on Elixir some day, the only issue is unless it can become a part of the language, I don't see the :ok | :error tuples going away, so a library would have to deal with all the permutations of those.

@CrowdHailer
Copy link
Owner

I think you might need to wait for Elixir to have a type system to see those but here's hoping. I think they would be good additions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants