Zulip Chat Archive

Stream: general

Topic: Not so random


Yaël Dillies (Jan 16 2022 at 20:31):

If you run this file from the command line

import control.random

open io list nat

def rand_nat' (lo hi : ) : rand  :=
λ seed, (rand_nat seed.down lo hi).map id ulift.up

/-- Runs the program. -/
def main : io unit := do
  l  mmap (λ _, io.run_rand (rand_nat' 0 10)) (range 10),
  print_ln l

you always get the same result. This looks like Lean gets the same seed from the system over and over again.

Kyle Miller (Jan 16 2022 at 20:45):

If you don't do docs#io.set_rand_gen, it uses C++'s std::rand, I guess seeded automatically by std::srand(1) (the vm never calls std::srand itself).

You can use docs#mk_std_gen to create seeded random number generators, but then it will use a pseudorandom number generator written in Lean.

It's pretty common for languages to have pseudorandom number generators that are seeded with a constant unless you seed it yourself. (Long ago I remember typing randomize timer in QBasic, to use the current time as a seed.)

Heather Macbeth (Jan 16 2022 at 20:46):

Kyle Miller said:

(Long ago I remember typing randomize timer in QBasic, to use the current time as a seed.)

Ha, me too!

Heather Macbeth (Jan 16 2022 at 20:47):

00's kids ...

Yaël Dillies (Jan 16 2022 at 20:47):

I know about mk_std_gen, and running it from the command line was my attempt to not get the same seed over and over again.

Yaël Dillies (Jan 16 2022 at 20:52):

Is there really no way to get nondeterminism?

Kyle Miller (Jan 16 2022 at 20:54):

There's nothing in the vm that calls std::srand, so not using the C++ generator.

Bhavik Mehta (Jan 16 2022 at 20:57):

Kyle Miller said:

If you don't do docs#io.set_rand_gen, it uses C++'s std::rand, I guess seeded automatically by std::srand(1) (the vm never calls std::srand itself).

You can use docs#mk_std_gen to create seeded random number generators, but then it will use a pseudorandom number generator written in Lean.

It's pretty common for languages to have pseudorandom number generators that are seeded with a constant unless you seed it yourself. (Long ago I remember typing randomize timer in QBasic, to use the current time as a seed.)

Is this true? io.run_rand uses mk_generator which says it seeds a random number generator, and the seed is generated using the instance of monad_io_random

Bhavik Mehta (Jan 16 2022 at 20:59):

And that instance is implemented in C++, with the rand part of it depending on std::rand()

Kyle Miller (Jan 16 2022 at 21:02):

io.run_rand does do mk_std_gen for you, using whatever the current random generator is for a seed. If you didn't do io.set_rand_gen, it's the unseeded std::rand.

Kyle Miller (Jan 16 2022 at 21:05):

At least, this is all how I've understood the code. The key part for the behavior of set_rand_gen is https://github.com/leanprover-community/lean/blob/master/src/library/vm/vm_io.cpp#L904

Kyle Miller (Jan 16 2022 at 21:06):

(is_simple checks that the object is not a pointer.)

Bhavik Mehta (Jan 16 2022 at 21:10):

Ah I see, I was also looking at the same bit of code, but I didn't know about the behaviour of std::rand before seeding occurred. I agree with you!

Kyle Miller (Jan 16 2022 at 21:21):

I guess there's no way to get the current time in Lean?

@Yaël Dillies I don't see a good cross-platform way to seed the random number generator and get nondeterminism. It looks like you can (1) run a program that reports the time and then capture the standard output, (2) docs#io.net.connect to some random host for HTTP and rely on the headers containing a timestamp, so you can hash the response to get a seed(!), (3) have a file containing a seed that Lean reads, and by alternative means you update it each time you run lean, or (4) patch lean to have a function that gives the current time.

Yaël Dillies (Jan 16 2022 at 21:27):

I was expecting such a timer function to already exist.

Kyle Miller (Jan 16 2022 at 21:28):

It looks like in Lean 4 you can look forward to io.get_random_bytes, which uses /dev/urandom on Linux and BCryptGenRandom on Windows.

Bhavik Mehta (Jan 16 2022 at 21:50):

I wonder if we can use io.read to get bytes from /dev/urandom already in Lean 3?

Kyle Miller (Jan 16 2022 at 23:27):

@Bhavik Mehta Yeah, certainly, it's just not cross-platform

import control.random

open io list nat

def rand_bytes (n : ) : io (buffer ) := do
  h  mk_file_handle "/dev/urandom" mode.read tt,
  chrs  io.fs.read h n,
  when (chrs.size < n) $ io.fail "did not read enough bytes of randomness",
  pure $ chrs.map char.val

def rand_seed : io unit := do
  bytes  rand_bytes 8,
  let seed :  := bytes.foldl 0 (λ a b, a * 256 + b),
  set_rand_gen $ mk_std_gen seed

def rand_nat' (lo hi : ) : rand  :=
λ seed, (rand_nat seed.down lo hi).map id ulift.up

/-- Runs the program. -/
def main : io unit := do
  rand_seed,
  l  mmap (λ _, io.run_rand (rand_nat' 0 10)) (range 10),
  print_ln l

Kyle Miller (Jan 17 2022 at 18:25):

@Yaël Dillies Since you mentioned that running from the command line was an attempted workaround, then for one of the many things it might be a workaround for I thought I'd mention that #eval runs IO actions.

#eval set_rand_gen $ mk_std_gen 37

#eval io.rand 0 5 >>= print_ln
#eval io.rand 0 5 >>= print_ln
#eval io.rand 0 5 >>= print_ln

Yaël Dillies (Jan 17 2022 at 18:26):

Oh, very cool! And it's also much faster than running it from the command line.


Last updated: Dec 20 2023 at 11:08 UTC