3
\$\begingroup\$

Next version of Adding a duplicate entry randomly into a list in haskell using random monad

I wrote this trying to set up a Haskell testcase. The aim is to take a list and add a single duplicate from any place in the list to anywhere else in the list. I'm trying to learn to use the Random Monad properly so the main aims should be clear, simple, idiomatic and pure code. However any recommendations for improvement are appreciated.

The code here has most of the improvements suggested by @Petr in the review of the previous version, however

  1. I'm still using lists rather than sequences because the code I plan to test uses lists
  2. I still have an infinite list function because I want to be able to use it easily from IO code where the number of strings to be generated isn't known in advance. I believe that since the Random Monad uses incremental state based on the State Monad, it should be okay to generate infinite lists with it.

-- DataListDuplicator.hs by Michael De La Rue 2014
-- licensed to StackExchange codereview under cc by-sa 3.0
-- may be used under AGPLv3

module DataListDuplicator where 

import Control.Monad.Random

infiniteDuplicateLists :: (MonadRandom m) => [a] -> m [[a]]
infiniteDuplicateLists = mapM addRandomDuplicate . repeat

addRandomDuplicate :: MonadRandom m => [a] -> m [a]
addRandomDuplicate genlist = do
     frompos <- getRandomR (0 ,llen - 1)
     topos   <- getRandomR (0 ,llen)
     let  newlist = listEntryDuplicate frompos topos genlist
     return newlist
   where
     llen = length genlist

listEntryDuplicate from to list =
      start ++ [repeat] ++ end
    where
      repeat = list !! from
      (start, end) = splitAt to list

Here's a little program which drives that:

-- duplicate-to-list-randmonad.hs by Michael De La Rue 2014
-- licensed to StackExchange codereview under cc by-sa 3.0
-- may also be used under AGPLv3
-- N.B. Trivial copying of code fragments does not normally require any license.

import Data.List
import Control.Monad.Random
import DataListDuplicator

main :: IO ()
main = do
    putStrLn $ "list comparison " ++ prettyList list
    g <- getStdGen
    let shuffled = evalRand (infiniteDuplicateLists list) g
    putStrLn $ "lists after \n" ++ intercalate "\n" ( map prettyList (take 5 shuffled))
    where
    list = ["a","b","c"]

prettyList :: (Show a) => [a] -> String
prettyList list = " [ " ++ intercalate "," (map show list) ++ " ] "
\$\endgroup\$
1
\$\begingroup\$

I'd again advise against using monadic computations that produce lazy infinite lists. For most monads (including State) this does not work. For MonadRandom it works only very special cases, when the infinite list is the last randomized computation (if you add another randomized computation after the infinite list, it will diverge).

I can think of two possible solutions:

  1. Find a way to properly formalize this idea of infinite randomized lists and get support from the library authors (or send patches yourself). Since this is for me an interesting problem as well, I started a discussion on this topic.

  2. Use conduits or a similar pipe-like library (such as pipes). This way, you can separate the producer of your values, which can be infinite, and a consumer, that takes only as many values as it needs. The library then "switches" between them: each time a consumer requests input, the producer is asked to provide a new value and is suspended again. In your case this could be used as follows:

    import qualified Data.Conduit as C
    import qualified Data.Conduit.List as C
    import Data.List
    import Control.Monad
    import Control.Monad.Trans
    import Control.Monad.Random
    
    -- ...
    
    -- Produces an infinite source of randomized lists.
    duplicateListsSource :: (MonadRandom m) => [a] -> C.Source m [a]
    duplicateListsSource xs =
        forever $ lift (addRandomDuplicate xs) >>= C.yield
    
    
    main :: IO ()
    main = do
        putStrLn $ "list comparison " ++ prettyList list
        g <- getStdGen
        shuffled <- evalRandT (duplicateListsSource list C.$$ C.take 5) g
        putStrLn $ "lists after \n"
                   ++ intercalate "\n" (map prettyList shuffled)
      where
        count = 5
        list = ["a","b","c"]
    
\$\endgroup\$
  • \$\begingroup\$ Thanks again for the comment. I started to think about the problem more and read your Haskell-cafe post and understand what you are trying to warn me against. I'm going to study the internals of haskell randomness a bit more before I try to make an intelligent comment. This may take some time. \$\endgroup\$ – Michael Jun 1 '14 at 17:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.