{"id":20243916,"url":"https://github.com/cratelyn/j","last_synced_at":"2025-04-10T20:32:29.090Z","repository":{"id":205346108,"uuid":"708184763","full_name":"cratelyn/j","owner":"cratelyn","description":"💐 j is a subset of J, and an essay.","archived":false,"fork":false,"pushed_at":"2023-11-23T23:38:24.000Z","size":264,"stargazers_count":40,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-24T18:06:07.787Z","etag":null,"topics":["apl","array-programming","j","k","performance-art","programming-language-concepts","programming-language-theory","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cratelyn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-10-21T19:21:59.000Z","updated_at":"2025-03-17T14:26:29.000Z","dependencies_parsed_at":"2023-11-23T23:23:56.932Z","dependency_job_id":"9bc85c09-e85c-401c-b9e1-3d09625e98e8","html_url":"https://github.com/cratelyn/j","commit_stats":null,"previous_names":["cratelyn/j"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cratelyn%2Fj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cratelyn%2Fj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cratelyn%2Fj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cratelyn%2Fj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cratelyn","download_url":"https://codeload.github.com/cratelyn/j/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248290033,"owners_count":21078923,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apl","array-programming","j","k","performance-art","programming-language-concepts","programming-language-theory","rust"],"created_at":"2024-11-14T09:10:11.791Z","updated_at":"2025-04-10T20:32:29.065Z","avatar_url":"https://github.com/cratelyn.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 💐 j\n\nj is a limited subset of J, an array programming language. this file is an accompanying essay.\n\nthis project is, in spirit, a reimagining of the [\"Incunabulum\"][incunabulum] J interpreter\nfragment, implemented in Rust. this project is, in a sense, a work of speculative science-fiction,\nexamining the linguistics of programming languages.\n\n## ✨ technical overview\n\nthe jsoftware wiki includes the following story concerning the original Incunabulum:\n\n\u003e One summer weekend in 1989, Arthur Whitney visited Ken Iverson at Kiln Farm\n\u003e and produced—on one page and in one afternoon—an interpreter fragment on the\n\u003e AT\u0026T 3B1 computer.\n\naccordingly, j does not intend to be a fully-fledged implementation of an array language.\n\nvisit \u003chttps://www.jsoftware.com/\u003e if you would like to learn more about J. the J wiki's\n[\"Getting Started\"][j-getting-started] page has useful information about installing J, and the\n[\"NuVoc\"][j-nuvoc] page is a useful reference for J's vocabulary.\n\nj values may be integers, arrays, or 2-dimensional matrices; higher-rank matrices are not\nimplemented. a small number of verbs and adverbs are provided. variables may be defined.\n\n**monadic verbs**\n* `i.` \"idot\" generates a value\n* `|:` \"transpose\" rotates its argument\n* `#` \"tally\" counts the elements in its argument\n* `$` \"shape\" returns a value containing the dimensions of its argument\n* `[` \"same\" returns the given value\n* `]` \"same\" returns the given value\n* `\u003e:` increments the given value\n\n**dyadic verbs**\n* `+` returns the sum of its two arguments\n* `*` returns the product of its two arguments\n* `[` \"left\" returns the left value\n* `]` \"right\" returns the right value\n\n**monadic adverbs**\n* `/` \"insert\" places a dyadic verb between items of its argument\n* `\\` \"prefix\" returns successive prefixes of its argument\n\n**dyadic adverbs**\n* `/` \"table\" returns a table of entries using a dyadic verb and two arguments\n* `\\` \"infix\" applies a verb to successive parts of its right-hand argument\n\nvariables are assigned using `=:`. variable names may only contain lowercase ASCII `a-z`\ncharacters, numeric `0-9` characters, and `_` underscores. variable names must begin with a\nlowercase ASCII `a-z` character.\n\n#### 📂 project structure\n\nthe code within this project is structured like so:\n\n```\n.\n├── src\n│  ├── a.rs                array\n│  ├── j.rs                `j` library crate\n│  ├── p.rs                prelude\n│  ├── r.rs                lexing and parsing\n│  ├── s.rs                symbol table\n│  └── x.rs                interpreter binary\n└── tests\n   └── t.rs                test suite\n```\n\n# 📜 implementing j; an essay\n\nthe rest of this document is an essay discussing the experience of writing software, using Rust,\nin a voice inspired by Arthur Whitney's style of C.\n\n### 🌷 an introduction\n\nthe purpose of building j was not merely to implement an array language in Rust. it was important\nto me that i did not write this software in the idiomatic style enforced by `cargo fmt`.\nconciseness should not come from retroactive search-and-replace, but from an honest attempt at\nadopting and recreating the _mindset_ of people who write software like this.\n\nin order to get a concrete sense of what this looks like, let's begin by reading a few snippets\nfrom the Incunabulum, an open-source implementation of k, and from j.\n\n#### 📜 the incunabulum\n\nconventional formatting of the Incunabulum's `main` function would look something like this:\n\n```c\nint main()\n{\n\tchar s[99];\n\tArray a;\n\twhile (gets(s))\n    {\n\t\ta = execute(parse(s));\n\t\tprint(a);\n    }\n}\n```\n\nfor C programmers, this should look like a familiar structure for the main event loop of an\ninterpreter; we read an input, parse it, and then execute that statement.\n\nin the Incunabulum, Arthur Whitney wrote that loop in this single line:\n\n```c\nmain(){C s[99];while(gets(s))pr(ex(wd(s)));}\n```\n\n#### 🔬 ngn k\n\nngn k is another commonly cited example of this style at its most extreme. excluding header\nimports and type aliases, its test runner fits in these 12 lines:\n\n```c\nS C*mm(C*s,C**e)_(I f=open(s,0);ST stat h;fstat(f,\u0026h);L n=h.st_size;C*r=mmap(0,n,1,2,f,0);cl(f);*e=r+n;r)\nS I nl(C*s,I n)_(C*p=s;I i=0;W(i\u003cn,I(s[i]==10\u0026\u0026i\u003cn-1\u0026\u0026s[i+1]==32,*p++=';';i+=2)E(*p++=s[i++]))p-s)\nS I t(C*s,I n)_(/*wr(1,\".\",1);*/P(*s=='/'||*s==10,0)\n C*u=strstr(s,\" /\");P(!u,wr(1,\"bad test: \",10);wr(1,s,n);-1)\n C*a[]={\"./k\",0};I p[4];pipe(p);pipe(p+2);\n I c=fork();P(!c,dup2(*p,0);dup2(p[3],1);i(4,cl(p[i]))setrlimit(RLIMIT_CPU,(ST rlimit[]){{1,1}});exit(execve(*a,a,0));0)\n cl(*p);cl(p[3]);wr(p[1],s,u-s);wr(p[1],\"\\n\\\\m\\n\",4);cl(p[1]);\n C o[256];L m=0;W(1,L k=read(p[2],o+m,SZ o-1-m);B(k\u003c=0)m+=k;B(m\u003cSZ o-1))\n cl(p[2]);m=nl(o,m);u+=3;kill(c,SIGKILL);P(s+n==u+m\u0026\u0026!strncmp(o,u,m),1)\n wr(1,\"\\nfail: \",6);wr(1,s,n);wr(1,o,m);wr(1,\"\\n\",1);-1)\nI main()_(setbuf(stdout,0);/*pr(\"unit tests\\n\");*/C*e,*s=mm(\"t/t.k\",\u0026e);I n=0,f=0;\n W(s\u003ce,C*u=strchr(s,10)+1;I r=t(s,u-s);n+=!!r;f+=r\u003c0;s=u)P(f,pr(\"\\nfail %d/%d\\n\",f,n);1)pr(\"\\n\");0)\n```\n\n\u003e ❗ **NB:** do not worry about understanding this snippet.\n\ni had always been fascinated by the fact that this is technically the same language as more\northodox dialiects of C, such as K\u0026R, GNU, or BSD.\n\nj follows this spirit and style, in order to investigate whether \"Whitney Rust\" is possible, what\nit might teach us about Rust as a programming language, and to learn what writing software in this\nvoice feels like, and why people do it.\n\n#### 🐣 j\n\n**🔁 main loop**\n\nabove we looked at the Incunabulum's main event loop. here is j's equivalent entrypoint:\n\n```rust\nmod p;use{p::*,j::*,std::io::Write};\nfn main()-\u003eR\u003c()\u003e{let mut st=ST::default();                                 // define symbol table\n  let prompt  =|   |{print!(\"  \");std::io::stdout().flush()?;ok!()};       // (callback) print whitespace\n  let read    =|_  |{let mut l=S::new();stdin().read_line(\u0026mut l)?;Ok(l)}; // (callback) read input\n  let mut eval=|s:S|{eval(\u0026s,\u0026mut st)};                                    // (callback) read and evaluate once\n  let print   =|a:A|{println!(\"{a}\")};                                     // (callback) print array\n  loop{prompt().and_then(read).and_then(\u0026mut eval)?.map(print);};          /* !!! main event loop !!! */ }\n```\n\n**🏃 verbs**\n\nthe core of the four dyadic verbs `[`, `]`, `+`, and `*` is shown below. the definitions of the `A`\narray type and the `D` dyadic verb enum are included for reference.\n\n```rust\npub enum D        {Plus,Mul,Left,Right}\npub struct A\u003cX=MI\u003e{/**columns*/ pub m:U,      /**rows*/  pub n:U,\n                   /**data*/        d:*mut u8,/**layout*/l:L,\n                   /**memory state*/i:PD\u003cX\u003e,                    }\n/**dyadic verbs*/impl D{\n  /*return dyad function**/ pub fn f(\u0026self)-\u003efn(I,I)-\u003eI{use D::*;\n    match(self){Plus=\u003eD::add, Mul=\u003eD::mul, Left=\u003eD::left, Right=\u003eD::right} }\n  /*add two numbers*/fn add (x:I,y:I)-\u003eI{x+y} /*multiply two numbers*/fn mul  (x:I,y:I)-\u003eI{x*y}\n  /*left           */fn left(x:I,y:I)-\u003eI{x  } /*right               */fn right(x:I,y:I)-\u003eI{  y}\n} impl A{\n  pub fn d_left (self,r:A)-\u003eR\u003cA\u003e{Ok(self)                }\n  pub fn d_right(self,r:A)-\u003eR\u003cA\u003e{Ok(r)                   }\n  pub fn d_plus(self,r:A) -\u003eR\u003cA\u003e{A::d_do(self,r,D::add)}\n  pub fn d_mul (self,r:A) -\u003eR\u003cA\u003e{A::d_do(self,r,D::mul)}\n  pub fn d_do(l@A{m:ml,n:nl,..}:A,r@A{m:mr,n:nr,..}:A,f:impl Fn(I,I)-\u003eI)-\u003eR\u003cA\u003cMI\u003e\u003e{\n            let(li,ri)=(l.as_i().ok(),r.as_i().ok());let(ls,rs)=(l.as_slice().ok(),r.as_slice().ok());\n         if let(Some(li),Some(ri))=(li,ri){r!(A::from_i(f(li,ri)))}                                                     // two scalars\n    else if let(_,Some(s),None,a@A{m,n,..})|(a@A{m,n,..},None,Some(s),_)=(\u0026l,li,ri,\u0026r)                                  // scalar and array\n      {let(f)=|i,j|{Ok(f(a.get(i,j)?,s))};r!(A::new(*m,*n)?.init_with(f))}\n    else if let(_,Some(s),None,a@A{m,n,..})|(a@A{m,n,..},None,Some(s),_)=(\u0026l,ls,rs,\u0026r)                                  // slice and array\n      {if(s.len()==*m){let(f)=|i,j|{let(x)=a.get(i,j)?;let(y)=(s[i-1]);Ok(f(x,y))};r!(A::new(*m,*n)?.init_with(f))}}\n    else if (ml==mr)\u0026\u0026(nl==nr){let(m,n)=(ml,nl);r!(A::new(m,n)?.init_with(                                              // matching arrays\n      |i,j|{let(l,r)=(l.get(i,j)?,r.get(i,j)?);Ok(f(l,r))}))}\n    else if (ml==nr)\u0026\u0026(nl==mr) /*NB: inherit the dimensions of the right-hand operand.*/                                // rotation\n      {let(f)=|i,j|{let(x)=l.get(j,i)?;let(y)=r.get(i,j)?;Ok(f(x,y))};r!(A::new(mr,nr)?.init_with(f))}\n    bail!(\"length error\");\n  }\n}\n```\n\n(_NB: we will talk more about the shorthand type aliases and macros in scope later in this essay_)\n\n## 💭 on brevity; readability depends on who is reading\n\nthus, array programming languages are somewhat notorious for their terseness. this terseness often\nextends to the underlying implementation of these languages as well. before we continue any\nfurther, i'd like to cite a snippet from `@xpqz`'s excellent introduction to the K programming\nlanguage:\n\n\u003e K, like its Iversonian siblings APL and J, values conciseness, or perhaps we should say\n\u003e terseness, of representation and speed of execution.\n\u003e\n\u003e [A]ccusations of “unreadable”, “write-only” and “impossible to learn” are leveled at all\n\u003e Iversonian languages, k included. [..] **Readability is a property of the reader, not the\n\u003e language.**\n\n- [_\"Why k?\"_][why-k] _(emphasis added)_\n\nto restate this point bluntly, we will not spend time in this essay humoring questions about the\naesthetic or practical validity of these languages. real people wake up, and solve real problems\nwith array programming languages. whether this is the right paradigm for you is not dependent on\nyour first impression, but upon what problems you are trying to solve, and whom you communicate\nwith when solving them.\n\n## 🌈 how broad is our imagination?\n\nprogramming languages used in industry today are descendents of a few shared ancestors, most\ncommonly C. exceptions may draw from other languages such as ML, Lisp, Smalltalk, or Fortran, but\neven these all share some unspoken consensus regarding whitespace, loop indentation, symbol names,\nand the like.\n\nmany readers approach array languages with some preconceptions of what code should look like.\nwe as software engineers today have conservative ideas of what programming languages\ncan look like, compared to the variety found in written languages around the world.\n\n### ➰ compute a cumulative sum in imperative programming languages\n\nlet's find the sum of integers `1..100` in a few programming languages.\n\n**note** source for these can all be found in the `sums/` directory of this repository. we will exclude\nunrelated syntactic overhead such as defining a `main` function, in order to focus on the core\nlogic of computing this sum.\n\n**C**\n\n```c\nint sum = 0;\nfor (int i = 0; i \u003c= 100; i++)\n{\n    sum += i;\n}\nprintf(\"%d\", sum);\n```\n\n**POSIX Shell**\n\n```sh\nSUM=0\nfor i in $(seq 100);\ndo\n\tSUM=$(expr $SUM + $i)\ndone\necho $SUM\n```\n\n**Julia**\n\n```julia\nsum = 0\nfor i=1:100\n\tglobal sum = sum + i\nend\nprint(sum)\n```\n\n**Rust**\n\n```rust\nlet mut sum = 0;\nfor i in 1..=100 {\n    sum += i;\n}\nprintln!(\"{sum}\");\n```\n\nthese are strikingly similar! consider that Julia appeared roughly 40 years after C, or that the\nBourne shell is a command-line interpreter rather than a programming language. despite that,\nmost of the differences between these examples are syntactic minutia:\n* in C, we must declare the type of the variable, an `int`\n* in a shell script, we must perform our arithmetic inside of a subshell, using the `expr` builtin\n* in Julia, we must specify that our assignment refers to the global `sum` variable\n* in C and Rust, loops are wrapped within `{` and `}` curly braces; in Julia and sh, `end` and\n`done` keywords are used.\n\notherwise, all of these programs share a common backbone, expressed in the following pseudocode:\n\n```\nsum = 0\nfor i in 1..100:\n    sum += i\nprint sum\n```\n\n#### λ find a cumulative sum in a functional language\n\nthis exercise could be repeated with a collection of various functional languages, but a similar\npattern can be found. programs declare a sequence or iterator and use a \"fold\" operation to find\nthe sum.\n\nat its most explicit, this would look something like the following Rust program:\n\n```rust\nfn main() {\n    let sum = (1..=100).fold(0, std::ops::Add::add);\n    println!(\"{sum}\");\n}\n```\n\nor, using the `Iterator::sum` helper:\n\n```rust\nfn main() {\n    let sum: u32 = (1..=100).sum();\n    println!(\"{sum}\");\n}\n```\n\n**⤆ a refresher on function composition: you are used to reading from right to left**\n\nsuppose we have a value `x`, and two functions, `f(x)` and `g(x)`. \"function composition\" refers\nto the act of finding the resulting value when `g` is provided the output of `f(x)`. this may be\nwritten out as `g(f(x))`.\n\nthis notation represents the precedence of functions such that you would read this expression\nfrom right to left. `x` is first provided to `f`, whose output is subsequently passed to `g`.\n\nmathemeticians alternately use the `∘` operator to represent this. this composition of `f` and\n`g` can also be written out as `f ∘ g`. some languages such as F# or Elixir provide a \"pipeline\"\noperator to facilitate this style, and other languages like Haskell may be written in \"point-free\"\nsyntax such as this. Rust's `.` operator functions are similar sugar, passing its left-hand side\nas the first parameter to the associated method named in the right-hand side.\n\nfunctional languages' solutions to this problem will fit into one of two syntactic structures,\nshown in pseudo-code below:\n\n```\nsum(seq(100))\n```\n\n```\n100 |\u003e seq |\u003e sum\n```\n\nneither of these approaches are incorrect, but it is worth pointing out that many programmers are\nalready quite familiar with the experience of reading expressions from right to left! additionally,\nmany are also familiar with the idea of programming _tacitly,_ wherein variable names are not\nassigned to intermediate values (_the `i` in the for-loops above_).\n\n#### 󰘨 find a cumulative sum in an array language\n\nhere are two solutions to the same problem in J, and its related cousin K.\n\n**K**\n\n```\n+/1+!100\n```\n\n**J**\n\n```\n+/1+i.100\n```\n\n_(NB: the \"increment\" operator is implemented later in this essay.)_\n\n**🎳 breaking it down**\n\nthe K solution performs this same computation in 8 characters. let's break down how this works\nbit-by-bit, starting with the lattermost 6 characters `1+!100`.\n\nhere are the definitions for the `!` and `+` verbs, from the `kona` K interpreter's help pages:\n\n```\n! monadic  enumerate. !4 yields 0 1 2 3\n+ dyadic   plus. add numbers together\n```\n\ntaken together, the expression `1+i.4` adds `1` to each element of the array `0 1 2 3`, yielding\n`1 2 3 4`.\n\n`/` is a kind of \"adverb.\" in traditional human languages, an adverb is a part of speech used to\napply an adjective to a verb. or in other words, it describes how a verb is/was performed. this\nsame concept holds roughly true for J and K's adverbs.\n\nadverbs and [gerunds][j-gerunds] are very similar to higher-order functions. a higher-order\nfunction is a function that either accepts as an argument, or returns, another function. these\nconstructs provide a way for programmers to abbreviate or abstract over common control flow\npatterns. J refers to the `/` adverb in this statement as \"insert\".\n\nthe \"insert\" adverb places the provided dyadic (`+`) operator between the elements of its argument.\nthus, `+ / 1 2 3` is equivalent to `1+2+3`. notice that this is, syntax notwithstanding, the same\nidea as saying `fold()`\n\nso, the expression above has the following structure:\n\n```\n+ / 1 + ! 100\n          ┝┳┥\n           ┗━━━━━ the number 100\n        ┝━┳━┥\n          ┗━━━━━━ the sequence of numbers 0 through 99\n    ┝━━━┳━━━┥\n        ┗━━━━━━━━ the sequence of numbers 1 through 100\n┝┳┥\n ┗━━━━━━━━━━━━━━━ find the sum of the given argument\n┝━━━┳━━━━━━━┥\n    ┗━━━━━━━━━━━━ add each of the numbers from 1 through 100\n```\n\nthese programs share the same structure, save that `i.` is the verb for generating sequences,\nrather than K's `!`. importantly, we can see a shared syntactic lineage here.\n\n###   language categories\n\nthe united states government groups languages into categories from 1-5, ranking how difficult they\nare to learn. category 1 languages are considered \"easy\" to learn, while a category 5 language\nwill take many hours before a student is considered fluent.\n\nthere is a catch: this scale measures the perceived difficulty for _native English speakers_.\n\nthe true difficulty of learning a new language is fundamentally entangled with what languages a\nstudent is previously familiar with. A Spanish speaker may be able to quickly learn Portuguese,\nbut a language like Cantonese might include novel concepts such as semantically meaningful tone\nthat would trip up such a student. in much the same way, J would be rather easy for a K programmer\nto learn and vice-versa.\n\nin truth, i think that the difficulty of learning array languages is overestimated. while this may\nseem to stem from incongruities in notation, i think this reputation is ultimately about larger\ndifferences in people's philosophies and preconceptions about human-computer interfaces.\n\nso, why _do_ so many programming languages look so similar?\n\n#### 🤨 \"strangeness budget\"\n\nwe can find an illustrative story in the development of Rust's syntax for asynchrony. to briefly\nsummarize, Rust opted to use \"postfix\" notation when awaiting a `Future`. this results in code\nthat looks like:\n\n```rust\nlet bytes = client\n    .send(request)\n    .await\n    .map(Response::into_body)?;\n```\n\nin other languages that use a traditional `await` keyword such as JavaScript, this might look\nsomething like:\n\n```javascript\nlet bytes = (await client.send(request)).into_body();\n```\n\nmany discussions about the validity of this approach have already been borne out at\n[great][rust-57640] [length][rust-50547]. i'll point to [this][ceej-postfix] excellent write-up\nabout the benefits of postfix `await` if you are interested in reading further. suffice to say,\nthis decision was controversial, because it strayed outside of what some people considered\nreasonable syntax for asynchrony.\n\nSteve Klabnik [wrote][klabnik-budget] about this phenomenon:\n\n\u003e [I]t’s important to be considerate of how many things in your language will be strange for your\n\u003e target audience, because if you put too many strange things in, they won’t give it a try.\n\u003e\n\u003e You can see us avoiding blowing the budget in Rust with many of our syntactic choices. We chose\n\u003e to stick with curly braces, for example, because one of our major target audiences, systems\n\u003e programmers, is currently using a curly brace language. Instead, we spend this strangeness\n\u003e budget on our major, core feature: ownership and borrowing.\n\nin other words, in order to define and manage a strangeness budget, as with the idea of\n\"categories\" for human languages, you must identify who your prospective students are. as Steve\nnoted, it was pragmatic and reasonable for Rust to focus on this demographic because many of its\nearly adopters would be C or C++ programmers.\n\nour summing exercise above illustrated how similar different programming languages' looping\nconstructs are. ultimately, each new programming language is incentivized _not_ to challenge\npeople's notions of loops.\n\nAaron Hsu has remarked in his [talks][hsu-apl] about APL that the people who have the most\ntrouble learning languages like APL are often _computer science students_. we learn more than\nsyntax or grammer when studying computer science: for better and for worse, we also internalize\nconventions of _thought._\n\nreadability is a property of the reader, indeed!\n\n## 🍄 my experience\n\nso, you ask, how was it?\n\nhopefully at this point i've convinced you that this is an internally consistent programming\nparadigm, even if it does not seem like your personal cup of tea. i'll be honest, at the outset\nof this project it did not seem like my cup of tea either.\n\nafter spending time building a piece of software in this style however, i grew to like it\nfar more than i expected i would. in no particular order, let's gloss through some thoughts about\nthis experience.\n\n### 🌐 brevity amplifies local reasoning\n\ncodebases for real-world production software are often quite large. most include tens of thousands\nlines of code, and it is not uncommon for this number to reach the hundreds of thousands, or even\nmillions.\n\n**lexical sprawl introduces a high amount of non-local reasoning into our systems.** with that,\nwe introduce a need for other specialized tooling: text editors with the ability to \"fold\"\nsections of text out of view, terminal multiplexers with tab and window management facilities,\nlanguage servers to help find references to a given variable, formatting tools to maintain a\nconsistent syntactic style, the list goes on.\n\nwhen working on j, i found that i spent about the same amount of time reading\nthrough my existing code to introduce a new feature, but reading became a passive activity. i\nno longer needed to scroll up and down, or jump to function definitions elsewhere. **my cognitive\nfunction was no longer divided between reading and traversal.** i could instead open a\nfile, lean back, and read through the entirety of a subsystem without needing to manually interact\nfurther.\n\nin contrast, concise code has the effect of maximizing the amount of code that may be included in\nlocal reasoning.\n\n### 🐘 sufficient brevity implies a DSL\n\nsoftware written in this style includes a \"_prelude_\" of sorts, defining various shorthand forms.\nthese preludes go beyond just type aliases like `typedef char C` or `typedef long I`, however.\nC's preprocessor is often leveraged to provide abstractions for keywords, function signatures,\nor even **control flow**.\n\nthe Incunabulum's prelude is shown below. it defines an `R` shorthand for `return`ing a value,\n`DO` to perform an operation across the length of an array.\n\n```c\n#define P printf\n#define R return\n#define V1(f) A f(w)A w;\n#define V2(f) A f(a,w)A a,w;\n#define DO(n,x) {I i=0,_n=(n);for(;i\u003c_n;++i){x;}}\n```\n\nKona, an open-source implementation of the k3 programming language, includes an almost verbatim\ncopy of this same macro:\n\n```c\n#define DO(n,x) {I i=0,_i=(n);for(;i\u003c_i;++i){x;}}\n#define DO2(n,x){I j=0,_j=(n);for(;j\u003c_j;++j){x;}}\n#define DO3(n,x){I k=0,_k=(n);for(;k\u003c_k;++k){x;}}\n```\n\nas another example, Kona defines some control-flow macros for early returns, based on some predicate\ncondition.\n\n```c\n#define R return\n#define P(x,y) {if(x)R(y);}\n#define U(x) P(!(x),0)\n```\n\nshorthand for common control-flow like early returns is tremendously useful. Rust has the `?`\noperator for this very reason! the next snippet shows some ngn k's equivalent shorthand notation\nfor loops, conditional statements, and switch statements.\n\n```c\n#define  W(x,a...) while(x){a;}\n#define  B(x,a...) I(x,a;break)\n#define  P(x,a...) I(x,_(a))\n#define  I(x,a...) if(x){a;}\n#define    J(a...) else I(a)\n#define    E(a...) else{a;}\n#define SW(x,a...) switch(x){a}\n```\n\nRust was just as capable of defining such a prelude: `r!()` could be used to perform early\nreturns, `b!()` could perform heap allocations, `R\u003cT\u003e` served as a shorthand for a fallible\noperation resulting in `T`, and similar type aliases `C` or `I` were defined for characters and\nintegers.\n\nj's prelude looks like this:\n\n```rs\n//! prelude; shorthand aliases for common types and traits, macros for common patterns.\npub(crate)use{Box as B,char as C,u32 as I,usize as U,Option as O,String as S,TryFrom as TF,TryInto as TI,Vec as V};\npub(crate)use{std::{alloc::Layout as L,clone::Clone as CL,cmp::{PartialEq as PE,PartialOrd as PO},\n  collections::{BTreeMap as BM,VecDeque as VD},fmt::{Debug as DBG,Display as DS,Formatter as FMT,Result as FR},\n  iter::{FromIterator as FI,IntoIterator as IIT,Iterator as IT},io::stdin,\n  slice::{from_raw_parts,from_raw_parts_mut},str::FromStr as FS}};\npub(crate)use{anyhow::{Context,Error as E,anyhow as err,bail}};\n#[macro_export] /**`return`*/              macro_rules! r   {()=\u003e{return};($e:expr)=\u003e{return $e};}\n#[macro_export] /**`return Ok(Some(..))`*/ macro_rules! rro {($e:expr)=\u003e{r!(Ok(Some($e)))}}\n#[macro_export] /**`Ok(())`*/              macro_rules! ok {()=\u003e{Ok(())}}\n#[macro_export] /**`Box::new(..)`*/        macro_rules! b   {($e:expr)=\u003e{B::new($e)};}\n#[macro_export] /**`unreachable!()`*/      macro_rules! ur  {()=\u003e{unreachable!()}}\n/**`Result\u003cT, anyhow::Error\u003e`*/            pub type R\u003cT\u003e = Result\u003cT,E\u003e;\n#[cfg(test)]/**test prelude*/pub(crate) mod tp{\n  pub(crate) use{assert_eq as eq,assert_ne as neq,assert as is};\n}\n```\n\nin my practical experience, this was also a pleasant toolbox to maintain. \"Don't Repeat Yourself\"\nis an old adage among programmers, and this worked well to that effect. when i began to recognize\na pattern of some sort, i could define a shorthand for it.\n\nLISP programmers have a mantra that code is data, and data is code; indeed, Kona's README notes\nthat LISP was an important influence on the K language. along the same lines, this style puts a\nheavy emphasis on metaprogramming facilities. code is also syntax, and syntax is code.\n\n**brevity mandates the construction of a domain-specific language (DSL) in which a piece of\nsoftware can then be written.** this style of hyper-succint code is ultimately a dialect to be\nembedded _within_ a \"host\" language.\n\n### 🦀 brevity is not a mutually exclusive property\n\nwriting concise Rust code did not detract from the traditional benefits of the language.\n\nrather than writing this as a 1:1 direct translation of the [Incunabulum][incunabulum], i was\nable to follow familiar idioms when implementing j, and found myself enjoying the usual benefits\nof writing software in Rust.\n\n**NB:** this next section assumes some previous familiarity with Rust's type system.\n\n**🌳 abstract syntax tree**\n\nas a straightforward example, this snippet of `src/r.rs` shows the definition of j's abstract\nsyntax tree (AST). an AST is the structured representation of statements in a language, which\nmost compilers and interpreters implement in some form or another.\n\n`SY` is a newtype wrapper around a `String`. they're tremendously useful. see\n[\"New Type Idiom\"][api-guidelines-newtype] in the Rust API guidelines for more information on this\npattern.\nthe `D` and `M` structures represent dyadic and monadic verbs: `+`, `*`, `i.`, and so forth.\nthe `Yd` and `Ym` are structures are monadic and dyadic adverbs, `/` and `\\`.\n\n`N` is a recursive structure that uses these to represent a statement in j.\n\n```rust\n/**symbol            */pub struct SY(S);\n/**dyadic verb       */pub enum D {Plus,Mul,  Left, Right             }\n/**monadic verb      */pub enum M {Idot,Shape,Tally,Transpose,Same,Inc}\n/**dyadic adverb     */pub enum Yd{/**dyadic `/` */      Table ,\n                                   /**dyadic `\\` */      Infix }\n/**monadic adverb    */pub enum Ym{/**monadic `/`*/      Insert,\n                                   /**monadic `\\`*/      Prefix}\n/**ast node          */pub enum N {/**array literal*/    A{a:A},\n                                   /**dyadic verb*/      D{d:D,l:B\u003cN\u003e,r:B\u003cN\u003e},\n                                   /**monadic verb*/     M{m:M,o:B\u003cN\u003e},\n                                   /**dyadic adverb*/    Yd{yd:Yd,d:D,l:B\u003cN\u003e,r:B\u003cN\u003e},\n                                   /**monadic adverb*/   Ym{ym:Ym,d:D,o:B\u003cN\u003e},\n                                   /**symbol*/           S{sy:SY},\n                                   /**symbol assignment*/E{sy:SY,e:B\u003cN\u003e}}\n```\n\nwhile the monadic and dyadic verbs are simple enumerations, notice that the the `N` node contains\nheterogenous payloads for each variant. outlining the benefits of pattern matching and algebraic\ndata types (ADTs) is out-of-scope for this essay, but in short: this approach helps prevent\ninvalid fields from being accessed or written to. interacting with the `l` and `r` fields will\ncause a compilation failure, _unless_ we are within a block of code that has properly matched\nagainst an `A::N` value.\n\n**🔐 interfaces with guardrails**\n\nhere are some abbreviated snippets of `src/a.rs`. first, we define a collection of \"marker\"\ntypes, to indicate whether the memory of an array has been initialized yet. this uses a \"sealed\"\ntrait; see the [API Guidelines][api-guidelines-futureproof] for more information on this pattern.\n\n```rust\n// src/a.rs\n/**sealed trait, memory markers*/pub trait MX{} impl MX for MU {} impl MX for MI {}\n/**marker: memory uninitialized*/#[derive(CL,DBG)]pub struct MU;\n/**marker: memory initialized*/  #[derive(CL,DBG)]pub struct MI;\n```\n\nnext, we use these marker types and `PhantomData` to mark an array as having memory that is either\ninitialized (`MI`), or uninitialized (`MU`). if no generic is given to `A\u003cT\u003e`, i.e. `A`, it will\nuse `MI` by default.\n\n```rust\nuse super::*; use std::marker::PhantomData as PD;\n#[derive(DBG)]pub struct A\u003cX=MI\u003e{/**columns*/ pub m:U,      /**rows*/  pub n:U,\n                                 /**data*/        d:*mut u8,/**layout*/    l:L,\n                                 /**memory state*/i:PD\u003cX\u003e,                    }\n```\n\nnow, what benefits does this provide?\n\nit means that we can use `impl A\u003cMI\u003e{}` blocks to \"gate\" public interfaces, so that array access\ncannot be performed until an array has been initialized. `impl\u003cX:MX\u003e A\u003cX\u003e` may in turn be used to\nprovide interfaces that apply to _both_ uninitialized and initialized arrays.\n\n```rust\nimpl A\u003cMI\u003e{\n pub fn get(\u0026self,i:U,j:U)-\u003eR\u003cI\u003e{Ok(unsafe{self.ptr_at(i,j)?.read()})}\n}\nimpl\u003cX:MX\u003e A\u003cX\u003e{\n  pub fn set(\u0026mut self,i:U,j:U,v:I)-\u003eR\u003c()\u003e{unsafe{self.ptr_at(i,j)?.write(v);}Ok(())}\n  pub(crate)fn ptr_at(\u0026self,i:U,j:U)-\u003eR\u003c*mut I\u003e{self.ptr_at_impl(i,j,Self::index)}\n}\n```\n\n...and finally, it means that we may mark certain interfaces as safe, or unsafe. for example,\n`A::init_with` provides a safe interface to initialize the memory of an array, using a callback\nthat is given the position `(i,j)` of each cell.\n\nconversely, `A::set` may be used to manually initialize each position of the array, but places the\nonus upon the caller to determine whether or not each cell has been initialized. thus, `A::finish`\nis an unsafe interface, and must be called within an `unsafe{}` block.\n\n```rust\nimpl A\u003cMU\u003e{\n  pub fn new(m:U,n:U)-\u003eR\u003cSelf\u003e{Self::alloc(m,n).map(|(l,d)|A{m,n,d,l,i:PD})}\n  pub fn init_with\u003cF:FnMut(U,U)-\u003eR\u003cI\u003e\u003e(mut self,mut f:F)-\u003eR\u003cA\u003cMI\u003e\u003e{let(A{m,n,..})=self;\n    for(i)in(1..=m){for(j)in(1..=n){let(v)=f(i,j)?;self.set(i,j,v)?;}}Ok(unsafe{self.finish()})}\n  pub unsafe fn finish(self)-\u003eA\u003cMI\u003e{std::mem::transmute(self)}\n}\n```\n\nnow, we can compare this to how this code might be formatted in common Rust, without such compact\nformatting and such short symbols:\n\n```rust\nimpl Array\u003cMemoryUninit\u003e{\n    pub fn new(m: usize, n: usize) -\u003e Result\u003cSelf, anyhow::Error\u003e{\n        let (l, d) = Self::alloc(m, n)?;\n        let a = A { m, n, d, l, i:PD };\n        Ok(a)\n    }\n\n    pub fn init_with\u003cF\u003e(mut self, mut f: F) -\u003e Result\u003cArray\u003cMemoryInit\u003e, anyhow::Error\u003e\n    where\n        F: FnMut(usize, usize) -\u003e Result\u003cu32, anyhow::Error\u003e\n    {\n        for i in 1..=self.m {\n            for j in 1..=self.n {\n                let v = f(i, j)?;\n                self.set(i, j, v)?;\n            }\n        }\n        let a = unsafe{ self.finish() };\n        Ok(a)\n    }\n\n    pub unsafe fn finish(self) -\u003e Array\u003cMemoryInit\u003e {\n        std::mem::transmute(self)\n    }\n}\n```\n\n**these two snippets are not any mechanically different!** it bears consideration that this\nterse style did not prevent me from using familiar idioms. \"_Whitney C_\" may be a grand departure\nfrom other variants of C, but it _is_ still ultimately a dialect of C. \"_Whitney Rust_\" is also,\nat the end of the day, a dialect of Rust.\n\n## 🏠 brevity is an architectural principal\n\na core lesson i learned by building j is that this is extensive pursuit of brevity is about much\nmore than syntactic brevity for cosmetic reasons. this is a kind of brevity that is also an\narchitectural principal, and a mode of cognition.\n\nworking like this had a perceptible impact on how i worked. it affected the tools i used, how i\nused them, and how i thought.\n\n**🔨 simple workflows**\n\nworking with succinct code meant that i could rely on succinct workflows. when files are this\nshort, you can print them with `cat`. remarkably simple. let's look at array indexing as another\nexample.\n\nm×n arrays `A` have m rows and n columns, and are indexed with 1-based coordinates `(i,j)`. the\ntop-left corner serves as the origin `(1,1)`. there is a documentation comment in `src/a.rs` on\nline 4 that shows this simple diagram:\n\n```\n[a_11, a_12, ... a_1n]\n[a_21, a_22, ... a_2n]\n[...., ...., ... ....]\n[a_m1, a_m2, ... a_mn]\n```\n\nwhen implementing new features, _especially_ when interacting with raw memory, i found that it was\ntremendously helpful to open this comment in a split window within my text editor, neovim.\n\nneovim have a `-c` option that may be used to run commands after opening a file. `+n` may be used\nto open a file at a particular line number. thus, nvim `+4 src/a.rs -c split -c res 4` would open\nthe core `a.rs` array logic, with a split pane showing me this reference for my memory indexing\nstrategy.\n\nrust's inline unit tests allowed me to often work with an entire subsystem _and_ its\nrespective test suite on my screen at the same time. it is hard to overstate how novel and exciting\nthis felt.\n\n**🌲 seeing the forest**\n\ni found that brevity changed the economy of space in my project away from _lines_ of code, into a\ntwo-dimensional economy of characters. while this use of horizontal alignment echoed what i have\npersonally seen in plenty of C codebases, within succinct code i found that i was able to highlight\ncommonalities and differences in higher-level parts of my software architecture: control flow,\nfunctions, etc.\n\nas an example, the `A` array type has two methods, `index` and `index_uc`, to convert 1-based\n`(i,j)` coordinates to the corresonding index of the raw allocated memory. `index` will check that\nthe coordinates are in bounds. for performance reasons, the \"_unchecked_\" variant will elide this\nbounds check.\n\nlook how clearly a succinct style highlights that difference:\n\n```rust\nimpl A{\n  fn index   (\u0026self,i:U,j:U)-\u003eR\u003cU\u003e{self.oob(i,j)?;let A{m,n,..}=*self;let(i,j)=(i-1,j-1);Ok((i*n)+j)}\n  fn index_uc(\u0026self,i:U,j:U)-\u003eR\u003cU\u003e{               let A{m,n,..}=*self;let(i,j)=(i-1,j-1);Ok((i*n)+j)}\n}\n```\n\n**🔎 brief reviews**\n\ncratelyn/j#10 introduces a new monadic verb, `\u003e:`, to increment its given noun.\n\n`git show` has an option `--word-diff-regex` that can be used to control what a \"word\" is in the\ndiff output. so `git show --word-diff-regex=.` is a way to see a per-character diff. using this,\nand the `-w --ignore-all-space` flag to ignore whitespace changes, this PR fits in one screen:\n\n![`62edb1f8061cdcf859f9335e13fb6c104718a4e5`](./10-diff.png)\n\n\u003e ❗ you can run `git show 62edb1f --ignore-all-space --word-diff-regex=.` if you would like to\n\u003e see this in a local clone of this repository.\n\nyou might naturally as a reviewer take some time to read through this change, and decide whether\nor not it looks good to you. this style does not mean that there is somehow _less code to review._\nit does mean however, that you can see all of these changes at once.\n\n**💔 tooling incongruities**\n\ncratelyn/j#3 is an example of a very simple bugfix, fixing an off-by-one error for the `i.` verb.\nit replaces a `j` with a `(j-1)` in a particular expression. here is the diff, again using\n`--word-diff-regex=.`:\n\n![`; git show --word-diff-regex=. --oneline fb72462\nfb72462 (origin/idot-should-start-from-zero, idot-should-start-from-zero) 🐛 bug: i. sequences should start from zero\ndiff --git a/src/a.rs b/src/a.rs\nindex 38f1d7b..f535c2c 100644\n--- a/src/a.rs\n+++ b/src/a.rs\n@@ -125,7 +125,7 @@ use super::*; use std::marker::PhantomData as PD;\n/**monadic verbs*/impl A{\n  pub fn m_idot(self)-\u003eR\u003cA\u003e{let(a@A{m,n,..})=self;let gi=|i,j|a.get(i,j)?.try_into().map_err(E::from);\n    if let(1,1)=(m,n){let(m,n)=(1,gi(1,1)?);let(mut o)=A::new(1,n)?;\n      for(j)in(1..=n){o.set(1,j,{+(+}j{+-1)+}.try_into()?)?;}Ok(unsafe{o.finish()})}\n    else if let(1,2)=(m,n){let(m,n)=(gi(1,1)?,gi(1,2)?);\n      let(mut v)=0_u32;let(f)=move |_,_|{let(v_o)=v;v+=1;Ok(v_o)};A::new(m,n)?.init_with(f)}\n    else{bail!(\"i. {m}x{n} not supported\")}}\ndiff --git a/tests/t.rs b/tests/t.rs\nindex bb3b691..fa86b41 100644\n--- a/tests/t.rs\n+++ b/tests/t.rs\n@@ -20,7 +20,7 @@\n    #[test]fn $f()-\u003eR\u003c()\u003e{let(a@A{m:1,n:1,..})=eval_s($i)? else{bail!(\"bad dims\")};eq!(a.as_slice()?,\u0026[$o]);ok!()}}}\n  t!(tally_scalar,\"# 1\",1);t!(tally_1x3,\"# 1 2 3\",3);t!(tally_3x3,\"# i. 3 3\",9);\n} #[cfg(test)]mod idot{use super::*;\n  #[test]fn idot_3()-\u003eR\u003c()\u003e{let(a)=eval_s(\"i. 3\")?;eq!(a.m,1);eq!(a.n,3);eq!(a.as_slice()?,\u0026[{+0,+}1,2[-,3-]]);ok!()}\n  #[test]fn idot_2_3()-\u003eR\u003c()\u003e{let(a)=eval_s(\"i. 2 3\")?;eq!(a.m,2);eq!(a.n,3);let o:\u0026[\u0026[I]]=\u0026[\u0026[0,1,2],\u0026[3,4,5]];\n    eq!(a.into_matrix()?,o);eq!(a,o);ok!()}\n  #[test]fn idot_3_2()-\u003eR\u003c()\u003e{let(a)=eval_s(\"i. 3 2\")?;eq!(a.m,3);eq!(a.n,2);let o:\u0026[\u0026[I]]=\u0026[\u0026[0,1],\u0026[2,3],\u0026[4,5]];`](./3-diff.png)\n\n\u003e ❗ you can run `git show fb72462 --oneline --word-diff-regex=.` if you would like to see this in\n\u003e a local clone of this repository.\n\nthis diff, when shown with `--oneline` and `--word-diff-regex=.`, is 1442 characters.\nonly 24 characters are the changes themselves (_including `{+` and `+}` markers_). only 16\ncharacters would be needed to print the names of the files shown.\n\nthis is of course, napkin math, but that means that more than 97% of the diff is _context._ the\nhomogeneity of so many programming languages in industry today doesn't just affect the way we\nwrite code. these preconceptions become assumptions that are baked into the tools we use.\nthus, git is showing me \"context\" that presumes we are measuring of code in terms of \"lines\".\n\n## 🖤 conclusion\n\noverall, i enjoyed working in this style. it provided me with a fresh perspective about how i\nuse the computer, what language can look like, and how notation affects the way we think.\nif you have ever been curious about array programming languages, i recommend you give them a try.\n\n---\n\n### 🔗 works cited\n\n* [\"Future Proofing\"](https://rust-lang.github.io/api-guidelines/future-proofing.html)\n* [\"New Type Idiom\"](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)\n* [\"Incunabulum\"](https://code.jsoftware.com/wiki/Essays/Incunabulum)\n* [\"J: Gerunds And Atomic Representation\"](https://code.jsoftware.com/wiki/Vocabulary/GerundsAndAtomicRepresentation)\n* [\"J: Getting Started\"](https://code.jsoftware.com/wiki/Guides/Getting_Started)\n* [\"J: NuVoc\"](https://code.jsoftware.com/wiki/NuVoc)\n* [\"The language strangeness budget\"](https://steveklabnik.com/writing/the-language-strangeness-budget)\n* [\"Why K\"](https://xpqz.github.io/kbook/Introduction.html#why-k)\n* [\"Why Rust’s postfix await syntax is good\"](https://blog.ceejbot.com/posts/postfix-await/)\n* rust-lang/rust#57640\n* https://github.com/rust-lang/rust/issues/57640\n* https://github.com/rust-lang/rust/issues/50547\n* [\"Does APL Need A Type System?\"](https://www.youtube.com/watch?v=z8MVKianh54)\n\n[api-guidelines-futureproof]: https://rust-lang.github.io/api-guidelines/future-proofing.html\n[api-guidelines-newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html\n[incunabulum]: https://code.jsoftware.com/wiki/Essays/Incunabulum\n[j-gerunds]: https://code.jsoftware.com/wiki/Vocabulary/GerundsAndAtomicRepresentation\n[j-getting-started]: https://code.jsoftware.com/wiki/Guides/Getting_Started\n[j-nuvoc]: https://code.jsoftware.com/wiki/NuVoc\n[klabnik-budget]: https://steveklabnik.com/writing/the-language-strangeness-budget\n[why-k]: https://xpqz.github.io/kbook/Introduction.html#why-k\n[ceej-postfix]: https://blog.ceejbot.com/posts/postfix-await/\n[rust-57640]: https://github.com/rust-lang/rust/issues/57640\n[rust-50547]: https://github.com/rust-lang/rust/issues/50547\n[hsu-apl]: https://www.youtube.com/watch?v=z8MVKianh54\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcratelyn%2Fj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcratelyn%2Fj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcratelyn%2Fj/lists"}