From 8b46394437ed6dbdcb75a7cf727deadd2ca9e101 Mon Sep 17 00:00:00 2001 From: Marshall Lochbaum Date: Wed, 9 Feb 2022 22:09:42 -0500 Subject: You can't hide from your problems but you can rearrange them as much as you like --- commentary/problems.md | 166 +++++++++++++++++++++++++------------------------ 1 file changed, 85 insertions(+), 81 deletions(-) (limited to 'commentary/problems.md') diff --git a/commentary/problems.md b/commentary/problems.md index e7a09450..a168fe81 100644 --- a/commentary/problems.md +++ b/commentary/problems.md @@ -10,132 +10,131 @@ I've omitted problems that are obviously addressed by speculated extensions. Of A pretty fundamental problem with dynamically-typed array languages: when computing something (say, a sum) that depends on all elements, if there are no elements then the structure of the result is indeterminate. Shape arithmetic means the shape of a cell is always known, except when using the Rank modifier so that every cell is computed independently. [Fills](../doc/fill.md) are BQN's solution for deeper structure, but they're incomplete. They store only types and not data, but operations like Reshape that use data to determine type are common enough to make this unreliable. ### Incoherent monad-dyad builtin pairs -BQN inherits the functions `+×⌊⌈|`, and adds the functions `∧∨<>≠≡≢↕⍷`, that are only paired for their glyphs and not for any other reason (that is, both function valences match the symbol but they don't match with each other). I find there are just not enough good glyphs to separate all of these out, but I'm sure the pairings could be improved. +BQN inherits the functions `+×⌊⌈|`, and adds the functions `∧∨<>≠≡≢↕⍷`, that are only paired for their glyphs and not for any other reason (that is, both function valences match the symbol but they don't match with each other). I find there are just not enough good glyphs to separate all of these out, but I'm sure the pairings could be improved. In some future language, that is, as BQN is past the point of being able to change these. ### Glyphs are hard to type There's been a lot of work done on this. Still there, still a problem. On the other hand, glyphs are easy to read, and write by hand! -### Syntactic type erasure -A programmer can call a modifier on either a syntactic function or subject, but there's no way to know within the modifier which syntax that operand had. Maybe this is a better design, but it doesn't feel quite right that `f˜` is `f`-Swap if `f` has a function value. The subject syntax suggests it should be Constant. Instead the Constant modifier `˙` has been added partially to mitigate this. - ### Search function depth The simplest way to define a search function like Index Of is to require `𝕨` to be a list, and search for an element that matches `𝕩`. But this means you can only search for one element at a time, which is annoying and doesn't work for Progressive Index Of. So we instead treat the searched argument as a list of major cells. Then we decide to search for cells of the other argument that have the same rank as those cells, since only cells with the same rank can match. That's a little strange for Bins, where it still makes sense to compare cells of different ranks. Furthermore, the result of any search function is always an array. To search for a single element and get an plain number, you need something like `list⊸⊐⌾v-n` (equivalent to `v<5+n`) is conceptually okay, but the first will always fail because `a+b` is invalid while the second will (even worse!) fail only if `v` is a character with code point smaller than `n`. Arithmetic manipulations that would be valid for numbers aren't for the number-character system. +### No one right way to check if a value is an array +The mathematical approach is `0<≡𝕩`, which can be slow without runtime support, while the efficient approach is `0=•Type𝕩`, which is ugly and uses a system function for something that has nothing at all to do with the system. These are minor flaws, but programmers shouldn't have to hesitate about which one they want to use. -Numbers and characters are subsets of a linear space with components "characterness" (0 for numbers and 1 for characters) and value (code point for characters). Numbers are a linear subspace, and characters a subset of an affine one. Their union isn't closed under addition and subtraction in either component. Usually this is good, as failing when the user creates a nonexistent character or double-character can catch a lot of errors. But not always. +### Tacit code can't build lists easily +It's unergonomic, and also quadratic in a naive runtime. The problem of course is that tacit code can only combine up to two values at a time, while in explicit code, list notation combines any number of them. In a language less beholden to syntax, `List` would simply be a function with an arbitrary number of arguments and you'd be able to form trains with it—although this *does* require distinguishing when it's used as a train versus as a plain function. First-class functions get you this behavior if you really need it. ### Monadic argument corresponds to left for `/` and `⊔` Called dyadically, both functions shuffle cells of the right argument around, which is consistent with other selection-type functions. But the monadic case applies to what would be the left argument in the dyadic case. -### Hard to manipulate the result of a modifier -Trains and compositions make it easy to work with the results of functions, in some sense. The same can't be said for modifiers: for example, in a non-immediate block modifier, the derived function is `𝕊`, but you can't apply `˜` to it. This seems to call for modifier trains but people who worked with early J were confident they're not worth it. Except they just added them back. Who knows. +### High-rank array notation awkwardness +The notation `[]` will be added for high-rank arrays, the same as BQN's lists `⟨⟩` except it mixes at the end. It looks okay with BQN strands but clashes with BQN lists. At that point it becomes apparent that specifying whether something is a high-rank array at the top axes is kind of strange: shouldn't it be the lower axes saying to combine with higher ones? A more concrete point of awkwardness is that literal notations can only form arrays with rank 1 or more: syntax with `<` and `[]` would be complete over non-empty arrays. + +### Assert has no way to compute the error message +In the compiler, error messages could require expensive diagnostics, and in some cases the message includes parts that can only be computed if there's an error (for example, the index of the first failure). However, Assert (`!`) only takes a static error message, so you have to first check a condition, then compute the message, then call Assert on that. Kind of awkward, but better than it used to be before one-argument Assert was changed to use `𝕩` for the message. The issue generally applies to high-quality tools built in BQN, where giving the user good errors is a priority. ### Monadic `⊑` versus `>` Both pull out elements and reduce the depth. But they face in opposite directions. However, neither should be thought of as the inverse to `<`: that's `<⁼`. And `>` can't reduce the depth to 0, so it's pretty different from `⊑` or `<⁼`. @@ -145,37 +144,45 @@ The directions of `⊏⊐` and so on were mainly chosen to line up with `∊`: t ### Can't take Prefixes or Suffixes on multiple axes This is a natural array operation to do, and results in an array with a joinable structure, but as Prefixes and Suffixes are monadic there's no way to specify the number of axes to use. +### Hard to manipulate the result of a modifier +Trains and compositions make it easy to work with the results of functions, in some sense. The same can't be said for modifiers: for example, in a non-immediate block modifier, the derived function is `𝕊`, but you can't apply `˜` to it. This seems to call for modifier trains but people who worked with early J were confident they're not worth it. Except they just added them back. Who knows. + ### Modified assignment modifies the left (secondary) argument So you end up with `˜↩` a lot of the time. For ordinary assignment it's pretty reasonable to say the value is primary, but modified assignment flips this around. +### Changing boundary behavior can require very different code +This mainly applies to pairwise operations; for bigger stencils you'd use Windows, and probably handle boundaries with multidimensional selection. For pairwise operations there are four different paths you might use: decrease size using `↓`; periodic conditions with `⌽`; fixed or computed boundaries with `«` and `»`; and increased size with `∾`. Having all this flexibility is great, and it's hard to imagine a parametrized system that offers the same without being difficult to remember. However, some of these functions take lengths and some take values, the latter class only works on one dimension at a time, and for `∾` the argument can go on either side. This is frustrating if you have a reason to switch between the conditions. + +### Only errors in functions can be caught +The modifier `⎊` allows errors in a function to be caught, but a more natural unit for this is the block (scope, really). However, catching errors shouldn't be common in typical code, in the sense that an application should have only a few instances of `⎊`. Ordinary testing and control flow should be preferred instead. + +### Modifiers look looser than trains without spaces +Consider `⋆∘-ט`. It's just a sequence of three functions so the use of `∘` rather than `·` is to highlight structure: `⋆∘-` is more tightly bound so the suggestion is to consider this composition as a single entity. But in fact `-` is closer to `ט` than to `⋆`, intuitively suggesting the opposite. Adding a space fixes it: `⋆∘- ט` visually connects `⋆∘-`. It's unfortunate that this is something the writer must do rather than something the notation encourages. + +### Have to be careful about intermediate results with affine characters +A computation like `(a+b)÷2` (midpoint between characters `a` and `b`, of the distance between them is even) or `5>v-n` (equivalent to `v<5+n`) is conceptually okay, but the first will always fail because `a+b` is invalid while the second will (even worse!) fail only if `v` is a character with code point smaller than `n`. Arithmetic manipulations that would be valid for numbers aren't for the number-character system. + +Numbers and characters are subsets of a linear space with components "characterness" (0 for numbers and 1 for characters) and value (code point for characters). Numbers are a linear subspace, and characters a subset of an affine one. Their union isn't closed under addition and subtraction in either component. Usually this is good, as failing when the user creates a nonexistent character or double-character can catch a lot of errors. But not always. + +### Each block body has its own label +In a block with multiple bodies, the label (the self-name part of the header) refers to the entire block. However, there's no way to give only one label to the entire block. If you want to consistently use the same internal name, then you may have to write it many times. It's also a weird mismatch, conceptually. + ### And/Or/Max/Min are all tangled up Boolean And (`∧`) and Or (`∨`) are identical to Min (`⌊`) and Max (`⌈`) when restricted to Boolean arguments, and this would fit nicely with their monadic role as sorting functions: for example `a∧b ←→ ⊑∧a‿b`. Furthermore the pairing of Min with Floor and Max with Ceiling is mnemonic only and not especially natural. The reason I have not used these glyphs for Min and Max, and have instead extended them to the somewhat superfluous [arithmetic logical functions](../doc/logic.md) is that Min and Max have different [identity elements](https://aplwiki.com/wiki/Identity_element) of `∞` and `¯∞` rather than `1` and `0`. Having to code around empty arrays when using `∧´` would be a fairly big issue. The other drawback of Min (`∧`) and Max (`∨`) is that the symbols are counterintuitive, but I have found a way to remember them: consider the graph of variables `a←x` and `b←¬x` for x from 0 to 1: two crossed lines. Now the graph of `a∧b` is a caret shape and `a∨b` is a vee. -### Acting on windows can be awkward -When taking Windows along more than one axis, acting on the resulting array requires the Rank modifier, duplicating either the right argument rank or (negated) left argument length. A nested Windows would only require Each. - ### Inputs to modifiers are called operands? "Operand" is derived from "operator". "Modificand" would be better if it weren't both made up and hideous. -### Converting a function expression to a subject is tricky -You can name it, you can write `⊑⟨Expr⟩` or `(Expr)˙0`, and if it doesn't use special names you can write `{Expr}`. All of these are at least a little awkward in reasonable cases. Should there be a dedicated syntax? Note that going the other way, from subject to function, isn't too bad: the modifier `{𝔽}` does it, as does `○⊢`. - ### Scan ordering is weird Scan moves along the array so that it uses results as left arguments, which is opposite to the usual right-to-left order of evaluation. But I think this is still better than scanning the array in reverse. You can always use Swap on the operand, or recover the APL scan ordering by doing a Reduce-Each on Prefixes. -### Only errors in functions can be caught -The modifier `⎊` allows errors in a function to be caught, but a more natural unit for this is the block (scope, really). However, catching errors shouldn't be common in typical code, in the sense that an application should have only a few instances of `⎊`. Ordinary testing and control flow should be preferred instead. - -### Special names other than 𝕣 can't be written as modifiers -I decided that it was better to allow `𝕨_m_𝕩` to work with no spaces than to allow `_𝕩` to be a modifier, and this rule also helps keep tokenization simple. But to apply `𝕩` as a modifier you have to give it a different name. - ### Bins is inconsistent with Index of In Dyalog APL, Interval Index is identical to Index Of if the left argument has no duplicate cells and every right argument cell intolerantly matches a left argument cell. In BQN they're off by one—Bins is one larger. But all the caveats for the Dyalog relation indicate this might not be so fundamental. -### Changing boundary behavior can require very different code -This mainly applies to pairwise operations; for bigger stencils you'd use Windows, and probably handle boundaries with multidimensional selection. For pairwise operations there are four different paths you might use: decrease size using `↓`; periodic conditions with `⌽`; fixed or computed boundaries with `«` and `»`; and increased size with `∾`. Having all this flexibility is great, and it's hard to imagine a parametrized system that offers the same without being difficult to remember. However, some of these functions take lengths and some take values, the latter class only works on one dimension at a time, and for `∾` the argument can go on either side. This is frustrating if you have a reason to switch between the conditions. +### Special names other than 𝕣 can't be written as modifiers +I decided that it was better to allow `𝕨_m_𝕩` to work with no spaces than to allow `_𝕩` to be a modifier, and this rule also helps keep tokenization simple. But to apply `𝕩` as a modifier you have to give it a different name. Could actually be a good thing in that it encourages you to stick to functions, as they're nicer in lots of other ways. ### Exact result of Power is unspecified The other arithmetic functions round to nearest, and compound functions such as `⊥` have been removed. But Power makes no guarantees, and the result could change over time based on different special code. Dyadic logarithm is similar, but expected because of its inverse status. @@ -186,9 +193,6 @@ Select chooses whether the left argument maps to right argument axes or selects ### Unclear primitive names Blanket issue for names that I don't find informative: "Solo", "Bins", "Find", and "Group". -### Modifiers look looser than trains without spaces -Consider `⋆∘-ט`. It's just a sequence of three functions so the use of `∘` rather than `·` is to highlight structure: `⋆∘-` is more tightly bound so the suggestion is to consider this composition as a single entity. But in fact `-` is closer to `ט` than to `⋆`, intuitively suggesting the opposite. Adding a space fixes it: `⋆∘- ט` visually connects `⋆∘-`. It's unfortunate that this is something the writer must do rather than something the notation encourages. - ### Tacit exports can leak data One of the nice facets of BQN's module system is that it provides perfect encapsulation: if you have variables `a` and `b` in a namespace (or closure) initialized so that `a≤b`, and all exported operations maintain the property that `a≤b`, then that property will always be true. Well, not quite: if you define, say `Inc ⇐ IncA ⊣ IncB` to increase the values of both `a` and `b` by `𝕩`, then `Inc` maintains `a≤b`, but `IncA` doesn't—and it can be extracted with `•Decompose`. This isn't too serious because it sounds impossible to do accidentally, and it's easy to protect against. @@ -196,7 +200,7 @@ One of the nice facets of BQN's module system is that it provides perfect encaps This is the best ordering, since it's consistent with `⟨⋄⟩` lists. And code in a strand probably shouldn't have side effects anyway. Still, it's an odd little tack-on to say separators *and strands* go left to right, and it complicates the implementation a little. ### Primitive name capitalization -I went with "Index of" and "Less Than or Equal to" but the last word blends into surrounding text. Should they be fully capitalized or hyphenated? +I went with "Index of" and "Less Than or Equal to" but the last word blends into surrounding text. Should they be fully capitalized or hyphenated? I've started to capitalize when there's ambiguity actually. ## Solved problems -- cgit v1.2.3