lexi-lambda / freer-simple

A friendly effect system for Haskell
https://hackage.haskell.org/package/freer-simple
BSD 3-Clause "New" or "Revised" License
227 stars 19 forks source link

How do you do something like `reinterpret2`, but backwards? #8

Closed RobertFischer closed 6 years ago

RobertFischer commented 6 years ago

I'm looking for a way to say that if the next two effects are f and g, then replace them with h.

A type something like: forall f g h effs. (f ': g ~> Eff (h ': effs)) -> Eff (f ': g ': effs) ~> Eff (h ': effs)

Is there is an easy way of doing this which I am missing? If not, would someone be willing to help me implement it and generate a pull request for it?

lexi-lambda commented 6 years ago

You’re saying you want to handle two effects simultaneously, in one go? Could you perhaps provide an example of what such a thing would look like?

RobertFischer commented 6 years ago

Yes, that's exactly what I'd like to do. It's mostly for optimization purposes.

For instance, consider the case that I have two operations: QueryForIds Foo Query and FetchFoos (Id Foo). It'd be nice if my SQL implementation could recognize that these could be merged into a single SQL call (SELECT * FROM foos WHERE id IN (SELECT ...)...)

Similarly, if I have two WriteFile File Bytes operations in a row (where File is a type-level specifier of the file to write to), I could concatenate them into a single write. When we went through the next round in reducing the effects, if the next effect was also a write to that same file (we had 3 in a row), we would then concatenate that, too, under the same rule.

Does it even make sense to want to do this?

lexi-lambda commented 6 years ago

I’m not sure it makes sense, since by the nature of a Monad, you can’t know what action will happen next until you’ve provided a result for the previous one. For example, a user might call QueryForIds, analyze the result, and only call FetchFoos if the result of QueryForIds is nonempty. In this case, how could you possibly know that the user will call FetchFoos before returning a result from QueryForIds.

Applicative gives you the power to statically analyze in this way, since Applicative disallows branching based on the result of a previous action. As soon as you add Monad, however, you lose this power, since the interface becomes too expressive.

RobertFischer commented 6 years ago

Thanks for explaining that. I appreciate it.

Going to close this issue.