2

I am trying to write a function that uses a random number as a condition to compare to a list (which is created by mapping a function over a range of integers). I'm doing it interactively, and if I do it by defining each term separately it works:

import System.Random.MWC (create)
import Statistics.Distribution (genContVar)
import Statistics.Distribution.Uniform (uniformDistr)

rng <- create
rd <- (genContVar (uniformDistr 0 1)) rng

f x = takeWhile (<rd) $ fmap (*x) [1..10]

alternatively, I can use a non-random Double with a let expression and also have no problem

f x = let rd = 0.4 in takeWhile (<rd) $ fmap (*x) [1..10]

however, if I try to put it all together I get an error

f x = let rand <- (genContVar (uniformDistr 0 1) g) in takeWhile (<rand) $ fmap (*x) [1..10]

<interactive>:39:16: error:
parse error on input ‘<-’
Perhaps this statement should be within a 'do' block?

I understand that having different variable types prevents so much as adding up and Int and a Double, and that monads are very particular, but being new to Haskell I'm hoping to avoid the broader philosophy about that for now and instead try to find a practical way of using random numbers in general functions.

  • The "pure" way of having pseudo-random values is to add to each function a new parameter (the current random seed) and a new ouput value (the modified seed, to be used by the following functions). Composing these modified functions can get annoying, and `State` or `State`-like monads can be used to reduce the boilerplate involved and avoid obscuring the main logic of the function. Alas, looking at the signature of `getContVar`, it seems to be inextricably bound to `IO` or `ST` (because of the `PrimMonad` constratint) so we can't use that technique here. – danidiaz Jul 28 '18 at 11:57
  • Thanks, @danidiaz. If using the "pure" approach works I may just try and go for it As for the others, I'm not trying to avoid monads, but I'm having trouble with putting together variables that should in principle be compatible. I do not know what "is Haskell" and did not "learn it" anywhere, I am actually trying to learn some of it now from basic mathematical and programming constructs that are easily functionally implemented in Python, Julia, or even C++. So I appreciate any help in putting together a working example, but please spare the smugness for all of your "fans" out there. – Twisted Mersenne Jul 28 '18 at 15:58
  • @TwistedMersenne Perhaps you could take a look at the "random" library http://hackage.haskell.org/package/random-1.1/docs/System-Random.html You can create a seed using `mkStdGen` (our using `getStdGen`, but that is an `IO` action). The type `StdGen` is an instance of `RandomGen`. `RandomGen` is a typeclass, somethign a bit like an "interface" in other languages. Then you can use functions like `random` or `randomR`, which take the seed and return random values along with a modified seed. The types that can be generated are instances of the `Random` typeclass. – danidiaz Jul 28 '18 at 18:26
  • @danidiaz, thanks. Using randomR may end up being "easier" since I'm trying to combine random numbers with pure mathematical functions. It's more work and not clear to me how to update the rng state in that case, since Haskell (mathematically correctly but annoyingly) doesn't allow something like `(rd,rng) = randomR (0, 1) rng`, but maybe an additional step using a different variable allows me to update a "global" `rng` state each time. If you have a working example, that'd be great, but I'll try to post it if it works. – Twisted Mersenne Jul 28 '18 at 21:22

1 Answers1

2

When you're evaluating expressions in GHCi, you're already in the IO monad. That's why the OP GCHi code works.

As n.m. points out in a comment, monads are the practical way of using random numbers (and all other non-deterministic or effectful behaviour) in Haskell. If you want to write Haskell code, you'll have to learn what they are, sooner or later. The good news, though, is that it's not as difficult to learn what a monad is than some people seem to believe.

Haskell does have syntactic sugar for monads, in the form of do notation. Using that, you can write the function like this:

f x = do
  rng <- create
  rd <- (genContVar (uniformDistr 0 1)) rng

  return $ takeWhile (<rd) $ fmap (*x) [1..10]

This function has the type PrimMonad m => Double -> m [Double]. The presence of PrimMonad in the output type implies some sort of effect. The function can (and is likely to) be impure.

In theory, one could write an entire Haskell program using impure language constructs like the above, but the point of Haskell is to keep as much of the code pure as possible, so one should limit the use of impure code as much as possible.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Thanks, that's really helpful. I should keep that in mind that GCHi is in `IO`, so when I write a one-liner function I'm no longer in `IO` territory, I'm guessing. Could you write a `do` syntax in a single line I can use interactively? And since it's returning a value of fmap over a list shouldn't it be just [Double]? The basics of monads are clear enough, I guess, like Functors and Applicative the definition `(>>=) :: (Monad m) => m a -> (a -> m b) -> m b` says almost all that is needed. In practice specific cases may be less straightforward with all the type restrictions of Haskell. – Twisted Mersenne Jul 28 '18 at 21:10
  • @TwistedMersenne: To write `do` notation as a one-liner, you can use semicolons and (optionally) curly braces instead of relying on the layout rule: `do a <- b; c <- d; e` or `do { a <- b; c <- d; e }`. This also works for other layout-sensitive notation like `case exp of { pat -> exp; pat -> exp; … }` and `let { pat = exp; pat = exp } in exp` since layout is syntactic sugar for explicit delimiters. Alternatively, you can enter multiple lines of input in GHCi with the `:{` and `:}` commands, or `:set +m` to enable multi-line input mode by default—although imo these are both a bit clunky. – Jon Purdy Jul 28 '18 at 21:43
  • Well, that works, on ghci it worked with the multiline environment, but couldn't make it work with curly brackets, though. Still, it returns a monad again and passes on the problem to the next function. I was hoping to use a monadic function to ultimately get a double and not have to wrap the entire program in a monadic construct, but I guess this is it in Haskell. Maybe with a pure prng it's better. – Twisted Mersenne Aug 07 '18 at 14:39
  • @TwistedMersenne *"I was hoping to [...] ultimately get a double and not have to wrap the entire program in a monadic construct"* Perhaps this will be helpful: https://stackoverflow.com/a/51620647/126014 – Mark Seemann Aug 07 '18 at 15:12