Disclaimer: I'm using PureScript, but also added the Haskell tag because I assume this might behave the same way in both languages and the Haskell community is bigger.
I want to pick a random element from an array, repeatedly. Each time I expect a new, random pick, but the value is always the same on repeated calls. It seems the random function is only evaluated once per running the program.
This always returns the same name on subsequent calls:
import Data.Array (length, unsafeIndex)
import Effect.Random (randomInt)
import Effect.Unsafe (unsafePerformEffect)
import Partial.Unsafe (unsafePartial)
pick :: forall a. Array a -> a
pick arr = unsafePartial $ unsafeIndex arr i where
i = unsafePerformEffect $ randomInt 0 (length arr - 1)
name :: String
name = pick names
With this workaround, it returns a new random pick each time:
import Data.Array (length, unsafeIndex)
import Effect.Random (randomInt)
import Effect.Unsafe (unsafePerformEffect)
import Partial.Unsafe (unsafePartial)
pick :: forall a. Array a -> a
pick arr = unsafePartial $ unsafeIndex arr i where
i = unsafePerformEffect $ randomInt 0 (length arr - 1)
-- without the dummy argument, this is not re-evaluated
-- on subsequent calls and always returns the same name
name :: Unit -> String
name _ = pick names
I'm using Data.Array, Effect.Random, Effect.Unsafe and Partial.Unsafe.
I feel like this is an ugly hack. What is the proper way of achieving this?
Thanks to the answer of @amalloy I found what I think is a good solution for my case.
The key was to keep the Effect
from the random number generation (Effect
corresponds to IO
in Haskell) instead of discarding it with unsafePerformEffect
. Effect
reflects the fact that some side effect is involved in the computation of that value and it might have different results each time. This is exactly what I want. So with this new type signature it now behaves as I expected: name :: Effect String
. Each time the effect is "run", it randomly selects a new string from the array.
I also use NonEmptyArray now, as @amalloy suggested.
pick :: forall a. NonEmptyArray a -> Effect a
pick arr = do
i <- randomInt 0 (length arr - 1)
let item = arr !! i
case item of
Just one -> pure one
Nothing -> pure $ head arr
-- still have to handle the Maybe from (!!) which is
-- a bit annoying since this obviously can never be Nothing
name :: Effect String
name = pick names
main :: Effect Unit
main = do
name >>= log
name >>= log
name >>= log
-- new pick each time