No Stinking Loops
nsl.com
nsl.com
> A Lazy K (No, not that one)
That link is broken since I had to give up my homepage at CWI. It's now located at https://tromp.github.io/cl/lazy-k.html
> A Ray-Tracer in 7 Lines of K
And here's a number greater than Graham's in 7*7 bits of binary lambda calculus [1]:
┬─┬ ┬─┬──────────
└─┤ │ │ ──┬──────
│ │ │ ┬─┼──────
│ │ │ └─┤ ┬─┬──
│ │ │ │ ┼─┼─┬
│ │ │ │ │ ├─┘
│ │ │ │ ├─┘
│ │ │ ├─┘
│ │ ├───┘
│ ├─┘
└─┘
[1] https://codegolf.stackexchange.com/questions/6430/shortest-t...Is this a collection of resources that promote using things other than loops while programming?
Are we advocating for a world without physical loops, eg. Rollercoasters without loops?
Having written Scala myself at one point, the thought of a loop-less language crossed my mind. It's amazing what you can accomplish with foldLeft and recursion schemes.
That's a great point. Often in my programming career, I've used the rule of least power[1]. In the looping/flow control ladder, I'd list descending
* JMP/GOTO
* for/while
* map/filter/reduce
If a problem only needs a map, using a for introduces needless complexity(ie: state). Personally, I think we have a pedagogical problem in that we teach people loops first and people confuse what they've been taught first with what is a reasonable choice[2].
For completeness, recursion has it's own ladder
* PUSH,POP,JMP
* function call
* recursion schemes[3]
Side note: recursion schemes have an enormous hurdle to adoption in that mathematicians got there first and the naming are so off putting that no one will take you serious if you bring up even simple schemes like anamorphism and catamorphism. Folks would rather accept a FactoryFactoryFactory over a futumorphism.
1. https://en.wikipedia.org/wiki/Rule_of_least_power
2. The other side of this coin is, "Why would they teach me a bad way of doing something?"
The claim is that by expressing programs in terms of these array primitives, you (a) can have those primitives be super optimised and (b) better see what the program is actually about without worrying too much about eg what names to give all the loop counters, all the other intermediate state associated with iteration, etc.
(dotimes (i 10)
(print i))
is just a predefined macro that might expand into primitives like (BLOCK NIL
(LET ((I 0))
(TAGBODY
#:LOOP-2867
(IF (>= I 10)
(GO #:END-2868))
(PRINT I)
(PSETQ I (1+ I))
(GO #:LOOP-2867)
#:END-2868
(RETURN-FROM NIL (PROGN NIL)))))But it's a different paradigm than procedural programming and embracing it might lead to a different mind set.
Using higher-order functions instead of loops is similar e.g. loops:
# input_ = "1,2,3"
numbers = (int(d) for d in input_.split(","))
product = 1
for n in numbers:
product *= n
can be replaced: numbers = map(int, input_.split(","))
product = reduce(mul, numbers, 1)Once you get into it, you start realizing what manipulating data is all about. And most of what you need can be expressed without for loops. And then the step from SQL to Scala is not long.
For me imperative languages are like Lego. SQL is like origami. You fold yourself to the solution.
That said, K is obviously one step further in that analogy.
It’s a wonderful language to work with.
As a trivial example, A + B means the element-wise addition two arrays, rather than use looping to compute the terms yourself.
The original array language is APL, which is also known for using its own non-ASCII notation. https://en.wikipedia.org/wiki/APL_(programming_language) . To give you a sense of what I mean, the implementation of the Game of Life is:
life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}
Compare that to how you might implement GoL as a couple of for-loops, with a hard-coded count of the 3x3 neighbor grid. But why use stinking for-loops when you express what the result you want directly, tersely, so you can able see the whole program at once?(OTOH, as I recall, that implementation uses a fixed-width array, which highlights a limitation to using that approch. For GoL you really want to use Hashlife, which isn't so amenable to an array approach.)
Many of the links here reference K, a descendant of APL which uses ASCII.
Must be fun to type that...
"APL's array operations are also ideal for implementation with SIMD, or "single instruction, multiple data", operations, that perform a single action on several different values. In some cases, such as scalar functions, the primitives are SIMD operations; in others such as Reverse, they are easily implemented using SIMD—for Reverse, SIMD selection or "shuffle". While experimental SIMD machines (such as the APL-influenced CDC Star-100) were created as early as the 1960s, SIMD computing first entered the personal computing mainstream in the 1990s and has steadily grown in prominence for high-performance computing since then. In APL, CPU vector instruction sets such as Intel's SSE are the most often way to access SIMD optimization, although Co-dfns instead runs on a GPU to attain much higher throughput at the cost of increased overhead and restriction of available algorithms."
> a new occasional column “No Stinking Loops” by the legendary Stevan Apter