diff options
Diffstat (limited to 'commentary')
| -rw-r--r-- | commentary/why.md | 20 |
1 files changed, 10 insertions, 10 deletions
diff --git a/commentary/why.md b/commentary/why.md index 96515192..03ef81fd 100644 --- a/commentary/why.md +++ b/commentary/why.md @@ -14,11 +14,11 @@ BQN has no intention of being the last word in programming, but could be a pract Here are some more specific comparisons against the two most similar languages to BQN. I'll try to bring up the areas where BQN can be considered worse, but my focus here is definitely on BQN's strong points—I'm not trying to offer an unbiased account. -BQN is more like APL, but adopts some of the developments made by J as well. However, it's much simpler than both, with fewer and less overloaded primitives as well as less special syntax (J has fewer syntactic rules, but more special cases handled during execution that I think *should* have been implemented with syntax). +BQN is more like APL, but adopts some of the developments made by J as well. However, it's much simpler than both, with fewer and less overloaded primitives as well as less special syntax (J has fewer syntactic rules, but more special cases handled during execution that I think *should* have been designed as syntax). The major differences are listed on [the front page](../README.md#whats-the-language-like) ("But it's redesigned…"): [based arrays](../doc/based.md), [list notation](../doc/arrayrepr.md), [context-free grammar](../doc/context.md) and [first-class functions](../doc/functional.md), [reworked primitives](../doc/primitive.md), and dedicated [namespace syntax](../doc/namespace.md). -In addition to these, BQN's [block system](../doc/block.md) extends APL dfns with headers, adding some very useful functionality: the header specifies block type and argument names, and also allows for simple pattern matching when used with multiple block bodies. +In addition to these, BQN's [block system](../doc/block.md) extends APL dfns with headers, adding some very useful functionality: the header specifies block type and argument names, and also allows for some pattern matching when used with multiple block bodies. Since this section gets into the details, it's worth highlighting stranding, a feature I think of as an obvious improvement but that many BQN newcomers see as an obvious sign that I don't know what I'm doing! My full argument for this decision is [here](../doc/arrayrepr.md#why-not-whitespace); the two key points are that stranding is a source of ambiguity that can strike at any time, requiring a correction with `⊢` or `]`, and that typing `‿` is really not hard I promise. @@ -29,7 +29,7 @@ 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 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. +- Random number generation isn't a primitive: instead there's a global generator `•rand` and initialized generators with `•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. @@ -43,13 +43,13 @@ BQN adopts [leading axis theory](../doc/leading.md) as developed in SHARP APL an 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). +BQN's version of the 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. +An APL selective assignment `arr[2 3]+←1` should usually be written with [Under](../doc/under.md) 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 doesn't have an exact replacement for dfn guards, although the [predicate](../doc/block.md#predicates) `?` 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. @@ -63,7 +63,7 @@ The biggest difference could be in file loading. If you write a script that depe cur_script =: {{(4!:3$0) {::~ 4!:4<'y'}} -In BQN it's `•path`. And usually you don't need it because `•Import` resolves paths relative to the file containing it. +In BQN it's `•path`. And usually you don't need it because `•Import` resolves paths relative to the file containing it (if you want to use the shell's current directory, you have to use `•wdpath` explicitly). J uses numeric codes; BQN uses mostly names. So J's `1&o.` is BQN's `•math.Sin`, and `6!:9` corresponds to BQN's `•MonoTime`. @@ -73,13 +73,13 @@ But J has its type advantages as well. I miss complex number support in BQN, as BQN uses a modifier `⟜` for J's hook, adding `⊸` for a reversed version (which I use nearly twice as often). This frees up the 2-train, which is made equivalent to Atop (`∘`). It's the system Roger Hui came to advocate, since he argued in favor of a hook conjunction [here](https://code.jsoftware.com/wiki/Essays/Hook_Conjunction%3F) and made 2-train an Atop when he brought it to Dyalog APL. As an example, the J hook `(#~0&<:)` to remove negative numbers becomes `0⊸≤⊸/` in BQN. Hooks are also the topic of [Array Cast episode 14](https://www.arraycast.com/episodes/episode17-tacit4-the-dyadic-hook), where the panel points out that in J, adding a verb at the far left of a dyadic train changes the rest of the train from dyadic to monadic or vice-versa, an effect that doesn't happen in BQN. -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). +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. J has no mutable data at all; to simulate it properly you'd have to write your own tracing garbage collector, as the J interpreter doesn't have one. 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. +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 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 assumed 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 ``(%&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. +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). Unrelated to these fundamental issues, passing J functions around either as values or gerunds presents some idiosyncratic challenges, discussed below. #### Named functions |
