Thinking in an Array Language: From Imperative Loops to Tacit K
Programming in an array language like K requires more than just learning a new syntax; it requires a fundamental shift in how you conceptualize problems. Most developers are trained in imperative or object-oriented paradigms, where the primary building blocks are loops, state changes, and explicit indexing. In K, these patterns are often anti-patterns.
The goal of "thinking in an array language" is a continuous process of simplification. It is the art of condensing unwieldy, iterative patterns into declarative, concise expressions that leverage the language's inherent ability to operate on entire sets of data at once.
The Trap of Direct Translation
When developers first encounter K, the instinct is often to translate a known algorithm from a source like Wikipedia or GeeksforGeeks directly into the language. Consider the standard iterative algorithm for matrix multiplication:
Input: matrices A and B
Let C be a new matrix of the appropriate size
For i from 1 to n:
For j from 1 to p:
Let sum = 0
For k from 1 to m:
Set sum → sum + Aik × Bkj
Set Cij → sum
Return C
A direct translation into K results in code that is functionally correct but architecturally poor. Such a translation typically involves multiple nested loops, the assignment of numerous global variables, and constant modification of state. In K, this is considered the "worst" way to write code because it fights the language's design rather than embracing it.
The Path to Simplification
Refining K code is an iterative process of removing "middlemen"—specifically globals and explicit loops. By applying array-oriented patterns, we can collapse the imperative structure step-by-step.
Step 1: Replacing Loops with Folds
The innermost loop of a matrix multiplication—calculating a sum—is a prime candidate for a fold (/). Instead of initializing a sum variable to zero and adding to it in a loop, a fold can express the summation of products in a single line.
Step 2: Eliminating State and Globals
In imperative languages, we often create a result matrix C and modify its cells. In K, the ' (each) operator returns an array. This means we can simply return the value of the nested loop directly, eliminating the need to pre-allocate and modify a result matrix.
Step 3: Removing Index Variables
As the code evolves, the focus shifts to the variables i, j, and k. In a traditional loop, these are indices used to fetch data. In an array language, we can match rows and columns directly. By pairing each row of A with each column of B, the index k becomes redundant.
Step 4: Leveraging Higher-Order Functions
To further simplify, we can use eachleft and eachright (/:) to pair rows of A with columns of B. By transposing B and pairing elements, the need for the index j vanishes. Eventually, the function can be reduced to a form where no globals are used at all:
matmul: {x{+/x*y}/:\:+y}
Reaching the Tacit Peak
The final stage of simplification is moving from a functional form to a tacit form. Tacit programming (or point-free style) defines functions without explicitly mentioning the arguments they operate on.
By removing the costly transpose operation and conforming each row of B to the whole of A, the matrix multiplication function can be condensed into its most elegant, tacit form:
matmul: (+/*)\:
This represents the pinnacle of the K philosophy: a complex iterative process reduced to a few powerful primitives.
Perspectives on Array Languages
While the power of K is undeniable, it evokes strong reactions from the broader programming community. Some observers compare array languages to regular expressions (regex): they are incredibly terse and powerful for interactive use, but can become "write-only" code if overused in large-scale software projects.
"I still haven't used K/Q/etc. because they look insane... they are basically the maths equivalent of regexes. Super terse and powerful. Pretty much write-only."
Despite this, for those who master the paradigm, the ability to express complex data transformations in a few characters provides a level of productivity and clarity that imperative languages cannot match. The transition from the verbose, loop-heavy approach to the tacit expression is not just a shortcut—it is a migration toward a more mathematical and declarative way of thinking.