•explain — Explain
•explain text runs text in the caller’s scope, as ⍎ does, computes its result, and gives a record of that result and of what happened while it ran and computed it. Code that another thread runs meanwhile records into the same report. Computing a pending result at the end makes no forced row:
valueis the result oftext.fusedhas a row for each fused group that ran, as•prefsdescribes fusion.glyphslists the glyphs of the group’s calls in the order they ran. A fold that reads through the group, without building it, adds its glyph last.itemsis the group’s item count.blockscounts the blocks of items that the group ran in turn.runscounts the pieces of work that those blocks split into. A block splits where it crosses the end of a long repeat or cycle in an input, as in adding a row of 5,000 items to every row of a matrix. More runs than blocks means the group ran in smaller pieces.copiedcounts the items copied out of inputs before the calls read them, as a reversed read needs. Acopiedof 0 means the group read every input in place.storedhas a row for each pending array that a fused group read as an input after computing it on its own.glyphsgives the glyph of the call that computed it, which a copy, such as a reversal, doesn’t have.itemsis its item count.reasonsays why the group didn’t compute it as one of its calls:"held outside the graph": a name or another array also holds it, astholdst←y-0.25int×1-t."read out of order": the group doesn’t read item i of it at position i. Windows that overlap read some items more than once, and an outer product repeats each item."kept by a fold": a fold left its calls in it, to compute later."a different kind or width": it gives neither floats of the group’s width nor a comparison of them, or its settings differ."reads a comparison computed outside the graph": its call reads a comparison that something else also holds, or that has been computed already. The group would have to read that comparison’s Booleans as floats.
forcedhas a row for each call or assignment that computed items of pending arrays. Computing an array ends its fusion.functionis the call’s function, or←for an assignment that computed one by writing into it, displaying it with⎕←or destructuring it.lineandcolumngive its position intext, anditemsis how many items it computed. A selection of a few items, such as↑ror2↑r, reads a pending copy from its source and computes nothing. From a pending result that nothing else holds, such as2×y+1in5↑2×y+1, it computes only the items it reads. A pending result that a name holds is computed whole on its first read, and keeps its items for later reads.fasthas a row for each fast combination taken, and for each fold of windows that slides over their source without building them.combinationnames it, andfunctionis the function that took it. The names are"partial grade","partial sort","ranks","group counts","group folds","determinant","permanent","masked select"and"sliding fold".accumulationshas a row for each sum that added items at positions an index gave them, as the gradient of a selection that repeats positions does under∂, and for each modified assignment to a selection, asz.[p]+←1makes.itemsis how many items it added, andsizeis its result’s item count. The gradients of reversal, rotation, take, drop, transpose, windows that move by 1, and⌈and⌊folds read the cotangent instead, and make no row.pathnames how the items were added:"in place"adds into the array, with nothing to undo."logged"keeps each target’s old item, to restore the array if a target fails. Exact integers take this path, because a sum can overflow."on a copy"converts the array first, as for 16-bit floats, or for Booleans that get numbers. Exact integers that get floats stay exact beside them, so they take"rounds"or"one call"."rounds"makes one call for each repeat of a position."one call"makes one call for targets at distinct positions."item by item"calls the function once for each target, as a dfn does.
fallbackshas a row for each map that called its operand once for each cell after its batched call fell back, or without a batched call because an earlier one stopped.itemsis the number of cells, andreasonsays why:"an effect": the operand writes output, reads input, formats a cell’s value with⍕, or runs a system function that acts, such as•ngetor•time. A batched call would do each of these once for all the cells."a write": the operand writes to a name outside the map, or a side of a guard on a cell’s value writes to a name outside its dfn, or the operand appends to a value that differs from cell to cell, or writes into one so that it would be held in mixed storage."a cell's value": the operand reads a cell’s value to give a count or a position, as2×⍣⍵does. A predicate on a cell’s value doesn’t stop the batched call: each side of the guard runs once, on the cells that take it, as Evaluation describes."no batch rule": the operand calls a function with no rule for batches of these arguments, such as∪, or the two sides of a guard on a cell’s value give results of different kinds or shapes. Where the stop comes from a primitive’s call, the primitive’s glyph follows, as in"no batch rule for ∪". A kernel that reads a cell’s number directly gives no glyph."too many items": a call would repeat an array that every cell shares into far more items than its arguments hold, and the array is too large for the map to run on chunks of cells instead, as Evaluation describes."a caught error": a guard would catch an error."a recursive call": a guard on a cell’s value runs in a recursive call of a dfn whose guard already split the cells, as in{⍵<2?⍵;(∇⍵-1)+∇⍵-2}¨v."an earlier stop: "and one of the reasons above: the operand is a dfn whose batched call stopped on an earlier call, for a cause that would recur, as Evaluation describes. The map calls it once for each cell without trying a batched call."an error": the batched call failed. The calls on each cell then report the error, if one of them fails."the result": the operand’s result wasn’t a batch of the cells’ results, such as a function. A strand counts as one batch where each of its items holds an atom for each cell and the items’ kinds join. So a strand that holds a vector for each cell falls back, and so does one that mixes numbers and characters.
mixedhas a row for each write that left a compact array in mixed storage, as writing an exact integer among floats does under the write rule.codeis the target or call that wrote, andlineandcolumngive its position intext.targetnames the array’s storage before the write, andwrittenthe storage of the values written, as•storagenames them.itemsis the array’s item count.itemwisehas a row for each route that handled an array’s items one at a time, where whole-array code runs most calls.functionis the innermost call that ran it, the target of a selective assignment, or•explainfor a route outside both, andlineandcolumngive its position intext.itemsis how many items the route handled, androutenames it:"item pairs": a pervasive function called itself on each pair of items of mixed storage or of a nested array."batched items": code with no rule for batches read a batch’s items one at a time, even where the batched call then succeeded."∂ items": code with no rule for∂read the items of a value that∂tracks one at a time."accumulation": a sum added items one at a time, as theaccumulationspath"item by item"does."item lanes": a fold or scan by a function with no kernel for its arguments, such as a primitive on mixed storage or a dfn on a lane that no batched map took, called the function on each pair of items in turn."number parts": monadic∨or∧took the parts of integers or exact numbers one at a time, where floats and complex numbers take one pass."nested labels","label ids"and"label offsets": a selection from a nested array labelled or placed its items one at a time, or read positions that had no plan."key groups": Key called its operand once for each group, where no fast combination covered it."batch cells": a batched map ran a system function, or a deal by¿, once for each cell in turn."inverse scan": the inverse of a scan by a function with no kernel called the function’s inverse on each pair of neighbouring items in turn.
declinedhas a row for each faster route that code considered and didn’t take. A decline explains why a slower route ran.routenames the route,reasonsays why code didn’t take it, anditemscounts the items that its declines concerned, summed for each route and reason. The routes are:"fusion": a call that could have joined a fused group ran on its own, because of"16-bit floats","no float array","an exact argument","a result at another width","keys, names or a renderer"or"an argument of another kind or width, or with keys". A pending array also ran without a group where its call reads"a comparison computed outside the graph"."fused fold": a fold computed its argument instead of reading through its group, because"another array holds the array", as a name does, because the argument is"a copy that no call moves under"or reads"a comparison computed outside the graph", or because of"a fold that its graph can't run".- A fast combination’s name, as in
"partial grade", with"arguments that don't suit it": the general path ran instead. "batching": a map didn’t try a batched call, because"the batch pref is off", because of"a label level, or a ∂ call inside a batched run"or"named axes", or because of"an argument too large to lay over the frame"or"an argument that isn't compact or has a named axis"."in place": a kernel copied an argument’s items into a buffer instead of reading them where they are, because of"repeats of each item","a cycle that wraps","steps other than a forward run"or"a repeated item".
Each of fused, stored, forced, fast, accumulations, fallbacks, mixed, itemwise and declined is a record of columns, as •csv gives a table, so (•explain t).fast.combination lists the combinations taken. •prefs ["fusion":$t] writes each forcing call and assignment as it happens. Nothing is recorded outside •explain.
y←¿1000000⍴0
(•explain "+/(2×y+1.5)-1").fused ⍝ ["glyphs":["+×-+/"] "items":[1000000]ₓ "blocks":[245]ₓ "runs":[245]ₓ "copied":[0]ₓ]
e←•explain "≢⍕(2×y+1.5)-1"
e.forced.function ⍝ ["⍕"]
e.fused.glyphs ⍝ ["+×-"]
(•explain "2×⌽y+1").fused.copied ⍝ [1000000]ₓ
t←y-0.25 ⋄ (•explain "t×1-t").stored ⍝ ["glyphs":["-"] "items":[1000000]ₓ "reason":["held outside the graph"]]
(•explain "5↑2×y+1").forced.items ⍝ [5]ₓ
s←2×y+1 ⋄ (•explain "5↑s").forced.items ⍝ [1000000]ₓ
r←⌽y ⋄ (•explain "r.[0]←0").forced.function ⍝ ["←"]
(•explain "(10↑)∘⍋ y").fast.combination ⍝ ["partial grade"]
x←¿1000⍴0 ⋄ p←¿1000⍴1000
(•explain "{+/,(3↕⍵)×⍤1⊢1 2 1}∂ x").accumulations ⍝ ["items":⍬ₓ "size":⍬ₓ "path":0⍴⊂""]
(•explain "{+/⍵.[p]}∂ x").accumulations ⍝ ["items":[1000]ₓ "size":[1000]ₓ "path":["in place"]]
z←1000⍴0ₓ ⋄ (•explain "z.[p]+←1ₓ").accumulations.path ⍝ ["logged"]
(•explain "{2×⍣⍵⊢1}¨⍳5ₓ").fallbacks ⍝ ["reason":["a cell's value"] "items":[5]ₓ]
w←4⍴0.5 ⋄ (•explain "(⍳2ₓ)@[1 3] w").mixed ⍝ ["code":["(⍳2ₓ)@[1 3]"] "line":[1]ₓ "column":[1]ₓ "target":["float64"] "written":["integer"] "items":[4]ₓ]
m←1ₓ 2.5 ⋄ (•explain "m+1").itemwise ⍝ ["function":["+"] "line":[1]ₓ "column":[2]ₓ "route":["item pairs"] "items":[2]ₓ]
(•explain "+/⌽y").declined ⍝ ["route":["fused fold"] "reason":["a copy that no call moves under"] "items":[1000000]ₓ]•memory reports the live record nodes and pending arrays, and what a value keeps alive.