haskell-numerics / random-fu

A suite of Haskell libraries for representing, manipulating, and sampling random variables
42 stars 21 forks source link

Proposals: Data.Random.RVar.Enum, Data.Random.List, and Data.Random.Choice... #18

Closed mgajda closed 10 years ago

mgajda commented 10 years ago

Hi,

I really like the way RVar monad works, and I have found myself making reusable code for some common use cases:

  1. Generic Uniform instance for Enum types, that generalizes most of Uniform instances for types that already have Enum.
  2. randomElement is more practical with V.Vector than with lists, for efficiency reasons.
  3. For those of us that occasionally make a random choice out of two different RVars, I added randomChoice that works as if then else operator, except that it takes ratio of probabilities of its alternatives instead of Bool.
{-# LANGUAGE OverlappingInstances, MultiParamTypeClasses, FlexibleInstances #-}
module Data.Random.RVar.Enum() where

import Data.Random.RVar
import Data.Random.Distribution
import Data.Random.Distribution.Uniform
import Control.Applicative((<$>))

-- TODO: Send to library author as a "missing instance"
instance (Enum a) => Distribution Uniform a where
  rvarT (Uniform l h) = toEnum <$> uniformT (fromEnum l) (fromEnum h)
{-# LANGUAGE FlexibleContexts #-}
-- | Main module generating passwords.
module Data.Random.Choice(randomChoice) where

import           Data.Ratio
import           Data.Random.RVar
import           Data.Random.Distribution
import           Data.Random.Distribution.Uniform

-- | Performs random choice between two RVar values.
--   Input is a _ratio_ of the _relative_ probabilities between first and
--   second option (A/B).
randomChoice :: (Fractional r, Ord r, Data.Random.Distribution.Distribution Uniform r) => r ->
                   RVar b -> RVar b -> RVar b
randomChoice ratio variantA variantB = do draw <- uniform 0 1
                                          if draw >= probabilityOfVariantA
                                            then variantA
                                            else variantB
  where
    probabilityOfVariantA = 1/(1+1/ratio)
module Data.Random.Vector(randomElement) where

import           Data.Random.RVar
import           Data.Random.Distribution.Uniform
import qualified Data.Vector  as V
import           Control.Applicative

-- | Take a random element of a vector.
randomElement :: V.Vector a -> RVar a
randomElement words = (words V.!) <$> uniform 0 (V.length words - 1)

Please let me know, if you need any corrections in order to include it in the upstream, or have any issues with it.

mokus0 commented 10 years ago

Unfortunately, because of the way type-class instances work in (all present implementations of) Haskell, instance Enum a => Distrubution Uniform a is a potentially dangerous instance, because it overlaps and creates ambiguity with every other possible instance Distrubution Uniform t in ways that can cause weird behavior - especially for types such as Double where the Enum behavior is wildly different from the expected behavior of a uniform distribution. It becomes a bit of "luck of the draw" (unless you know specifically how instances are resolved, in which case it's just "annoyingly inconsistent") whether you end up getting the generic Enum-based instance or the specialized Double instance. This general issue is why I've exposed all the different utility functions to sample uniform values based on class constraints. There's an enumUniform function which for some reason isn't exported from Data.Random.Distribution.Uniform. I'll fix that.

randomChoice is a useful function, and (if I'm reading correctly) is actually equivalent to a probably-poorly-named function already in random-fu: Data.Random.Distribution.Bernoulli.generalBernoulli

A vector-based randomElement would also be a good thing to include. I'll go ahead and add that.