Skip to content
1 change: 1 addition & 0 deletions examples/stdlib/acme.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import map
import option
import process
import queue
import random
import ref
import regex
import resizable_array
Expand Down
65 changes: 65 additions & 0 deletions examples/stdlib/random.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
prng
int32s:
-1472445053
-935901669
-1244218020
492812375
-894738723
1372722888
-1723450959
2005696606
536774910
2111603542
int32s, part2:
348632114
-493311473
521105902
-441336655
1315564179
-245050234
1432006216
-2018660684
349983049
-1541851413
1242068606
-953174617
728164170
-558026150
812040776
-225070679
125608749
-1547184487
2026319992
-627925429
doubles:
0.009
0.758
0.769
0.032
0.15
0.118
0.03
0.946
0.049
0.565
randomInt:
3 4 3
343
0 6 8
68
2 3 0
230
6 1 0
610
4 0 1
401
2 3 4
234
9 3 2
932
8 2 2
822
3 3 2
332
8 5 1
851
3 changes: 3 additions & 0 deletions examples/stdlib/random.effekt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import random

def main() = random::examples::main()
6 changes: 3 additions & 3 deletions libraries/common/effekt.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ extern pure def infixSub(x: Double, y: Double): Double =

extern pure def infixDiv(x: Double, y: Double): Double =
js "(${x} / ${y})"
chez "(/ ${x} ${y})"
chez "(fl/ ${x} ${y})"
llvm "%z = fdiv %Double ${x}, ${y} ret %Double %z"
vm "effekt::infixDiv(Double, Double)"

Expand Down Expand Up @@ -442,14 +442,14 @@ extern pure def toInt(d: Double): Int =

extern pure def toDouble(d: Int): Double =
js "${d}"
chez "${d}"
chez "(fixnum->flonum ${d})"
llvm "%z = sitofp i64 ${d} to double ret double %z"
vm "effekt::toDouble(Int)"


extern pure def round(d: Double): Int =
js "Math.round(${d})"
chez "(round ${d})"
chez "(flonum->fixnum (round ${d}))"
llvm """
%i = call %Double @llvm.round.f64(double ${d})
%z = fptosi double %i to %Int ret %Int %z
Expand Down
186 changes: 186 additions & 0 deletions libraries/common/random.effekt
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
module random

import stream
import io/error

/// Infinite pull stream of random bytes.
effect random(): Byte

// ---------------------
// Sources of randomness

/// A streaming source (push stream) of byte-level randomness
/// based on Park and Miller's MINSTD with revised parameters.
///
/// Deterministic: needs a 32bit `seed` -- you can use `bench::timestamp`.
def minstd(seed: Int): Unit / emit[Byte] = {
// Initialize state with seed, ensuring it's not zero
var state = if (seed == 0) 1 else seed

def nextInt(): Int = {
// Uses only at most 32-bit integers internally
// (Schrage's method: https://en.wikipedia.org/wiki/Lehmer_random_number_generator#Schrage's_method)
val a = 48271
val m = 2147483647

val q = m / a // 44488
val r = m.mod(a) // 3399

val div = state / q // max: M / Q = A = 48271
val rem = state.mod(q) // max: Q - 1 = 44487

val s = rem * a; // max: 44487 * 48271 = 2147431977
val t = div * r; // max: 48271 * 3399 = 164073129

val result = s - t
// keep the state positive
if (result < 0) result + m else result
}

while (true) {
state = nextInt()
val b = state.mod(256).toByte
do emit(b)
}
}

/// A thin wrapper over `minstd`, handling a reader of random bytes.
///
/// Deterministic: needs a 32bit `seed` -- you can use `bench::timestamp`.
///
/// Implementation is similar to `stream::source`, specialized for bytes and the `random` effect.
def minstd(seed: Int) { randomnessReader: () => Unit / random }: Unit = {
var next = box { 255.toByte } // sentinel value
next = box {
try {
minstd(seed)
<> // safe: randomness generator cannot run out of numbers...
} with emit[Byte] { v =>
next = box { resume(()) }
v
}
}

try randomnessReader() with random {
resume(next())
}
}

/// CSPRNG from `/dev/urandom`, handling a reader of random bytes.
/// Only works on Unix-like OSes!
def devurandom { randomnessReader: () => Unit / random }: Unit / Exception[IOError] =
try {
with readFile("/dev/urandom")
try randomnessReader() with random {
resume(do read[Byte]())
}
} with stop {
do raise(io::error::EOF(), "Unexpected EOF when reading /dev/urandom!")
}

// ------------------------
// Functions using `random`
//
// Always two variants:
// - readType(): Type / random
// - readTypes(): Unit / {emit[Type], random}

def randomByte(): Byte / random = do random()
def randomBytes(): Unit / {emit[Byte], random} =
while (true) do emit(do random())

def randomBool(): Bool / random = {
val b = do random()
b.toInt.mod(2) == 1
}
def randomBools(): Unit / {emit[Bool], random} =
while (true) do emit(randomBool())

def randomInt32(): Int / random = {
var result = 0
repeat(4) {
val b = do random()
result = result * 256 + b.toInt
}
val signBit = result.bitwiseShr(31).bitwiseAnd(1) == 0
result.mod(1.bitwiseShl(31)).abs * if (signBit) 1 else -1
}
def randomInt32s(): Unit / {emit[Int], random} =
while (true) do emit(randomInt32())

/// `max` is _inclusive_!
def randomInt(min: Int, max: Int): Int / random = {
if (min > max) {
randomInt(max, min)
} else {
val range = max - min + 1
val bytesNeeded = (log(range.toDouble) / log(256.0)).ceil

var result = 0
repeat(bytesNeeded) {
val b = do random()
result = result * 256 + b.toInt
}

min + (abs(result).mod(range))
}
}
/// `max` is _inclusive_!
def randomInts(min: Int, max: Int): Unit / {emit[Int], random} =
while (true) do emit(randomInt(min, max))


/// Random double between 0.0 and 1.0
def randomDouble(): Double / random =
(randomInt32().toDouble / 1.bitwiseShl(31).toDouble).abs
// This is not perfect, but it will do for now.
def randomDoubles(): Unit / {emit[Double], random} =
while (true) do emit(randomDouble())


namespace examples {
def main() = {
println("prng")
prngRandom()
}

def prngRandom(): Unit = {
with minstd(1337);

println("int32s:")
repeat(10) {
println(randomInt32())
}

println("int32s, part2:")
repeat(10) {
println(randomInt(0, 2147483647))
println(randomInt(-2147483648, 0))
}

println("doubles:")
repeat(10) {
println(randomDouble().round(3))
}

println("randomInt:")
repeat(10) {
val a = randomInt(0, 9)
val b = randomInt(0, 9)
val c = randomInt(0, 9)
println(a.show ++ " " ++ b.show ++ " " ++ c.show)
println(a*100 + b*10 + c)
}
}

def unixRandom(): Unit = {
with on[IOError].report;
with devurandom;

val a = randomInt32()
val b = randomInt32()

// This is just to use the generated numbers :)
println((a.show ++ b.show).length != 0)
}
}