Closed adamConnerSax closed 5 years ago
To answer your questions:
I'd love a pure runRandomFu
function. Rather than introducing the orphan, you can almost certainly get it by reinterpreting RandomFu
into Lift (Control.Monad.State whateverthingyouneed)
, and then handling that thing explicitly.
Tests would be good -- if only to show how to use the thing.
Fixed the simple stuff. Renaming, LambdaCase, removing Random. I'll have to think more about the MonadRandom/instance stuff. So that'll have to wait a bit...
What do you mean in the above by "get it by"? What would I do with a function that has a MonadRandom
constraint and which I would like to run in a Member RandomFu r => Sem r
without the instance I provided?
What do you mean in the above by "get it by"? What would I do with a function that has a MonadRandom constraint and which I would like to run in a Member RandomFu r => Sem r without the instance I provided?
There are Monad m => MonadRandom (StateT StdGen m)
instances; so I don't see why we couldn't reinterpret a Sem (RandomFu ': r) a
into a Sem (State StdGen ': r) a
into a StateT StdGen (Sem r) a
into Sem r (StdGen, a)
.
The Sem (State s ': r) a -> StateT s (Sem r) a
transformation might be tricky, but I don't see any reason it couldn't be done.
Nice. That would've taken me...a while.
So. I get the runRandomFu thing and, once there is a released version of polysemy with the hoistStateIntoStateT
, I'll fix up the PR.
But I still don't get what your mean about the instance/constraint thing. You may need to explain it to me like I am a (Haskell-knowing) five-year-old. Suppose I have a function:
doSomethingRandom :: MonadRandom m => m a
and I want to be able to call that function inside
myEffectfulFunction :: Member RandomFu r => (a -> b) -> Sem r b
myEffectfulFunction f = do
a <- ?? doSomethingRandom
return $ f a
either by having
instance Member RandomFu r => MonadRandom (Sem r)
or what? I guess a function of the form
liftRandomFu :: (Member RandomFu r, MonadRandom m) => m a -> Sem r a
??
I don't quite see how hoisting to StateT gives us the former. Am I missing something?
And the latter, a function which discharges the constraint in the presence of Member RandomFu r
certainly seems possible, but also sub-optimal for ease of use. Is that what you have in mind? Sort of like liftIO
? I guess I can see how that might be the right balance when trying to interoperate with mtl but not become shot through with it.
I was under the impression that the MonadRandom
bit was necessary to run any of the random-fu
stuff. If it's actually just for convenience of interoping with code that already uses MonadRandom
, then yes, I agree, let's just burn the bridge and not support any MonadRandom
stuff.
Hmm. But I still want the interop! I have some data-science stuff that has that signature. Which I guess I could change but there's a lot more mtl style code out there and it might be worth creating a path. Let me think about the constraint-discharging version. That wouldn't infect the lib with an orphan instance but would provide a way for interop to be relatively painless.
Hiya, what's the status of this?
Hiya!
I guess I was waiting for a release with hoistStateIntoStateT and then I would try to move forward. Has that happened? I get confused trying to work against unreleased versions...
In the meantime I have been using Polysemy a bunch in knit-haskell and doing some fun Bayesian inference on voter preference using US House Election Results. And discovering a couple of usability things:
(Without the plugin) Many errors become Ambiguous type errors. It's a little like TypeApplications. Any small thing breaks the inference and then the first error you get is always Polysemy.
Trying to add the plugin adds a lower bound on containers of 0.6.0 (not sure why. Maybe the "installed" ghc version?) and that makes it unusable for me because knit-haskell has a "plots" dependency with an < 0.6.0 bound on containers. Sigh.
Anyway, happy to get back to it. Should I?
Adam
custom-errors
flag --- it'll stop the aggressive polysemy errors. It's an unfortunate side-effect of GHC's error machinery. Maybe I'll investigate generating those errors from the plugin, where it'd be much easier to not clobber the usual Haskell errors.package.yaml
files for a better containers
lower-bound I'll happy merge it and push a release ASAP.As for hoistStateT
, I think we came to the conclusion that it won't actually help with the MonadRandom
instance.
I'll get back to it then! For now, I can work around the containers issue with an "allow-newer" in my cabal.project. Plots seems to compile fine with containers-0.6.
I fiddled with this for a bit and so far the only thing I can do is
absorbMonadRandom
:: Member RandomFu r => (forall m . R.MonadRandom m => m a) -> Sem r a
(just via an instance on a newtype wrapper) which gets us some of the way but not as far as I'd like. But I don't think we can do the more general
absorbMonadRandom :: (MonadRandom m, Member RandomFu r) => m a -> Sem r a
because we can't the a
out of that monad, right? The case above works because of the Rank2-ness of the 'm' so the absorbMonadRandom
function gets to choose to implement m
as the wrapped Sem
with the instance, thus discharging the constraint. But the more I think about it, the less clear I get on how we could do more, especially without violating the spirit of Polysemy--to not prematurely foreclose on our possible interpretations.
I don't know how useful this thing will be. I'm going to try and find some places to use it and see if it's helpful .
And "absorb"? I don't know. It's not quite lift or hoist or raise, right?
Once I use it a bit, I'll try to add an example that makes it clearer how to use it and random-fu altogether. But that might need to wait until next week.
I really like the type of absorbMonadRandom
. I think it's an elegant solution to the mtl interop problem. Maybe we could go further with constraint kinds:
class CanonicalEffect (c :: (* -> *) -> Constraint) (e :: (* -> *) -> * -> *) where
absorb :: Member e r => (forall m. c m => m a) -> Sem r a
instance CanonicalEffect MonadRandom Random where
instance CanonicalEffect (MonadReader r) (Reader r) where
etc., although I'm not sure how nicely it would play if you have multiple mtl constraints you want to absorb simultaneously.
Thanks!
Yeah. But maybe single effects are enough? If you start having multiple effects you should re-write in polysemy or use polysemy on top or on the bottom of the stack, maybe? Let me make some examples for this one and then think a bit about the generalized version of "absorb". And multiple effects.
Would that class need a fundep (or an associated type family)? How would the compiler know what e
you had in mind at the absorb
call-site? Also, I wonder if all the absorbers are canonical? Is there ever a case where we might choose different effects to absorb the same mtl-constraint? For instance, if we could interpret random-fu via System.Random then we might want to absorb the MonadRandom
constraint via Random
instead of RandomFu
. But maybe that's a bad example because the random-effects are specifically weird.
But the question stands. I'm not sure this generalization should be a typeclass though we can certainly generalize the signatures and add some helpers so that each specific absorber is more compact and uniform.
And I suppose you could write multi-effect absorbers. Not in the polysemy libraries, but for a specific application it might make sense as a way to use legacy mtl-code. So maybe the "absorb" kit should be built to make those easy to build when you want them? I'll think about that as well.
See https://github.com/isovector/polysemy-zoo/pull/8 for CanonicalEffect. I'll clean up this one once you decide how you want that to work...
Also: Updated package.yaml and regenerated polysemy-zoo.cabal. Added some things to gitignore for cabal v2-style builds and emacs TAGS files.
Questions:
Are we comfortable with the orphan instance of "MonadRandom"? I think it will be useful but it's somewhat against the spirit of polysemy's mtl-free-ness and it's an Orphan.
I don't see an easy way to implement these as the same effect and then interpret but I didn't look that hard. I can imaging Random being implemented in terms of RandomFu but not the other way around. And that's a commitment to a less standard external dependency.
It would be possible, but, I think, a bit more work to write the equivalent of
runRandom
for RandomFu. random-fu has instances of things in terms ofState StdGen
but those are built around the mtlState
and they'd have to be re-written, which would mean more orphan instances. I think.Should there be tests?