Skip to content

Respond to community feedback#144

Merged
lehins merged 14 commits intov1.2-proposalfrom
respond-to-community-feedback
Jun 7, 2020
Merged

Respond to community feedback#144
lehins merged 14 commits intov1.2-proposalfrom
respond-to-community-feedback

Conversation

@lehins
Copy link
Copy Markdown
Collaborator

@lehins lehins commented Jun 4, 2020

This PR covers some of them feedback we received from the community:

  • extraneous state token parameter in MonadRandom g s m - Removed the token and all functionality that relied on it: thawGen freezeGen, withGenM ...
  • Suggestion to add FunDeps MonadRandom g m | m -> g - This is not a useful setup since it will require a generator to depend on a specific monad, which we are trying to avoid. Instead renamed the class to StatefulGen, to emphasize that it is the generator that is important, not the monad.
  • New version of splitmix-0.1 was released that does not depend on random, setting it as a lower bound.

Other adjustments:

  • Relax dependencies on non-library components, since they can't be stricter than the library itself.
  • Relax warnings from -Weverything to -Wall to avoid useless compilation warnings.
  • Fix CI, so it doesn't fail for PR that don't have coveralls token available

@Shimuuar
Copy link
Copy Markdown

Shimuuar commented Jun 4, 2020

Suggestion to add FunDeps MonadRandom g m | m -> g - This is not a useful setup since it will require a generator to depend on a specific monad, which we are trying to avoid.

I don't quite understand this. It says that given m we can determine g. But for any g we can have arbitrarily many monads

@lehins
Copy link
Copy Markdown
Collaborator Author

lehins commented Jun 4, 2020

I don't quite understand this. It says that given m we can determine g. But for any g we can have arbitrarily many monads

@Shimuuar That is precisely why this suggestion cannot work. But considering that it has been suggested by at least 3 people that I know of, there is some confusion. Probably because people only think with transformers in their mind. Therefore I decided to solve the problem of confusion with an approach of renaming. Maybe StatefulGen, will make people think about generator first and a monad second

@lehins lehins force-pushed the respond-to-community-feedback branch from b3ae4c7 to 14df799 Compare June 4, 2020 12:22
@lehins
Copy link
Copy Markdown
Collaborator Author

lehins commented Jun 4, 2020

Posting here a plan that I already shared privately for recovering freeze/thaw functionality:

Just as before, but with a bit of renaming

class StatefulGen (g s) m => SeededGen g s m | g -> s, m -> s where
  type Seed g = (r :: *) | r -> s
  saveGen :: g s -> m (Seed g)
  restoreGen :: Seed g -> m (g s)

This would not only recover all of our functionality back, but also make it possible to create an instance for: https://www.stackage.org/haddock/lts-8.24/mersenne-random-1.0.0.1/System-Random-Mersenne.html#t:MTGen
Which I totally forgot about. It doesn't have a frozen state, so we can have StatefulGen instance, but not SeededGen

Other benefits of splitting this freezing/thawing functionality into a separate class is:

  • It will be visible at the type level when a function relies on a potentially expensive operation of thaw+freeze
  • type signatures of majority of our functions become slightly simpler, since they loose that s, (eg. uniformM :: StatefulGen g m => g -> m a, vs uniformM :: MonadRandom g s m => g s -> m a)

@lehins lehins mentioned this pull request Jun 4, 2020
@Shimuuar
Copy link
Copy Markdown

Shimuuar commented Jun 4, 2020

@lehins Now I'm even more confused. Why are saying that it's impossible? As I see both designs are pretty much equivalent. In one design dispatch is done over g type. In another it's done over monad m. Right now It writing PR with alternative design and translation is straightforwad and almost mechanical. In former design there're StateGen/StateGenM to link RandomGen & MonadRandom. In latter there's functionally identical RandT newtype

Copy link
Copy Markdown
Collaborator

@curiousleo curiousleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great simplification, thanks for addressing the larger and smaller comments that we've received.

I've left a few minor comments.

Comment thread random.cabal Outdated
Comment thread random.cabal
Comment thread src/System/Random/Internal.hs Outdated
Comment thread src/System/Random/Stateful.hs
Comment thread src/System/Random/Stateful.hs
Comment thread src/System/Random/Stateful.hs
Comment thread src/System/Random/Stateful.hs
Comment thread stack-coveralls.yaml
@curiousleo
Copy link
Copy Markdown
Collaborator

Just to add to #144 (review) - my working assumption for the review was that we'd find some other way to add freeze and thaw back, e.g. following the approach in #144 (comment). I suggest holding back until @Shimuuar has had a chance to present what I understand to be an alternative approach.

@Boarders Boarders mentioned this pull request Jun 4, 2020
@lehins
Copy link
Copy Markdown
Collaborator Author

lehins commented Jun 4, 2020

Why are saying that it's impossible? As I see both designs are pretty much equivalent.

@Shimuuar I am not saying it is impossible, I am saying it is strictly worse! And they are far from equivalent. In MonadRandom g m | m -> g you are forced to define a new monad transformer be it RandT or what have you, which comes with a lot of baggage, like creating instances and forcing users to deal with stacks of transformers. I am really against such an approach. I do not want to see lift . lift . lift .... just in order to use different RNGs in a single function.

By definition of g m | m -> g makes it impossible for m to be ST or IO, therefore you can't possibly call these designs equivalent.

@Boarders
Copy link
Copy Markdown

Boarders commented Jun 4, 2020

@Shimuuar Given IO it should not be determined what type of generator is being used. That will mean the library cannot be agnostic with respect to PRNG and so if you want to try out different implementations you need to constantly change the monad you are using. I think it sounds like a bad design that should be avoided if you want to be agnostic.

@Shimuuar
Copy link
Copy Markdown

Shimuuar commented Jun 4, 2020

Now I understand. So main arguments against m -> g approach are: 1) it makes it necessary to define specific newtypes for generator types. That's fair criticism while I think that with appearance of DerivingVia it's much less of a problem. 2) It couldn't work with IO/ST which is again fair.

On other hand what are problems of StatefulGen approach. 1) One have to thread g manually which is annoying. 2) type inference for RandomGen based instances is rather poor. For example type for uniform StateGenM couldn't be inferred. I'm not sure how much it's of a problem. No one used proposed API.


@lehins By "equivalent" I meant that everything written in one style could be straightforwardly translated into other. But some things could be easier in one style of another.

@lehins lehins force-pushed the respond-to-community-feedback branch from 63df9d6 to a48d55f Compare June 4, 2020 18:34
@lehins lehins force-pushed the respond-to-community-feedback branch from a48d55f to 85ec246 Compare June 4, 2020 19:04
@lehins
Copy link
Copy Markdown
Collaborator Author

lehins commented Jun 4, 2020

On other hand what are problems of StatefulGen approach.

  1. One have to thread g manually which is annoying.

This is orthogonal to the current design because it can be easily solved by MonadReader. In some future release we can maybe add extra functions that avoid the manual passing of generator around, but I don't think it is worth to do it right now.

  1. type inference for RandomGen based instances is rather poor. For example type for uniform StateGenM couldn't be inferred. I'm not sure how much it's of a problem. No one used proposed API.

It is best to use StateGenM with helper functions, such as runStateGen, etc. then type inference is not a problem. In fact StateGenM acts as a simple proxy, because otherwise type of pure gen would be ambiguous. Moreover, once I bring SeededGen back from the #144 (comment) it will be even less of a problem, because StateGenM constructor never needs to used by the user.

@lehins
Copy link
Copy Markdown
Collaborator Author

lehins commented Jun 4, 2020

Posting it here new addition, which I think is really cool:

Here is the new freeze/thaw interface I came up with. It is even better than before. @Shimuuar suggested at some point before possibility of flipping frozen and mutable around, but before it would have looked really ugly (every stateful gen would have had a signature Mutable f m), but with the split into two classes it looks great. I think overall it removes all the warts that I didn't like that where in MonadRandom g s m class, namely redundant state token for pure parameter and unnecessary state token in the class head itself:

class StatefulGen (MutableGen f m) m => FrozenGen f m where
  type MutableGen f m = (g :: Type) | g -> f
  freezeGen :: MutableGen f m -> m f
  thawGen :: f -> m (MutableGen f m)

mwc-random is a pleasure to look at

instance PrimMonad m => FrozenGen MWC.Seed m where
  type MutableGen MWC.Seed m = MWC.Gen (PrimState m)
  thawGen = MWC.restore
  freezeGen = MWC.save

and pure gen adapters are just as beautiful 🙂

instance (RandomGen g, MonadIO m) => FrozenGen (IOGen g) m where
  type MutableGen (IOGen g) m = IOGenM g
  freezeGen = fmap IOGen . liftIO . readIORef . unIOGenM
  thawGen (IOGen g) = newIOGenM g

runGenM is just as wonderful

runGenM :: FrozenGen f m => f -> (MutableGen f m -> m a) -> m (a, f)

Comment thread src/System/Random/Internal.hs Outdated
Comment thread src/System/Random/Internal.hs Outdated
Comment thread src/System/Random/Internal.hs Outdated
Comment thread random.cabal Outdated
Comment thread src/System/Random/Stateful.hs
curiousleo and others added 2 commits June 5, 2020 09:31
Co-authored-by: Leonhard Markert <curiousleo@users.noreply.github.com>
lehins and others added 2 commits June 5, 2020 11:29
Co-authored-by: Leonhard Markert <curiousleo@users.noreply.github.com>
Co-authored-by: Leonhard Markert <curiousleo@users.noreply.github.com>
@Shimuuar
Copy link
Copy Markdown

Shimuuar commented Jun 5, 2020

It is best to use StateGenM with helper functions, such as runStateGen

That's why said that I'm unsure whether it'll be problem in practice. However one shouldn't underestimate ability of library user of using in weird and unanticipated ways. However only way to find out is to release library

Also new version of FrozenGen resolves last problem. Now it's possible to write instance for StateGen and instance is present. I think at this point there's no alternate design that is clearly better. So it's better release this design and try it out in practice

@lehins lehins merged commit 3bd7b9a into v1.2-proposal Jun 7, 2020
Shimuuar pushed a commit to Shimuuar/random that referenced this pull request Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants