diff options
| author | Marshall Lochbaum <mwlochbaum@gmail.com> | 2022-01-06 11:15:24 -0500 |
|---|---|---|
| committer | Marshall Lochbaum <mwlochbaum@gmail.com> | 2022-01-06 11:15:24 -0500 |
| commit | ad9335c0d1c0c3ec50ec92431f865f1b78666e17 (patch) | |
| tree | bffca85ccb3fc46e49e7c2190960b392d9bfd7fd /commentary | |
| parent | e969134d3923ef2e87f7150746ea68fdbf8adeae (diff) | |
A few more differences versus APL and J
Diffstat (limited to 'commentary')
| -rw-r--r-- | commentary/why.md | 18 |
1 files changed, 12 insertions, 6 deletions
diff --git a/commentary/why.md b/commentary/why.md index 456815d6..96515192 100644 --- a/commentary/why.md +++ b/commentary/why.md @@ -28,25 +28,29 @@ BQN has no built-in control structures, which can be quite an adjustment coming Primitives in BQN are pure functions that don't depend on interpreter settings. The following kinds of interpreter state don't apply: - The index origin is 0. -- APL and J use approximate comparison in primitives, controlled by a value called the comparison tolerance (`⎕CT` in APL). The choice of which primitives use it and how is kind of arbitrary, and a nonzero comparison tolerance can lead to confusion, bugs, and unavoidable performance problems in code. Nonetheless, I planned to add tolerant comparison to BQN—until I realized that after a year spent programming in BQN I'd hardly noticed its absence, and no one had asked for it either. +- APL and J use approximate comparison in primitives, controlled by a value called the comparison tolerance (`⎕CT` in APL). The choice of which primitives use it and how is kind of arbitrary, and a nonzero comparison tolerance can lead to confusion, bugs, and unavoidable performance problems in some primitives. Nonetheless, I planned to add tolerant comparison to BQN—until I realized that after a year spent programming in BQN I'd hardly noticed its absence, and no one had asked for it either. - Random number generation isn't a primitive, but instead uses the global generator `•rand` or an initialized generator `•MakeRand`. This makes managing independent generators easier, and with namespaces [you get](../spec/system.md#random-generation) several convenient functions for different use cases. Some factors specific to APL or J are given in the sections below. ### APL -*See also the [BQN-Dyalog APL dictionary](../doc/fromDyalog.md).* +*See also the [BQN-Dyalog APL dictionary](../doc/fromDyalog.md). I compare to Dyalog here as it's the most widely used dialect.* -BQN cleans up some awkward syntax left over from when each APL operator was special: the outer product is written `Fn⌜` rather than `∘.fn`, and reduction `Fn´ arr` is separated from compress `b/arr`. +BQN cleans up some awkward syntax left over from when each APL operator was special: the outer product is written `Fn⌜` rather than `∘.fn`, and reduction `Fn´ arr` is separated from compress `b/arr` instead of [overloading](https://aplwiki.com/wiki/Function-operator_overloading). -BQN adopts [leading axis theory](../doc/leading.md) as developed in SHARP APL and applied in A+ and J. With this it can collapse APL pairs such as `⌽⊖` and `/⌿` to one primitive each, and remove APL's complicated function index mechanism. The Rank modifier `⎉` then applies these primitives to non-leading axes. While this method is required in J and also favored by many users of Dyalog APL, it definitely doesn't enjoy universal support—it can be harder to learn, and less convenient for some common cases. Summing rows with `+/` in APL is quite convenient, and BQN's `+˝⎉1`, or `+˝˘` for matrices, just aren't as nice. +BQN adopts [leading axis theory](../doc/leading.md) as developed in SHARP APL and applied in A+ and J. With this it can collapse APL pairs such as `⌽⊖` and `/⌿` to one primitive each, and remove APL's complicated function axis (such as `⌽[2]`) mechanism. The Rank modifier `⎉` then applies these primitives to non-leading axes. While this method is required in J and also favored by many users of Dyalog APL, it definitely doesn't enjoy universal support—it can be harder to learn, and less convenient for some common cases. Summing rows with `+/` in APL is quite convenient, and BQN's `+˝⎉1`, or `+˝˘` for matrices, just aren't as nice. Arguably BQN cuts down the set of primitives too much. Base conversion `⊥⊤`, partitioning `⊂⊆`, and matrix division `⌹` are commonly asked-for primitives, but they don't match [my conception](primitive.md) of a primitive. And while each can be implemented (with short snippets, other than `⌹` which requires a library), there's definitely a convenience loss. But there's always [ReBQN](../doc/rebqn.md)… +BQN's Power modifier `⍟` allows an array operand to specify multiple results, for example `Fn⍟(↕4)` to get 0 up to 3 iterations. Intermediate results are saved, so the number of calls only depends on the highest iteration number present. On the other hand, BQN has no direct equivalent of Power Limit `⍣≡`, requiring it to be [implemented manually](https://mlochbaum.github.io/bqncrate/?q=power%20limit). + An APL selective assignment `arr[2 3]+←1` should usually be written with Under in BQN: `1⊸+⌾(2‿3⊸⊏)arr` (but the correspondence might not always be so direct). You can think of this as a very fancy At (`@`) operator, that lets you pull out an arbitrary part of an array. Dfns are adjusted in a few ways that make them more useful for general-purpose programming. A BQN block always runs to the last statement, so a block like `{Update 𝕩 ⋄ 1+x}` won't return early. Writing modification with `↩` makes it clearer which variable's which. Dfns also do a weird shadowing thing where `a←1⋄a←2` makes two different variables; in BQN this is an error because the second should use `↩`. Tradfns are removed entirely, along with control structures. +BQN doesn't have an exact replacement for dfn guards, although the predicate `?` can look similar: `{2|⍵ : 1+3×⍵ ⋄ ⍵÷2}` is equivalent to `{2|𝕩 ? 1+3×𝕩 ; 𝕩÷2}`. But note that where APL uses the statement separator `⋄`, BQN uses the body separator `;`. This means that the if-true branch in BQN can consist of multiple statements (including additional predicates), but also that the if-false branch can't access variables defined in or before the condition. In both cases the "better" behavior can be obtained with an extra set of braces and possibly assigning names to arguments `⍵`/`𝕩`. I think guards end up being cleaner when they work, and predicates are more versatile. + BQN's namespaces have a dedicated syntax, are *much* easier to create than Dyalog namespaces, and have better performance. I use them all the time, and they feel like a natural part of the language. ### J @@ -71,9 +75,11 @@ BQN uses a modifier `⟜` for J's hook, adding `⊸` for a reversed version (whi J locales are not first-class values, and BQN namespaces are. I think BQN's namespaces are a lot more convenient to construct, although it is lacking an inheritance mechanism (but J's path system can become confusing quickly). More importantly, BQN namespaces (and closures) are garbage collected. J locales leak unless manually freed by the programmer. More generally, J has no mutable data at all, and to simulate it properly you'd have to write your own tracing garbage collection as the J interpreter doesn't have any. I discussed this issue some in [this J forum thread](http://www.jsoftware.com/pipermail/programming/2021-April/058006.html). -In J, each function has a built-in rank attribute: for example the ranks of `+` are `0 0 0`. This rank is accessed by the "close" compositions `@`, `&`, and `&.`. Choosing the shorter form for the close compositions—for example `@` rather than `@:`—is often considered a mistake within the J community. And function ranks are unreliable: consider that the ranks of `]@:+`, a function that behaves just like `+`, are `_ _ _`. In BQN there aren't any close compositions at all, and no function ranks. J's `&.>` is simply `¨`, and other close compositions, in my opinion, just aren't needed. +In J, each function has a built-in rank attribute: for example the ranks of `+` are `0 0 0`. This rank is accessed by the "close" compositions `@`, `&`, and `&.`. Choosing the shorter form for the close compositions—for example `@` rather than `@:`—is often considered a mistake within the J community. And function ranks are unreliable: consider that the ranks of `]@:+`, a function that always has the same result as `+`, are `_ _ _`. In BQN there aren't any close compositions at all, and no function ranks. J's `&.>` is simply `¨`, and other close compositions, in my opinion, just aren't needed. + +J has several adverbs (key, prefix, infix, outfix…) to slice up an argument in various ways and apply a verb to those parts. In BQN, I rejected this approach: there are 1-modifiers for basic iteration patterns, and functions such as [Group](../doc/group.md) (`⊔`) that do the slicing but don't apply anything. So `</.~a` is `⊐⊸⊔a`, but `fn/.~a` is `>Fn¨⊐⊸⊔a` (I also reject J's implicit merge except for the Rank modifier, as I don't think function results should be homogeneous by default). BQN's approach composes better, and is more predictable from a performance perspective. -Gerunds are J's answer to BQN's first-class functions. For example J's ``(+&3)`(2&*)@.(2&|)`` would be written `2⊸|◶⟨+⟜3,2⊸×⟩` with a list of functions. I think lists of functions are a big improvement, since there's no need to convert between gerund and function, and no worries about arrays that just happen to be valid gerunds (worried about losing the ability to construct gerunds? Constructing tacit functions in BQN is much easier). The usability gap widens because passing J functions around either as values or gerunds has presents some highly idiosyncratic challenges, discussed below. +Gerunds are J's answer to BQN's first-class functions. For example J's ``(%&2)`(1+3*])@.(2&|)`` would be written `2⊸|◶⟨÷⟜2,1+3×⊢⟩` with a list of functions. I think lists of functions are a big improvement, since there's no need to convert between gerund and function, and no worries about arrays that just happen to be valid gerunds (worried about losing the ability to construct gerunds? Constructing tacit functions in BQN is much easier). The usability gap widens because passing J functions around either as values or gerunds has presents some highly idiosyncratic challenges, discussed below. #### Named functions |
