Skip to content

Please provide instance for (Enum a, Bounded a) for Random #21

Description

@kvanbere

It would be really nice to be able to do something like this:

> data Action = Code | Eat | Sleep deriving (Bounded, Enum)
> randomR :: IO Action
Eat

The implementation for (Enum a, Bounded a) => can cheat a little to get a valid range (psuedo, for randomRIO since it's easier/shorter to show):

let lower = fromEnum (minBound :: a)
let upper = fromEnum (maxBound :: a)
return $ toEnum <$> randomRIO (lower, upper)

Note the usage of the underlying instance for Int to select a constructor.

It would have been magical if we could have just a Enum a => instance, but I can't think of a way to do it.

Activity

  1. changed the title [-]Please provide instance for (Enum a, Bounded a)[/-] [+]Please provide instance for (Enum a, Bounded a) for Random[/+] on Oct 4, 2014
  2. rwbarton commented on Oct 4, 2014

    @rwbarton

    What instance do you have in mind, instance (Bounded a, Enum a) => Random a where ...? There can't be such an instance, since it would overlap with every other instance.

  3. kvanbere commented on Oct 4, 2014

    @kvanbere
    Author

    I thought there was a most specific instance rule?

    The instance would just pick a random constructor from those available (range known by Bounded), evenly distributed among all the enumerations (defined by Enum). The implementation is not hard, I just haven't written it or opened a PR or anything yet.

    Essentially, the best case scenario would be if the compiler could derive Random, but it can't, so having an Enum/Bounded instance is the closest thing to it, since it will derive those automatically for you.

  4. cartazio commented on Oct 4, 2014

    @cartazio
    Contributor

    This would prevent any user derived instances. If you manufactured a newtype for just giving this instance, that might be more tenable. But even then, this also implicitly assumes a uniform distribution is always the correct default, and I'm not sure if thats true! (Or that every Bounded instance is a properly lawful one, which I'd hope but wouldnt want to impose.)

    The analogous monoids induced by Num use Sum and Product newtypes, so i think any analogous choice here would needs be using the same strategy. like UniformBounded newtype or some such

  5. kvanbere commented on Oct 5, 2014

    @kvanbere
    Author

    I kind of got the impression that the Random typeclass implied uniform, but no?

    At least, all the existing instances seem to be uniform, and I can't think of a case where you wouldn't just plug something on top of the linear distr to give the different distribution you need.

  6. emmanueltouzery commented on Nov 22, 2014

    @emmanueltouzery

    A reasonable default instance is:

    instance Random Action where
      randomR (a, b) g =
        case randomR (fromEnum a, fromEnum b) g of
          (x, g') -> (toEnum x, g')
      random g = randomR (minBound, maxBound) g

    It's tricky boilerplate that would be easy to get wrong and that you just copy-paste.

    Maybe Random could export:

    defaultBoundedEnumRandomR :: (Bounded a, Enum a, RandomGen g) => (a, a) -> g -> (a, g)
    defaultBoundedEnumRandomR (a, b) g =
      case randomR (fromEnum a, fromEnum b) g of
        (x, g') -> (toEnum x, g')
    
    defaultBoundedEnumRandom :: (Bounded a, Enum a, RandomGen g) => g -> (a, g)
    defaultBoundedEnumRandom g = defaultBoundedEnumRandomR (minBound, maxBound) g

    And then users could simply do:

    instance Random Action where
        randomR = defaultBoundedEnumRandomR
        random = defaultBoundedEnumRandom
  7. cartazio commented on Dec 31, 2014

    @cartazio
    Contributor

    exporting some default methods might be good. interesting idea!

  8. rwbarton commented on Dec 31, 2014

    @rwbarton
    defaultBoundedEnumRandomR :: (Bounded a, Enum a, RandomGen g) => (a, a) -> g -> (a, g)
    defaultBoundedEnumRandom :: (Bounded a, Enum a, RandomGen g) => g -> (a, g)
    

    These would also be useful to use directly (rather than in a Random instance): suppose you import a type that is an instance of Bounded and Enum but not Random, and you don't want to define an orphan Random instance. So, we could just leave default out of the name.

    The first one doesn't require Bounded either, so we could drop that from the name too.

  9. buggymcbugfix commented on Oct 30, 2017

    @buggymcbugfix

    This would be really nice to have.

  10. kvanbere commented on Oct 31, 2017

    @kvanbere
    Author

    Ah yes, another +1 on this .. it’s 3 year’s old, I had forgotten all about it .

  11. kvanbere commented on Oct 31, 2017

    @kvanbere
    Author

    Is PR accepted ?

  12. cartazio commented on Oct 31, 2017

    @cartazio
    Contributor
  13. Bodigrim commented on Jun 24, 2020

    @Bodigrim
    Contributor

    I'm not convinced that sprinkling a couple of fromEnum / toEnum is that much of boilerplate. It certainly does not justify introducing overlapping instances.

    However, I would not particularly oppose a PR, introducing uniformBoundedEnum :: (Enum a, Bounded a, StatefulGen g m) => g -> m a or something similar.

  14. Shimuuar commented on Jun 24, 2020

    @Shimuuar
    Contributor

    I think we should introduce newtype wrapper which allows to derive Uniform/UniformRange instances using DerivingVia

    newtype UniformEnum a = UniformEnum a
    
    instance Enum a => UniformRange (UnniformEnum a) where
      ...
    instance (Bounded a, Enum a) => Uniform (UnniformEnum a) where
      ...
    
    data RGB = R | G | B
      deriving (Enum.Bounded)
      deriving Uniform via UniformEnum RGB
  15. chessai commented on Jun 26, 2020

    @chessai
    Member

    @Shimuuar that looks good to me!

  16. linked a pull request that will close this issueAdd uniformEnumM and uniformEnumRM #104on May 8, 2021
  17. Bodigrim commented on May 8, 2021

    @Bodigrim
    Contributor

    Closing as done in #104. Feel free to reopen if there is a specific motivation to use this feature via old Random interface.

  18. georgefst commented on Jul 31, 2025

    @georgefst
    newtype UniformEnum a = UniformEnum a
    
    instance Enum a => UniformRange (UniformEnum a) where
      ...
    instance (Bounded a, Enum a) => Uniform (UniformEnum a) where
      ...
    
    data RGB = R | G | B
      deriving (Enum, Bounded)
      deriving Uniform via UniformEnum RGB

    [typos corrected]

    Sorry to resurrect an old thread, but I felt I should point out that this just doesn't work:

    • Couldn't match representation of type: m (UniformEnum RGB)
                               with that of: m RGB
        arising from the coercion of the method ‘uniformM’
          from type ‘forall g (m :: * -> *).
                     System.Random.Internal.StatefulGen g m =>
                     g -> m (UniformEnum RGB)’
            to type ‘forall g (m :: * -> *).
                     System.Random.Internal.StatefulGen g m =>
                     g -> m RGB’
      Note: We cannot know what roles the parameters to ‘m’ have;
            we must assume that the role is nominal.
    • When deriving the instance for (Uniform RGB)typecheck(GHC-18872)
    

    I'm not all that familiar with roles, so I don't know if there's a simple way around this. I suspect not, since this looks similar at a glance to what @amesgen wrestled with in #96.

  19. lehins commented on Aug 1, 2025

    @lehins
    Contributor

    @georgefst Here is the reason why it doesn't work: #185 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions