Return type of random in Haskell when used in a function

Given:

import System.Random

data Rarity = Common | Uncommon | Rare | Legendary
              deriving (Show, Eq, Ord, Enum, Bounded)

I wanted to instance Random, so I started out with something like:

randomRarity :: (RandomGen g) => g -> (Rarity, g)
randomRarity g = case random g of
  (r,g') | r < 0.50 -> (Common, g')
         | r < 0.75 -> (Uncommon, g')
         | r < 0.88 -> (Rare, g')
         | otherwise -> (Legendary, g')

but this fails because even though it knows it needs a random Ord from all the comparisons, it can't tell that what I really want is a random Float. So I think I need to type the return value of random g, but I ran into a problem:

(random g :: (Float, ???))

Since it's a tuple, what do I declare as the second type? I tried something like (random g :: (Float, RandomGen t)), but t cannot be deduced and I don't know how to match it up with g's type. I got it to work by using StdGen everywhere instead of RandomGen g, but then I couldn't instance Random, and it probably ought to work with any random RandomGen, anyway. And, to be honest, I don't even care WHAT it is, since I'm just passing it on, but it feels like I'm being forced to. I tried to figure out the right type by doing the following:

randomRarity g@(RandomGen t) = case (random g :: (Float, RandomGen t)) of
  ...

but that operates on type constructors (private ones, no less), not types, so I think that's a fundamentally wrong approach.

After reasoning about it, the thing that finally worked for me was the following:

randomRarity g = case random g of
    (r,g') | r' < 0.50 -> (Common, g')
           | r' < 0.75 -> (Uncommon, g')
           | r' < 0.88 -> (Rare, g')
           | otherwise -> (Legendary, g')
           where r' = r :: Float

which is reasonably concise, but it declares another variable that's located far away from the thing it is intended to affect, and it means you have to do a double-take when you see that r' and then go figure out what it is, then come back. Worst of all, it leaves my curiosity unsatisfied. So my question is:

Is there a way in this context to tell random g to generate a Float at the time I call it either by correctly declaring the second type in the tuple, or else by somehow inferring it from g. Or, failing that, is there a way to constrain the type of r without declaring another variable like r'?

Answers


I think in this case the simplest solution is to explicitly type one of the numbers you're comparing to. This forces the (<) operation to be specialized to Float, which in turn forces r to be Float:

randomRarity :: (RandomGen g) => g -> (Rarity, g)
randomRarity g = case random g of
  (r,g') | r < (0.50 :: Float) -> (Common, g')
         | r < 0.75            -> (Uncommon, g')
         | r < 0.88            -> (Rare, g')
         | otherwise           -> (Legendary, g')

Otherwise, this is a know problem in Haskell and the standard solution is to use asTypeOf :: a -> a -> a. It's just a type-restricted version of const, which restricts the type. So in this case, if we can't know the exact type of random g :: (Float, ???), we could import first from Control.Arrow and map over the first element of the pair for example like this:

randomRarity g = case first (`asTypeOf` (0 :: Float)) $ random g of
    ...

In particular, the added expression is of type

first (`asTypeOf` (0 :: Float)) :: (Float, a) -> (Float, a)

It's not very concise, but it works and is localized to the one place random g is used.


If you're using GHC, you can use -XScopedTypeVariables (or add {-# LANGUAGE ScopedTypeVariables #-} to the top of your file) with the code

randomRarity g = case random g of
      (r::Float,g') | r < 0.50 -> (Common, g')
                    | r < 0.75 -> (Uncommon, g')
                    | r < 0.88 -> (Rare, g')
                    | otherwise -> (Legendary, g')

Or if you want to use standard Haskell, you can use

randomRarity g = let (r,g') = random g
                 in case r :: Float of
                   r | r < 0.50 -> (Common, g')
                     | r < 0.75 -> (Uncommon, g')
                     | r < 0.88 -> (Rare, g')
                     | otherwise -> (Legendary, g')

Need Your Help

how to filter the string/list?

java json list filter

I have a no. of arrays/list items in the format: (I am using Java)

Why lombok doesn't create getter?

getter builder lombok

I imported a project that use lombok to decrease code, but I got error "The method getBooks() is undefined for the type Author", where Book and Author are two entities.