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

Api encouraging incorrect implementation #40

Open
turboMaCk opened this issue Nov 9, 2022 · 3 comments
Open

Api encouraging incorrect implementation #40

turboMaCk opened this issue Nov 9, 2022 · 3 comments

Comments

@turboMaCk
Copy link
Member

turboMaCk commented Nov 9, 2022

This PR rotted a lot and should probably be closed: #28

Anyway I would like to instead open an issue to discuss the core problem it was attempting to address.

This library exposes 2 types of errors:

  1. WithNamedError is used for errors that come from implementation of this library (named argument not found)
  2. IO exceptions that happen within postgresql-simple

My claim is that one never wants to handle the 1 (it's implementation error not legitimate runtime error in most cases). But almost always wants to handle some subset of 2.

The problem

Putting WithNamedError thing aside lets clarify why IO exceptions matter.

Imagine a table with UNIQUE constrained. For obvious reasons when inserting data to the table there is an expected error that could happen - violation of unique constrained. This error most likely needs to be clearly communicated to caller - for instance the specific 400 response with information that name is taken in some form should be rendered by HTTP server.

The solution that this library encourages is to write 2 queries (why is explained bellow). One to check if given unique column doesn't already exist and then performing the insert. This solution is of course incorrect. In presence of concurrency some other thread could create conflicting entry in between the select and insert.

The correct solution is to perform insert and catch unique constrained violation. So what does it take to do this?

With postgresql-simple one works with IO so catching this means catching exception in IO monad. However with postgresql-simple-named this IO is lifted to LiftIO m => m. To catch the exception one needs to use construct that could catch it in the m (lifted IO context). For instance using Control.Exception.Lifted which is not part base or implement similar exception handler to this one. I claim that this is no good because good API should make correct implementation simpler not harder.

Proposed solutions

There are multiple potential solutions. I have some ideas myself but first I would like to see what others think.

Currently there is an issue for one possible approach #27

@jhrcek
Copy link
Contributor

jhrcek commented Nov 9, 2022

I feel that nowadays people are using unliftio or similar "lifter IO" lib to catch exceptions, which already makes it possible to catch exceptions thrown by this library relatively easily. Of course there's still the downside that exceptions are not visible in types.
I find it unfortunate that this library is adding MonadError as separate route through which exceptions can be thrown, but I'd like to avoid the trap of exposing more exceptions via this library's MonadError .

@turboMaCk
Copy link
Member Author

turboMaCk commented Nov 9, 2022

Well this issue is not really about exposing exceptions via MonadError. The issue you've created is #27 but I intentionally created this one to avoid talking about general problem in terms of one specific solution.

find it unfortunate that this library is adding MonadError as separate route through which exceptions can be thrown

I agree with this. Especially since the errors thrown in MonadError are implementation errors (at least in most cases) one doesn't want to handle anyway. So api makes it easy to handle things you don't want to handle and sort of obscures things you might need to handle.

I think there might be some solutions that avoid issues with exposing more exceptions in MonadError. For instance:

  • we can just ditch monad error and io lifting (the low tech solution) completely to avoid confusion
  • we can ditch just MonadError/WithNamedError so it's clear everything is thrown in lifted io
  • we can provide primitive function to catch specific io exception from the library
  • we can provide function that would turn result of queries to Either err a where err is constructed based on thing passed into the function which will represent catches of handled io exceptions to some error type.

to name a few which immediately come to my head.

in the end I would like to be able to write something along (pseudo code):

executeNamed [sql| query |] [ param =? foo ]
  `catching` \IoExceptionThing -> NameTakenError
  `catching` \AnotherIOExceptionThing -> AliasTakenError

@nitinprakash96
Copy link
Member

I wonder if haskellari/postgresql-simple#108 is going to be any helpful for handling different errors arising out of postgres-simple? @turboMaCk

@nitinprakash96 nitinprakash96 changed the title Api encuraging incorrect implementation Api encouraging incorrect implementation Nov 22, 2022
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

3 participants