-
Notifications
You must be signed in to change notification settings - Fork 19
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
Suggestion to improve type inference #14
Comments
As an example of where the inference in I create my effect type: data Log a where
LogMsg :: a -> Log ()
log :: Member (Log msg) effs => msg -> Eff effs ()
log = send . LogMsg However, if I try and log a foo :: Member (Log (Doc ()) effs => Eff effs ()
foo = do
...
log "Hello"
foo :: Member Log (Doc ()) effs => Eff effs ()
foo = works out beautifully - |
I’m not sure I like the idea of effectively forcing a single instance of a given effect per list, but I’m not dead set against the idea, either. I’ll probably have to think about it a little more. |
Glad you're open minded on this. To be honest, I can count on one hand the amount of times I've wanted multiple instances of one effect. I'd also note that this approach doesn't necessarily preclude this. Assume we had data Multi effect cfg a where Multi :: effect a -> Multi effect cfg a (Name subject to bike shedding) Now we can say (Member (Multi (Reader r1)) () m, Member (Multi (Reader r2)) () m) => The fun-dep says "there can only be one Next, add multi :: forall x. Eff (effect x ': effs) a -> Eff (Multi (effect x) ': effs) a And we can write x <- multi @Int ask And recover multiple instances of an effect, essentially via newtyping. Basically we've shifted the burden onto people who want multiple instances, rather than having everyone pay. See also the discussion here. |
After a little bit of thought, my gut reaction is that I don’t like how this makes the API more complicated. I wrote the API of freer-simple so that I could explain a Haskell system that uses effects to someone who has never even written any Haskell before. Sure, it requires some white lies and vigorous handwaving, but the notation is clear (which it turns out helps immensely in practice), and actually writing it is harder than reading it, but it’s critical that people don’t feel completely lost in the language as soon as they have to do a tiny bit of I/O. The mental model of freer-simple is that an effect is just a capability, and they are individual, self-contained things that can be moved around at will. Don’t get me wrong, I am deeply saddened by the fact that these very same beginners can bump into awkward type ambiguity errors, but those type errors can be explained, and I think the simpler mental model has a lot going for it. |
I'd like to strongly weigh-in against this; when you start considering freer monads as "implementation mixins", you generally do want a significant number of the same effect. I've used an eff stack in production with as many as eight readers and 12 writers--amongst other more interesting effects. For example, at the domain level, lots of things are just writers---you don't particularly care what happens to them in your business logic, just that you emitted them. The mixin behavior can rewrite these into external effects with arbitrary systems. Restricting effects to one-per-stack fundamentally limits the composability of these mixins. |
I may explore class MonadFoo a m | m -> a where
sendFoo :: Foo a -> m a
instance Member (Foo a) effs => MonadFoo a (Eff effs) where
sendFoo = send
data Foo :: * -> * -> * where
Op :: a -> Foo a ()
op :: MonadFoo a m => a -> m ()
op = sendFoo . Op I still feel that while multiple effects is nice to have theoretically, in practical Haskell it just doesn't work for me. I easily end up with polymorphic effects and then hating the complete lack of any usable inference. I ultimately moved to
Are those monomorphic reads/writes? If so I can understand how that could work. I find things start to fall apart when you write things like |
Yeah, monomorphic reads/writes. I will admit that numbers (and Not an ideal solution by any means, but I'd argue the benefits greatly outweigh the costs. |
It's worth noting that you can still compose multiple readers in this, you just have to map them into a common type when you need to use two at once. I could imagine combinators like embed :: Member (Reader y) effs => (y -> x) -> Eff (Reader x ': effs) ~> Eff effs such that you might write do
foo <- embed fst ask
bar <- embed snd ask
return (if foo then bar else "!") This isn't much more than forcing people to use |
The problem with that is that you often want to interpret I wonder if the underlying problem could be better solved via a typechecker plugin. If our only given in scope is a |
https://gitlab.com/LukaHorvat/simple-effects/commit/966ce80b8b5777a4bd8f87ffd443f5fa80cc8845. FWIW, it didn't work for the majority of uses in our code at work (loads of stuff was still ambiguous). |
Great minds. Thanks for the lead---I'll take a look when I get a chance! |
I really want to like
freer-simple
, but I can't until it infers at least as well as idiomatic mtl usage. The big problem at the moment isMember
:This multi-parameter type class infers miserably because there is no connection between the parameters. However, we can do better! Ignoring
FindElem
for now, this has much better inference, at the cost of: a. limiting you to a single type of effect and b. Adding a parameter to effect definitions:That is, if I know the set of effects and a particular effect, I can tell you the "configuration" of that effect. Configured or parameterized effects are polymorphic effects such
Reader
,State
, etc. This encoding meansis like
MonadReader r m
This has the same "drawbacks" as functional dependencies in
mtl
, but I believe those drawbacks are significantly overblown. The good news is that as effects are first class infreer-simple
, it's pretty trivial to justnewtype
Reader
for example to have it occur twice (at different types) ineff
.The complication to the kind of
eff
can probably be worked around withor something.
What do you think? Happy to throw up a PR if you want to see how it looks in practice. I'm using this in a private extensible effects like library to much success.
The text was updated successfully, but these errors were encountered: