simonmar / async

Run IO operations asynchronously and wait for their results
BSD 3-Clause "New" or "Revised" License
321 stars 64 forks source link

Musings on an Alternative instance for ConcurrentlyE #127

Open danidiaz opened 2 years ago

danidiaz commented 2 years ago

The Applicative instance for ConcurrentlyE behaves like concurrently for successes and like race for errors. Does ConcurrentlyE also admit an Alternative instance, like Concurrently does?

One way to define it would be making (<|>) behave like race for both successes and errors. The problem is that the instance is boring. It offers nothing you couldn't do by wrapping an IO (Either e a) in the nomal Concurrently.

A more interesting instance would require a Semigroup instance on the error e, and return either the first success or the combination of all the errors.

Alas, this second definition has a problem. If empty is the action that waits forever, then, for any action that fails, action <|> empty will also wait forever! This is because we must wait for all the errors, and empty never completes. This makes uses of Data.Foldable.asum also hang.

The semigroupoids package provides Alt, which is "Alternative without empty". It can be used with functions like asum1.

semigroupoids is a big package and I'm not proposing depending on it. But perhaps no Alternative instance for ConcurrentlyE should be defined, and an Alt instance could be defined as an orphan in some other package instead.

danidiaz commented 2 years ago

Come to think of it, "either the first success or the combination of all the errors" is what the proposed Monoid instance for ConcurrentlyE does, if you flip the error and success types (the Monoid gives you "either the first error or the combination of all successes") 🤔