-
Notifications
You must be signed in to change notification settings - Fork 35
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
Correct the Applicative instances #38
Conversation
When a type has a Monad instance its Applicative instance should be identical to using |
Nonetheless, this patch still makes sense, especially in a post-AMP world. It relaxes the constraints necessary for the |
I agree with @DanBurton that the patch is good. But @glguy is correct - the bug here is actually in the async package. |
@ygale Can you clarify why, if Applicative offers concurrency, there is no Monad instance consistent with it? |
@jwiegley because to implement bind, you need to complete the action on the LHS before you can apply the function on the RHS to the result. There is no way to run the actions concurrently. Hmm, perhaps there is a way to exploit laziness and allow the calculation to begin immediately and only block if it needs to force the value from the LHS. But still, would |
The monad interface is inherently sequential. When (>>=) :: m a -> (a -> m b) -> m b You can't run the ap mf mx = I'm surprised that Concurrently has a Monad instance. It is indeed -- Dan Burton On Thu, Apr 23, 2015 at 10:00 AM, John Wiegley notifications@github.com
|
That would be |
@evincarofautumn Yes, or we could use |
Could we please continue this discussion in simonmar/async#26? It is veering off-topic here. |
I think we've started off on the wrong foot and the above statement is the root of all misunderstandings here. Being implemented identically and producing the same results are two different things. The rule of I want to stress that nothing in this pull request violates |
This is sadly, not possible.
This means that the This has been repeatedly raised against |
The way I see it, it simply relays this decision on the |
foo = EitherT $ return $ Left "wrong launch codes" foo <*> bar Under the existing semantics this doesn't launch missiles. You need the use of the Monad on |
Maybe something less general than IO is needed to indicate this. Effects which don't mutate RealWorld are free to have their execution order commuted (or skipped entirely if their value isn't needed)? |
@ekmett I understand that there is a problem with changing the semantics of |
You're right. However, launching missiles in such a scenario is what I would expect from a parallel (i.e., non-sequential) composition of two things, which, AIU, the power of Applicative is all about. As another point, what's the good of having Applicative completely reproduce what would otherwise be still achievable with |
LeifW: You don't need foo = throwError "stack is empty" *> modify tail. exhibits the same kind of problem using |
Nikita, you can build such a type with your chosen instance it just isn't a "monad transformer" and it fails to satisfy the laws you need to be able to refactor code. As It relies on invariants about |
Yes, it is what the Applicative is all about: Context freedom. In which case, if that is what you want, then |
@ygale: I have no problem with weakening the constraint on |
There is a similar issue with |
I guess the part of the patch that uses less restrictive typeclass constraints should be merged. As said above, it makes sense after AMP. |
@phaazon: We'll want to make a small tweak to that part to avoid worsening the constraints on users < 7.10, but otherwise, sure. |
Thank you, Edward, for thorough answers.
I've never considered that. It's a good option. However I just hate to lose the Monad instance as well.
That's the part I don't understand. I understand from our conversation, it's not just about monad transformers, but monads in general. I can't expect the following behaviour from a monad without breaking the laws - is that correct? -- Executes sequentially:
links <- scrapeLinks
-- Executes concurrently:
fmap concat $ traverse scrapeResults links But, Gods, do I love how neatly it abstracts from the problem of execution order with the most general interfaces. And BTW it is exactly the way that So basically I just don't understand the practical implications of the laws, which prohibit that behaviour. What do we get exactly which is able to compensate the price of losing such features?
Can you elaborate?
Did this happen during AMP? Why does changing the semantics of the "do" sugar neccessarily imply breaking it? True, it will behave differently in certain cases, but again it's the behaviour I for one would rather expect in such circumstances. |
Nothing prevents you from writing two combinators foo :: Compose m (Either e) a -> EitherT e m a
bar :: EitherT e m a -> Compose m (Either e) a or a lens-style Iso, to transition back and forth. This is more or less how folks work with Along the way someone bolted on an illegal extension of it to a You can make more limited concurrency monads where the e.g. haxl quotients out the number of passes to the server in its definition of how parallel an operation is, and under that quotient the notion of parallelism it uses is okay -- if you ignore the ability to lift IO operations in.
You can have |
Okay. Thanks. |
@ekmett, could you clarify what you mean by:
|
i mean that haxl doesn't try to duplicate the exact number of passes in terms of number of round trips made to the various data sources just the final result. So the fact that The goal of haxl is to maximize the number of queries that can be executed in parallel and performance may be better in the Applicative setting, but the assumptions of the haxl monad are that answers won't change between passes and that the queries are just that, queries, and don't change the data downstream so if it can yield an answer without making a query that doesn't affect its semantics. For haxl to get its parallel Applicative instance it needs both of those assumptions. By 'quotienting' out I'm referring to the act of considering equal up to some condition. Here they are equal up to the number of round trips (which can be lower in the applicative setting) and the possible early avoidance of some queries (which can happen in the monadic setting), but both bits of code yield the same answer. |
In my application I used the following stack:
where
Concurrently
is a type from the "async" package, whose instances forApplicative
andAlternative
utilise concurrency. To my surprise I discovered that this stack behaved sequentially when used with Applicative operators. Then I found out that the cause was in theEitherT
instances, which were implemented sequentially in terms ofMonad
.This pull request fixes the instances and lightens the constraints.