Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-resilience

Resilience pipelines for Pudu: retry, circuit breaker, timeout, fallback, hedging, rate limiting, and chaos injection

0.1.0Apache-2.01

InstallClose

Randomizer.pudu

Pudu63 lines2.5 KB

GitHub ↗
1/** @Resilience.Randomizer.Seam — uniform draws for jitter and injection */2module PuduLangResilience.Randomizer34import Std.Decimal as Decimal5import Std.Random as Random6import PuduLangResilience.Utils.Numeric as Numeric7import PuduLangResilience.Utils.Shared as Shared89/** @Resilience.Randomizer.Randomizer — answers a whole number below a bound */10export type Randomizer = { draw: fn(Int) -> Int }1112/// The number of parts one unit is divided into by `fraction`.13export const SCALE: Int = 10000000001415/// A randomizer seeded from the clock, safe to share between threads.16export fn system() -> Randomizer { fromGenerator(Random.fromClock()) }1718/// A randomizer that draws the same sequence for the same seed.19export fn seeded(seed: Int) -> Randomizer {20  match convertInteger[UInt64](seed) {21    case Some(value) => fromGenerator(Random.fromSeed(value))22    case None => fromGenerator(Random.fromSeed(1u64))23  }24}2526/// A randomizer whose every draw is `ratio` of the bound, rounded down. `ratio` is clamped to27/// the range from 0 up to just below 1.28export fn fixed(ratio: Decimal) -> Randomizer {29  let parts = if ratio <= 0d { 0 } else if ratio >= 1d { SCALE - 1 } else { Numeric.partsOf(ratio, SCALE) }30  Randomizer{draw: fn(bound: Int) -> Int {31      if bound <= 0 { 0 } else { Numeric.multiply(parts, bound) / SCALE }32    } }33}3435/// A whole number from zero up to, but not including, `bound`; zero for a bound below one.36export fn below(source: &Randomizer, bound: Int) -> Int {37  if bound <= 0 { return 0 }38  let drawn = (source.draw)(bound)39  if drawn < 0 { 0 } else if drawn >= bound { bound - 1 } else { drawn }40}4142/// A fraction of one in parts of `SCALE`, from zero up to but not including `SCALE`.43export fn fraction(source: &Randomizer) -> Int { below(source, SCALE) }4445/// A float from zero up to but not including one.46export fn unit(source: &Randomizer) -> Float { Numeric.toFloat(fraction(source)) / Numeric.toFloat(SCALE) }4748/// Whether a draw falls below `rate`, a ratio from 0 to 1. A rate of 1 always holds and a rate49/// of 0 never does.50export fn chance(source: &Randomizer, rate: Decimal) -> Bool {51  if rate <= 0d { return false }52  if rate >= 1d { return true }53  fraction(source) < Numeric.partsOf(rate, SCALE)54}5556/// A randomizer drawing from a generator kept under a lock.57fn fromGenerator(start: Random.Generator) -> Randomizer {58  let state = Shared.shared(start)59  Randomizer{draw: fn(bound: Int) -> Int {60      Shared.change(&state, fn(generator: Random.Generator) -> (Random.Generator, Int) { Random.below(&generator, bound) })61    } }62}63