Kartik Agaram
18 * 9/5 + 32
, and incrementally end up at a function like celsius-to-fahrenheit
. The backward path fits with various metaphors for debugging programs. Debug by print, debug by step, time-travel debugging. A key concern is how to uncoil static computations (loops, recursion) into dynamic metaphors (traces, stack frames, interactive movements).
Postfix expressions fit beautifully with the backward path. As the demo of Brief () showed, execution is already quite uncoiled, with no backward jumps. While the Brief demo didn't show it (it's easy to spot where the presenter plays computer in their heads), it's reasonable to imagine a way to drill down into function calls, replacing words with their definitions. By contrast, conventional expressions -- tree-shaped and using names -- immediately throw up impediments in understanding what happens first.
However, the forward path is thornier:
1. It's common to claim that point-free programs make it easy to factor out new definitions, but that's only true when the definition consists of consecutive words. Consider how you would go from * 3 3
to a definition of square
, or from 3 4 + 5 *
to a definition of (a+b)*c
.
2. After they're extracted, point-free functions are harder to understand. What does the stack need to look like at the start? How many words, what types, how many words consumed, all these questions require simulating programs in your head. Or a manual comment.
This was the idea maze in my head until I saw LoGlo (https://loglo.app/2020-06-16). The cool idea here has taken me weeks to articulate: lines have names and get separate stacks. Forth typically writes to names within lines with words like !
. Limiting definitions to one per line strikes me as an advance. And having names gives us a way to make Forth words less point-free. I start imagining flows like turning * 3 3
into * x x
using two 'rename' operations, and then turning the entire line into a new function. Like, imagine a UI with a code side on the left, and a scratch computation on the right:
│
│ x: 3
│ * x x
│
After defining a function it might look like this:
│
: sq x │ sq 3
* x x │
│
Notice how the definition of x:
above gets replaced by the call to sq
below. That's kinda pleasing.
But there's issues. This manipulation requires modifying definitions of free variables. Worse, I ended up with the function call in prefix order. Trying to make things consistent got me stuck up on a tree with a 2D layout until I noticed I'd lost the benefits of postfix that got me on this road in the first place. I'll include it here just in case it sparks ideas for others, but I'm starting to think it's a dead end.
Anyways, that's where I am, still looking for a representation that's easy to uncoil and where inlining function calls is a 'smooth' visualization.Kartik Agaram
│ 3 :x
│ * x x
│
=>
: sq x │ 3 sq
* x x │
│
Garth Goldwater
09/21/2020, 1:24 AMGarth Goldwater
09/21/2020, 1:25 AMGarth Goldwater
09/21/2020, 1:30 AMKartik Agaram
Garth Goldwater
09/21/2020, 1:33 AMKartik Agaram
x: 3
is intended to replace 3 x !
not : define x 3 ;
.Garth Goldwater
09/21/2020, 1:45 AMGarth Goldwater
09/21/2020, 1:46 AMGarth Goldwater
09/21/2020, 2:13 AMKartik Agaram
:x
to a line saves the top of the stack as the value of x
. Every line starts with an empty stack, but can share data with previous lines via variables.
Now inlining shows a second stack in its own row. We might even want to expand the stack of the caller to fit the callee in, just one 'row' down to show that it's an independent, isolated stack. (The x
in sq
is unrelated to the x
in the caller since each function has its own namespace.)
I think both are equally uncoiled. The big benefit of option B to me is that the accidental complexity of stack manipulation (swap
and dup
) has been eliminated.Garth Goldwater
09/21/2020, 4:16 AMrm *.txt
<tab>
can expand the text at your prompt rm hello.txt readme.txt getting_started.txt
in a directory containing all those files) i think i have about 8 total options for this but my notecards aren’t with me and those are the ones i both know of and can remember off the top of my headGarth Goldwater
09/21/2020, 4:18 AMKartik Agaram
Jack Rusher
09/21/2020, 8:30 AMsquare
as dup *
? I always liked that sort of thing in FORTH, which is clouding my understanding.Kartik Agaram
(* 13 13)
(or its Forth equivalent) at your favorite REPL. How do you get from that expression to a definition of the function square
in persistent form?
The assumption I'm making is that nobody calculates the square of 13 at the REPL by typing 13 dup *
. So it seems to me that we need some way to nudge people to massage 13 13 *
into a form that needs a single copy of the input(s).
It's just a thought experiment in the end. It's a frame of reference I'm taking on for the duration, and in the process I'm working against some of my own discomfort with spreadsheets and other 2D representations.Jack Rusher
09/21/2020, 4:31 PM(* 13 13)
I see 169
. Or, to be more clear, when I see a form that operates only on constants, I think of it as a constant itself. So I would only execute a form like that one in a Lisp REPL using eval-and-replace
semantics to calculate some constant I want to embed in the code. If, on the other had, I were planning to parameterize such a calculation I'd use a lambda (say, #(* % %)
in Clojure notation), which lifts quite naturally to a def
or defn
if needed.Jack Rusher
09/21/2020, 5:41 PMGarth Goldwater
09/21/2020, 9:53 PMGarth Goldwater
09/21/2020, 9:53 PMGarth Goldwater
09/21/2020, 9:54 PMGarth Goldwater
09/21/2020, 9:54 PMAlexey Shmalko
09/22/2020, 6:30 AM{ n1 n2 } n1 n1 n2
syntax. ({ ... }
introduces local bindings which you can reference at any point later.) I find it quite a useful feature when the word definition grows and juggles 3+ pieces of datawtaysom
09/23/2020, 5:11 AMdup
and shuffling is that they force you to pay the cost of non-linearity. Since named variables give you non-linearity for free, reference hydras sprout up easily.
I guess my bigger qualm is that catenative programs tend to feel less catenative and more conventually referential as they get larger. How can one keep the catenative spirit in bigger programs?
Maybe some kind of dynamic scope? Instead of one stack, each dynamic variable names the top of a stack.
With queues instead of stacks and you get to Lucid. Concatenative reactive programming here we come!Garth Goldwater
09/23/2020, 11:05 AMGarth Goldwater
09/23/2020, 11:06 AMpublic
or export
wtaysom
09/23/2020, 11:40 AMGarth Goldwater
09/23/2020, 2:55 PM