Repository navigation
The prompt rebinds the operators it says it rebinds (#1133) - #1134
Conversation
The Terminal opened `AggressiveOperators`, which rebinds `+ - * / **` *and* `= <> < > <= >=`. The wiki says it rebinds five: "By default it applies aggressive operators which override the `+`, `-`, `*`, `/`, `**` operators for any pair of object. This is not an F# console, but AngouriMath terminal, after all." The comparisons are not in that sentence and never were. What they cost is that `if`, `while`, `List.filter` and a `match` guard all want a `bool` and were getting an `Entity`, so a large part of ordinary F# did not typecheck at a prompt whose purpose is to be typed at. Measured, with and without AngouriMath loaded on the same kernel: `if 1 < 2 then 1 else 0`, `let b: bool = 1 < 2`, `[1;2;3] |> List.filter (fun x -> x > 1)` and an active pattern all failed on the comparison. So the Terminal opens `ArithmeticOperators`, which is the five the documentation promises. `AggressiveOperators` is untouched: it is public surface, its comparison overloads are deliberate and tested, and somebody who wants them opens it at the prompt in one line. That was measured too -- `x > 0` is an ordinary comparison until that line and an inequality after it. **What this does not fix, which is the honest half.** Rebinding arithmetic has the same shape of cost, and it is the cost the tool exists to pay: once `+` and `*` build expressions, a function whose branches mix an `Entity` with an `int` does not typecheck, and `List.sum` over expressions has no `+` it can use. `let rec fib n = if n < 2 then n else fib (n-1) + fib (n-2)` still does not compile. It fails on the arithmetic now rather than on the comparison, and #1133 used it as the headline example, so it is pinned as a test with a remark saying so rather than left to be discovered as an overclaim. Narrowing bought back the part that was costing something and buying nothing. Five of the seven cases in the issue compile now; the two that do not are the trade the tool makes. 41 terminal tests pass, and `AggressiveOperators`' own 18 are unchanged. Part of #1133.
…ants
The previous commit narrowed what the prompt rebinds and left `fib` failing, on the grounds that
rebinding arithmetic is the trade the tool makes. That is true and it is not the whole answer: the
trade can be made per session rather than once, and then `fib` is available.
open Microsoft.FSharp.Core.Operators // fib 20 is 6765, and 3 / 2 is 1
open AngouriMath.Interactive.ArithmeticOperators // back: x + 1 is an expression, 3 / 2 is 3/2
Both directions measured, in one session, either following the other. `operators ()` prints them, so
it is discoverable from the prompt rather than from a wiki page.
**Why not by default.** Rebinding arithmetic and writing ordinary F# are mutually exclusive, which
was measured rather than reasoned: with F#'s own operators `fib 20` is 6765 and `3 / 2` is 1; with
arithmetic rebound `3 / 2` is 3/2 and `x + 1` is an expression, and `fib` does not typecheck. One
operator cannot have both return types.
An overload that keeps `int + int` an int was tried before concluding that -- a witness-constrained
SRTP operator over a base class carrying the general case. It resolves the simple cases and resolves
them all at once: `1 + 1` is 2, `x + 1` is an expression, `1 + 4.5` is 11/2. It does not resolve
`let rec`, because the operator has to be picked while the function's own type is still being
inferred, so it falls to the general case and `fib` fails anyway. Annotating the parameter and the
return type does not help either, which was checked rather than assumed. That is a negative result
about this mechanism, not about every mechanism.
The test that recorded `fib` as broken is replaced by one that runs it, and it fails if `fib` ever
starts compiling by default -- which would mean this reasoning had stopped being true and the remark
should be rewritten.
Part of #1133.
fib compiles nowThe PR above left Measured in one session, either following the other, any number of times. Why not by defaultRebinding arithmetic and writing ordinary F# are mutually exclusive, measured rather than reasoned: with F#'s own operators I tried to have both before concluding that. A witness-constrained SRTP operator over a base class carrying the general case does resolve the simple cases, and resolves them simultaneously:
It does not resolve The test that recorded
|
… case Each case was calling createKernel, which starts an F# compiler service, and most of the new ones then loaded AngouriMath into it as well: about thirty-four compiler services and sixteen full AngouriMath loads for forty-one assertions. Locally that was thirteen seconds and looked like nothing. On the macOS runner the `Test Terminal Lib` step ran for over two hours without finishing, while the Linux and Windows legs of the same run passed and the previous master run finished the same step in two minutes. So the cost was real and this machine was hiding it. xUnit runs the cases in a class one after another, so there was nothing to share a kernel with in the first place. The cases that only ask the kernel to compile something now share one; the two that change a session by `open`ing something still take a fresh one each, since that is the whole of what they test. Forty-one assertions, five seconds instead of thirteen.
…service Every test in this assembly starts an F# compiler service, and xUnit runs test classes in parallel by default. Two modules were fine; the third made three kernels start together, and on the macOS runner the `Test Terminal Lib` step then ran for over an hour against the two minutes it takes on master, while the Linux and Windows legs of the same run passed. Sharing a kernel within each class was the previous commit and was not enough on its own: it cut thirty-four kernels to four, and the four still started at the same time. Measured rather than assumed -- that commit went to CI and the step hung again. Serialising is right on the merits rather than as a workaround. There is nothing to gain from running two compiler services side by side on a three-core runner, and the assembly takes seven seconds either way here. The config is copied to the output directory, which is checked rather than assumed: a xunit.runner.json that stays in the source tree configures nothing.
Closes #1133.
The issue offered three options and did not pick one, because option 3 "changes what documented examples do and would want measuring against the wiki's samples first". That measurement inverts the concern.
The wiki already documents option 3
Five operators. The comparisons are not in that sentence and never were — and the Terminal's page has no code samples at all, so there is nothing documented to break. The code rebinds eleven; the documentation describes five. This makes the code match.
What the extra six were costing
Measured on one kernel, with and without AngouriMath loaded:
if 1 < 2 then 1 else 0bool, gotEntity1let b: bool = 1 < 2True[1;2;3] |> List.filter (fun x -> x > 1)[2; 3]seq { 1..100 } |> Seq.filter (fun x -> x % 7 = 0) |> Seq.length14n % 2 = 0eAnd the reading the Terminal exists for is untouched —
x + 1,3 / 2→3/2,x ** 2,solutions,derivativeall unchanged.AggressiveOperatorsis not touchedIt is public surface, its comparison overloads are deliberate and covered by their own tests, and Jupyter users may well want
x > 0to be an inequality. All 18 of its tests still pass.The old behaviour is one line at the prompt, and that is measured rather than asserted:
x > 0is an ordinary comparison untilopen AngouriMath.Interactive.AggressiveOperators, and an inequality after it. That is option 2 from the issue, obtained without adding any API.What this does not fix
Rebinding arithmetic has the same shape of cost, and it is the cost the tool exists to pay. Once
+and*build expressions, a function whose branches mix anEntitywith anintdoes not typecheck, andList.sumover expressions has no+it can use:fibwas the issue's headline example and it still fails — on the arithmetic now rather than on the comparison. Rather than let that be discovered as an overclaim, it is pinned as a test whose remark says so, and which fails if it ever starts working so the remark gets rewritten.Five of the issue's seven cases compile now. The two that do not are the trade the tool actually makes, and it is the documented one.
The wiki
The Terminal page's sentence becomes accurate with this change rather than needing an edit. Worth adding a line about
open AngouriMath.Interactive.AggressiveOperatorsfor whoever wants the comparisons, but that is the wiki repo rather than this one.Failed: 0, Passed: 41inTerminalUnitTests, andFailed: 0, Passed: 18inInteractiveWrapperUnitTests.🤖 Generated with Claude Code
https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura