I've been doing a lot more graphical and numerical programming lately, and I've been reminded of some of the unique challenges such programs pose.
It's hard to write automated tests for them.
Also, errors can be subtle. It's very difficult, when I'm tracing through a program, to stare at a line of code and a value of a variable and tell if it's right or wrong.
So I've been thinking about how I can improve the debugging experience for myself, now that I have this live programming environment. I feel like I'm not using it to its full potential.
Here's a very simple testbed program. I press a key, it performs some computation and it prints a value on the on the screen.
Let's look at the code for it.
As I said, it's printing a result to the screen and the result is computed on a keypress. And this is the computation over here.
The result is computed in a single shot. But if I insert a line that looks like this, now I can see the intermediate results render as the computation is performed.
This is kind of nice and of course, we don't have to be limited to textual rendering. It's like debug print but with greater expressivity.
However, there are some drawbacks. For one, I keep finding myself pushed when I program in this style, to not have local variables. Any intermediate result I might want to render wants to be a global variable.
That's why the result of the computation is a global. Instead of saving the result to the global in the caller, which makes the computation purely functional, I need to compute directly into the global.
I'm using coroutines under the hood and by writing directly to the globals, I don't have to think too hard about how to resume the coroutine. I can treat arbitrary computations the same way. Each coroutine is responsible for its side effects.
But again, globals all over the place, which makes me nervous.
On my Android web browser, I can download a .love file and open it.
After downloading the file, I can open it in my file browser.
I can copy the file into Android using the USB port -- but I CANNOT open it there. Identical bits in storage.
And mobile browsers don't understand file:// So I need the internet to transfer data between two devices right next to me -- even WHEN I have the right wire.
Local-first?! I'd settle for local-somehow-anyhow.
Paste in the following 70-line script and hit 'Run':
g = love.graphics
draw, line, circ, pt, color = g.draw, g.line, g.circle, g.points, g.setColor
abs,min,max = math.abs, math.min, math.max
audio = love.audio.newSource
if od == nil then
od,ou,omp,omr,okp,okr = love.draw, love.update, love.mousepressed,
love.mousereleased, love.keypressed, love.keyreleased
end
W,H = g.getDimensions()
-- visuals
cs, N = {}, 100
function sq(x) return x*x end
function dist2(x1,y1, x2,y2)
return sq(x2-x1) + sq(y2-y1)
end
-- audio
ts = {}
function love.draw() -- od()
for _,c in pairs(cs) do
circ(c[1],c[2])
end
end
function circ(cx,cy)
for x=cx-N,cx+N do
for y=cy-N,cy+N do
local dist = dist2(cx,cy, x,y)
if dist < N*N then
color(abs(x-cx)/N, abs(y-cy)/N, 0.5) --, dist/N/N)
pt(x,y) end end end
end
function love.update(dt) ou(dt)
local touches = love.touch.getTouches()
for _,id in ipairs(touches) do
if cs[id] then
cs[id][1], cs[id][2] = love.touch.getPosition(id)
ts[id]:setPitch(pitch(cs[id][2]))
end
end
end
function pitch(y)
if y <= H/2 then
return 2 - y*2/H -- vary pitch from 2 to 1
else
return 1.5 - y/H -- vary pitch from 1 to 0.5
end
end
function love.keypressed(key, ...) okp(key, ...)
escape()
end
function love.touchpressed(id, x,y, ...)
cs[id] = {x,y}
ts[id] = audio('ivanish/MaternalBowedGuitar.mp3', 'static')
ts[id]:setLooping(true)
ts[id]:play()
end
function love.touchreleased(id, ...)
cs[id] = nil
if ts[id] then ts[id]:stop() end
ts[id] = nil
end
function escape()
error('StopScript')
end
One of those 70 lines contains 'ivanish', and I've been playing with that line by varying the sample. (Unfortunately there isn't a way to browse the list, so you have to look inside the love/zip file.)
My versions are to communicate identity to users. That's it. Not value, not recency, not stability, not compatibility, not difference, not support status, just identity. Am I using the same version as you?