Changelog
New features, improvements and fixes in the Probl language and tools. Release notes from the Probl repository.
Probl 0.3.2
Changes on GitHubAdded
envis the environment variables, amap[str, str]constant that the host captures once, liketoday:env.get("REGION", "eu")reads a variable with a default, and"CI" in envsays whether one is set. Every world, run and thread sees the same map, and a program can't change it:env = …,env["A"] = …andenv.insert(…)are compile errors, andvar copy = envmakes a copy that can change. A program's ownenvhides it. Values are text;date(…)reads a date from one, and there's no conversion to numbers yet (semantics).probl runand the REPL give a program their whole environment, captured when they start;probl run --env NAME=VALUEsets a variable in place of the process's. In the library,Options::envgives a variable, and the library never reads the process's environment. The WebAssembly request takes anenvobject. The playground has none, soenvis empty there.- Each GitHub release has a browser bundle,
probl-X.Y.Z-browser.jsonwith its checksum: the WebAssembly engine, its JavaScript adapter and highlighter, the built playground, the changelog, licenses and native reference results. The release tests it in Chrome and attests it like the source archive, and the hosted playground is deployed from it (releases).
Probl 0.3.1
Changes on GitHubAdded
prime_factors(n)lists the primes whose product isn, smallest first and repeated as often as they divide it:prime_factors(360)is[2, 2, 2, 3, 3, 5].is_prime(n)andis_squarefree(n)say whethernis prime, and whether no prime divides it twice. Like the other integer functions they work on bigints and apply to each outcome of a distribution:is_prime(d1000)is 16.80%.is_primeis exact below 3.3 × 10²⁴ and uses the Baillie–PSW test above, which no known composite passes (semantics).divisors(n)lists the divisors ofn, andprimes(hi)orprimes(lo, hi)the primes in a range, both ends included: all 5,761,455 primes below 10⁸ take 0.3 s.next_prime(n)andprev_prime(n)find the nearest prime above and belown.isqrt(n)is the exact floor of a square root, without a float:isqrt(10^400)is10^200.mod_pow(b, e, m)isb^e mod mwithout formingb^e, somod_pow(3, 10^20, 7)is 4, andmod_inverse(a, m)is thexwitha * x mod mequal to 1. Both place results on the modulus's side of 0, asmoddoes.
Changed
euler_phifactors with Miller–Rabin and Pollard's rho method rather than trial division:euler_phi(2^61 - 1)takes milliseconds rather than 9.7 s, and summing it over 1 to 1,000,000 takes 1.0 s rather than 2.1 s. A large prime no longer reaches the work limit; a number with two large prime factors still can.- A value that fails a declared type is described as
typeofwould describe it: "bshould be a list[int], but it's a list[str]", rather than "but it's a list". A declared record type is written astypeofwrites one,{a: int}. - Comparing collections, and copying one to change it while something else holds it, count against the work limit: a unit for each pair of elements compared, or each element copied. A program that compares long lists in a loop can now reach the limit, which before let it run unbounded.
Probl 0.3.0
Changes on GitHubChanged
- A function's return type follows a
:, like a variable's:fn explode(): int { … }rather thanfn explode() -> int { … }. It's still optional. A program written the old way no longer compiles, and the error says how to write it. - A named function used as a value reads the variables it uses when it's called, as a call by name does: after
let f = feeand a change to theratethatfeereads,f(100)uses the new rate, where it used the old one.f == feestays true. Lambdas still keep the values from when they were made. push,pop,insertandremovechange a list or map in place, rather than copying all of it on every call, unless something else holds it: 20,000 pushes in a loop take 0.01 s rather than 0.97 s. On a variable with a declared type, they check only what they add, rather than the whole variable. Built-ins likegetno longer look through every element of a list on every call.
Probl 0.2.2
Changes on GitHubAdded
- More continuous calculations work when enumerating (semantics).
abs,min,max,clamp, andminimumandmaximumof a list, are exact on a draw, including the probability at a bound:max(x, 0) == 0is the probability thatxis negative.sumandmeanof a list of outcomes of one draw, and the built-ins that move values without looking at them, such asget,slice,reverseandpop, keep the draw.report x by x > 1has a group for each side. - Enumeration weighs finite worlds by densities:
let mu ~ one_of([0, 1]); observe 0 from normal(mu, 1)no longer needs sampling. The evidence is then a log density,log evidence -1.1380, and the library'sEvidence::kindsays so. Worlds must observe as many continuous values, outside functions and without unresolved weight; other programs are rejected as unsupported. - Enumeration updates continuous draws exactly by conjugate observations, as sampling does:
let p ~ beta(2, 3); observe 3 from binomial(5, p)reportspas a beta(5, 5), with evidence 4/21. The same goes forbernoulliandscorewith a beta,poissonwith a gamma or exponential rate, andnormalwith a normal mean; a uniform probability counts as a beta(1, 1). A draw restricted by a condition stays restricted.if p,~bernoulli(p)and~binomial(n, p)with a betapbranch exactly, updatingpin each branch. floor,ceil,truncandroundof a draw with bounded values work when enumerating:floor(x)forx ~ uniform(0, 3)is 0, 1 or 2, a third each, and observing it restrictsx. Assigned, it's a plain int in each world, which can index a list.- Squares, square roots, exponentials, logarithms and reciprocals of a continuous draw work when enumerating:
x * x,(x - 1) ^ 2,sqrt(x),exp(x),ln(x),1 / x,2 ^ xand the other logarithms and exponentials. Their quantiles, CDFs and observations are exact, and their means and sds where the draw's family has formulas for them; otherwise a report shows the mean as unavailable.x * xforx ~ uniform(-1, 1)has mean 1/3, andobserve exp(z) > 2restrictszto above ln 2. - Conditions on different continuous draws combine when enumerating:
x > 0 and y > 0for two draws has probability P(x > 0) P(y > 0), and observing or branching on one restricts each draw exactly, in parts:observe x > 0 or y > 0keeps the part wherex > 0, and the part wherex ≤ 0andy > 0. - Sums and differences of normal draws work when enumerating, and so do normal draws and observations whose mean is one:
let y ~ normal(mu, 1)has variance 2 and covariance 1 withmu, andobserve 1.5 from normal(y, 0.5)updatesmuandytogether, exactly, with their covariance. A normal observation's mean can be affine in one draw, likenormal(2 * mu, 1). Conditions on a sum, and sums with a restricted draw, are unsupported. - Continuous draws work inside
simulate, in both modes.simulate { let x ~ normal(0, 1); x > 0 }is a distribution of bools, 50% each. A block whose value holds outcomes of its draws gives a distribution to draw from: each draw is fresh, and the parts of one outcome stay correlated, like the fields of a record. Itsmean,sd,quantileand other queries come from its marginal, and the block can observe densities, sosimulate { let mu ~ normal(0, 1); observe 1.2 from normal(mu, 0.5); mu }is a posterior to draw from. - Reporting a record or list of continuous outcomes summarizes each field, when enumerating:
report {x: x, twice: 2 * x}gives each field's marginal. The library'sGroup::fieldsgives them. @integrate(or--integrate, orOptions::integrate) lets enumeration compute by numerical integration what has no formula: observations of one draw that aren't conjugate, likeobserve true from bernoulli(1 - p)orobserve 3 from poisson(rate)for a lognormalrate, and means without formulas, likeexp(p)for a betap. Results computed that way are approximations to its relative tolerance (1e-9 by default): the summary line saysintegrated to 1e-9, andOutcome::integrationgives it. Without@integrate, such observations stay unsupported, and their error says that@integratewould compute them.- A loop that counts its rounds, or adds to a total that nothing else in the loop reads, is solved as a Markov chain over its other variables when enumerating, rather than unrolled world by world: the same results, much faster. Example 05's race runs in a sixth of the time, and a two-player snakes and ladders game 28 times faster. A loop whose worlds can never leave is found, rather than run to a limit.
pmfandcdfofbinomial,poissonandgeometricare exact, rather than rejected for the tail their listed outcomes leave out:cdf(poisson(3), 2)is 42.32%.- A count law too broad to list, like
geometric(1e-12), can be named and drawn from when sampling, andmean,varianceandsdof it are exact (andpmf,cdf, quantiles and medians, for a geometric). Building one no longer fails on the outcome limit; listing it still does. - An experimental symbolic backend, off by default and not part of the supported API:
probl run --symbolicanswers programs made of Boolean draws, Boolean expressions,if,observe,scoreand reports with binary decision diagrams instead of worlds, with the same results. Eighteen independent faults with evidence that one failed take under a millisecond rather than 0.8 s, and two hundred, which enumeration can't hold, a few milliseconds.--symbolic=preferenumerates the programs it doesn't support (plan). - Each GitHub release has a source archive, with the workspace's
Cargo.lock, a manifest and checksums, all with build-provenance attestations, and the Homebrew tap updates its formula from it (releases).
Changed
- Two capabilities that aren't built yet are reported as
Unsupportederrors rather than language errors: observing a value from a continuous distribution when enumerating, and arithmetic on an undrawn continuous distribution, likenormal(0, 1) * 2. - The container image's
problis built with the whole program optimized together, by the workspace's newdistprofile: its binary is 2.8 MB rather than 3.3 MB, and most models run faster (build profiles).
Probl 0.2.1
Changes on GitHubAdded
- A container image of the
problcommand,ghcr.io/garlab/probl, for linux/amd64 and linux/arm64, published with each release with a provenance attestation. - Each crate has a README of its own on crates.io: the library's and the command's show how to use them, and the internal crates' say to use
problinstead.
Probl 0.2.0
Changes on GitHubAdded
- Failure modes (semantics). A fault that depends on a world's values, like a division by zero or an index past the end, always ends that world. In partial mode the other worlds finish, and the result says how much failed and where; in total mode the run stops, as before. Choose with
@on_error totalor@on_error partial,probl run --on-error,Options::on_erroror the playground's "On error" option. - A partial result:
probl runprints what the other worlds gave, with afailedsection, and exits with status 3.Program::runstill returns an error, with the result inError::partial. try { … } catch DivisionByZero { … } catch { … }: a world where the body faults goes on in the first catch that names its fault, or in a catch without a name, with what it did before the fault. Faults from called functions reach the caller'stry. The faults a catch can name areDivisionByZero,DomainError,IndexOutOfBounds,MissingKey,EmptyCollection,ConversionErrorandNumericOverflow; other errors aren't caught. In the playground, completion offers the fault names aftercatch, and hovering one says what it is.- In the
problcrate:FailureMode,Options::on_error,Program::failure_mode,Error::partial,Failure, andOutcome::failure_mode,failures,failed_shareandfinished.
Changed
tryandcatchare keywords: a program that uses either as a name no longer compiles.- Sampling is partial by default: a run that faults no longer stops the whole sample, and the result is partial, so
probl runexits with status 3 instead of 1. Enumeration stays total by default.--on-error totalor@on_error totalkeeps the old behavior. - Explicit conversions out of range, like
prob(1.5), are now faults; a declared type that a value fails, likelet p: prob = 1.5, still always stops the run.
Fixed
- The
problcrate's documentation linked to probl.dev, which doesn't exist; it now links to the repository.
Probl 0.1.0
Changes on GitHubThe first release.
probl: compile Probl programs, run them by enumeration or by sampling, and read their reports as structured results with their bounds, sampling status and evidence.probl-cli: theproblcommand, withrun,check,schemaandrepl.probl-number,probl-syntax,probl-semaandprobl-engine: the internal crates thatprobldepends on, with no stable API.
The language overview and the reference semantics describe the language, and status and limits lists what isn't implemented yet.