What I'm Watching: The I-Land

10 very pretty people wake up with amnesia on a beach, on a deserted tropical island. As they wander, they find useful items for survival. If they go in the water… da-dum da-dum… They have some stupid interpersonal drama.

Then the very telegraphed "twist" happens; which given the staticky intro effect, and the unreal nature of many events, should be no surprise at all.

Up thru E2 it's a mediocre Lost with really terrible dialogue and deliberately no character development. Then E3 is the exposition ep, but the writing is even worse, with a fat moron not answering questions, then terrible caricatures of academics not answering questions. The only saving grace is one good action scene. But these supposed future police have no idea how to handle prisoners, they're like the idiot cops in Demolition Man trying to handle Wesley Snipes, but this isn't intended as dark comedy.

★★½☆☆ as of E3 out of 7 — If I hadn't seen Lost, The Cell, or Demolition Man, I'd think this was at least sort of creative. Probably some people will call out The Matrix, but that's a happier kind of prison.

The thing of casting only pretty people for the island and often ugly ones for outside is a shallow trick, and I find it kind of insulting.

A Scheme of Gerbils Runnin' in a Wheel

Chicken's mediocre performance is causing me problems, so I'm taking yet another language tour/spike.

Looking at Gerbil Scheme, as a faster Scheme and as a bonus it's closer to R7RS. It has far less library support than Chicken, but does have a FFI, and compiles to a native binary through C, so I really don't need too many libraries.

Instead of the #!key #!optional #!rest keywords used in Chicken and some others, Gerbil uses a literal template style for functions:

;; Chicken: (define (foo a #!key (b 'bar) #!optional (c "see?") #!rest rest)
;; Gerbil:
(def (foo a b: (b 'bar) (c "see?") . rest)
    (print "a=" a " b=" b " c=" c " rest=" rest "\n")
)
(foo "Mark" 1 b: "bees!" 'what) ;; note out-of-order optional & keyword
;; a=Mark b=bees! c=1 rest=what

I like this style better, but I dislike the "def" keyword (which uses the enhanced lambda that does this) instead of "define" which uses "lambda%" (traditional lambda).

Gerbil uses [] for list construction, and {} for method dispatch, so goodbye to the nicely distinguishable braces I was using in Chicken. The spec says implementations can do what they want with those symbols, but I wish they wouldn't. Ah, well. I'll add a shitload more whitespace and some comments and it'll be fine.

The struct/object system in Gerbil is pretty nice, they're slightly upgraded records, but there's an (@ obj field) syntax instead of (MyClass-field obj), and (defmethod {mymethod MyClass} ...) creates a polymorphic {mymethod obj args} which finds the right method for any object, which is especially useful for inheritance.

I tried doing some VT100 graphics just to proof-of-concept, but it's doing something to the terminal, such that the same escape codes which work in Python don't clear the screen fully in Gerbil. After a short losing battle with stty and termcap, I give up on that and I'll jump right to writing a C FFI to SDL, because in 2019 that's easier than writing to a console.

Daily reminder that everything we have made since 1984 is overcomplicated junk. On an Atari 800, this took a few seconds to type, and you could start coding a nice UI instantly:

atari-is-awesome-graphics1

Alas, we live in a fallen world, so this is going to be trouble. Here's my Gerbil FFI template so far:

package: myffi

(import
    :std/foreign
)

(export #t)

(begin-ffi
    ;; names of all Scheme wrappers to expose
    (chello)

(c-declare #<<CDECLEND

#include <stdio.h>

int chello(void) {
    printf("Hello this is C!\n");
    return 1;
}

CDECLEND
)

    (define-c-lambda chello () int "chello")

) ;; begin-ffi


; TODO for constants: (define FOO ((c-lambda () int "___result = FOO;")))

(define (main . args)
    (chello)
)

Gerbil has a Scheme-based build system, but I'm a caveman so I make another build.zsh:

#!/bin/zsh

function usage {
    echo "Usage: build.zsh MAIN.scm [LIBS] || -?"
    exit 1
}

if [[ $# -eq 0 || "$1" == "-?" || "$1" == "--help" ]]; then
    usage
fi

mkdir -p bin

main=`basename $1 .scm`

gxc -exe -static -O -o bin/$main "$@" || exit 1
echo "Built bin/$main"

Now:

% ./build.zsh myffi.scm
Built bin/myffi
% bin/myffi
Hello this is C!

Hooray! Unconditional success! Only took all afternoon and a half-pot of coffee!

Now I "merely" have to wrap all of SDL (well, just the parts I need) and get linking working and oh sweet merciless Cthulhu. And I still won't know how much this'll help my performance until I'm days into it. But, the first step is the hardest.

Not-Atari Still Has No VCS

"Atari SA", formerly Infogrames who bought the name, are still not shipping their fantasy "modern VCS", to everyone's total lack of surprise.

This is just about physically painful as these useless parasites take peoples' money (not mine) for a grift, based on love of what a different company named Atari was like 30-50 years ago.

Now they're so shitty, their con so obvious, they've been removed from moderation of the subreddit about them.

Code Mystics' Atari Greatest Hits collection is unrelated, and a fantastic emulation on iOS and DS.

REXX Primes

Just a quick sanity check on performance.

/** primes.rexx */
/* Created 2019-09-26 */
/* Copyright © 2019 by Mark Damon Hughes. All Rights Reserved. */

PARSE ARG primeCount
IF \ DATATYPE(primeCount, "W") THEN DO
    SAY "Usage: primes.rexx N"
    EXIT 1
END

CALL clearPrimes
CALL sievePrimes
CALL printPrimes
EXIT 0

clearPrimes: PROCEDURE EXPOSE primes. primeCount
    primes. = 1
    primes.0 = primeCount
    primes.1 = 0
RETURN

sievePrimes: PROCEDURE EXPOSE primes. primeCount
    DO i = 2 TO primeCount
        DO j = (i * i) TO primeCount BY i
            primes.j = 0
        END
    END
RETURN

printPrimes: PROCEDURE EXPOSE primes. primeCount
    DO i = 1 TO primeCount
        IF primes.i THEN CALL CHAROUT , i || " "
    END
RETURN
# REXX: 0.8% C
% time rexx primes.rexx 1000000 >~/tmp/primes-rexx.txt
rexx primes.rexx 1000000 > ~/tmp/primes-rexx.txt  6.43s user 0.36s system 99% cpu 6.831 total

# Regina: 1.53% C
% time regina primes.rexx 1000000 >~/tmp/primes-regina.txt
regina primes.rexx 1000000 > ~/tmp/primes-regina.txt  3.25s user 0.26s system 99% cpu 3.521 total

# Python: 4.8% C
% time ./primes.py 1000000 >~/tmp/primes-python.txt
./primes.py 1000000 > ~/tmp/primes-python.txt  0.75s user 0.02s system 68% cpu 1.123 total

# Julia: 1.4% C
% time ./primes.jl 1000000 >~/tmp/primes-julia.txt
./primes.jl 1000000 > ~/tmp/primes-julia.txt  0.45s user 0.35s system 21% cpu 3.797 total

Most of REXX's bad time can be attributed to using stem variables in a tight loop, effectively string-keyed hashtables, so I'm sure an ooRexx Array implementation would be significantly faster. But stems are what you'd use in "real code", so caveat coder. In a long real-world program I don't think it's as big an issue, but it's definitely not received the kind of optimization love that newer languages have. Aesthetically, the REXX source is a little wordier and more explicit about globals access, but not hard to write or read.

I went ahead and grabbed Regina, and it doubles the speed of ooRexx. I'm still wary of it, but that's a big win.

Python's not terrible at anything; it's within a stone's throw of a compiled language at this point. Competent mediocrity has really made Python the new dynamic Java of our time. But you still can't do multithreading in it.

Julia's slow because the startup time is just atrocious; if I timed it internally after startup it'd be as fast as a compiled native program, but as a scripting language Julia's bad news.

(I do have real work to do, but I'll keep playing with REXX more over the next few days)

REXX Monarchial Madness is Hereditary

So, I was trying to figure out how to manage global vs. local state, and made the dumbest program possible:

/** array.rexx */

a. = 0
a.0 = 10
DO i = 1 TO a.0; a.i = i; END

SAY array_string("a.") -- prints 1 to 10
SAY alen -- prints "ALEN" because it doesn't exist in global scope

EXIT 0

/* Returns string representation of an array, stem. passed as first arg. */
array_string:
    PARSE ARG _arrayvar .
RETURN _array_string()

_array_string: PROCEDURE EXPOSE (_arrayvar)
    alen = VALUE(_arrayvar || 0)
    str = ""
    DO i = 1 TO alen
        IF i > 1 THEN str = str || ", "
        str = str || VALUE(_arrayvar || i)
    END
RETURN str

So… I've made a global variable "a." full of numbers. PROCEDURE makes a local variable context until you hit RETURN; you should never work at global scope if you can avoid it. So to pass a. in, I have to bounce through a global function array_string(), set a global var _arrayvar to whatever arg was given, which calls a local function _array_string and the paren in EXPOSE (_arrayvar) expands that into "a.", and then use VALUE() to dynamically read the variable.

Well, it works. I remember now how crazy complex REXX programs got, with giant global databases being exposed in every function. I bet this is the origin of my "World object" pattern in so many later languages.

This and another dumb example or two ate a couple hours of my brain, but it kinda makes sense again, you know? Interesting for a language I haven't written in anger in 20 years. I went fully back to SHOUTING KEYWORDS, it just makes sense.

The Python version of this is:

#!/usr/bin/env python3

def array_string(arrayvar):
    return ", ".join(map(str, arrayvar))

a = []
for i in range(1, 11): a.append(i)
print(array_string(a))

Which I'd argue is just as stupid, because Python thinks ranges end 1 before the last number, doesn't know how to join numbers with strings, on a large array that map will consume all memory, and is a single-pass interpreter so I have to put the function above the script code; but it is much, much shorter.

REXX is Still the King

Random REXX mention on Fediverse reminded me of good times with it. REXX was originally a scripting language for the IBM /360 series, later OS/2 where I encountered it. There's three main branches: REXX (original, simple procedural language), ooRexx (strict superset adding OOP tools to REXX), and NetRexx (almost-REXX implemented in Java, so it can interact with the entire Java API).

The things it does well are: Simple notation, like BASIC, but it's structured and dynamic: Variables have their NAME as default value, and it's trivial to evaluate strings as code. Variables can also be "stems", either indexed or associative arrays depending on key. a.=0; a.0=10 and you have an 10-element array of 0's (using .0 as length is common). Any command it doesn't recognize is passed to the outside environment, which might be a command-line shell, another program you're scripting, whatever, no special syntax needed. REXX is preposterously fast for a scripting language, and has perfect decimal math (Cowlishaw's main academic area of interest), though it's still not compiled language fast.

OS/2 made great use of it. The E and E/PM editors written in REXX are all about scripting; like Emacs but far more so, there's almost no program except the E script.

Back in the '90s, I wrote a ton of programs for clients (all dreadfully dull line-of-business stuff) which were thousands of lines of REXX with VT100 UI or a little teeny GUI shell in C it controlled and got messages from; my turnaround on a business change could be hours instead of days or weeks for a C-only program. There's somewhere on FTP sites or in my old code archive my QUEST game, which was just a random D&D wilderness crawl hack-and-slash, written the same way, and a huge script for writing a nice config file to generate the impossibly fussy bit-twiddling world files for DikuMUD and CircleMUD.

Down side, by the late '90s OS/2 was shut down, Mike Cowlishaw started doing everything in NetRexx, so the core language languished, and there weren't good ports to Linux or Mac (either System 9 or early OS X). The open-source "Regina" interpreter was almost but not quite compatible, and was very frustrating to debug around. I completely gave up on it around 1999, when Python got usable.

Seems in the last decade they've somewhat recovered, REXX Language Association, aka ooRexx.org, and NetRexx all have maintained sites, the old RexxInfo site is as hideous as it was in the '90s, and unmaintained now, but does have a free copy of the REXX Programmer's Reference book which is nicely complete.

The RexxLA symposium is going on as we speak. No livestreams, sadly, but last year's presentation decks are up. You might want to start with Rexx Tutorial for Beginners part 1, part 2, and ooRexx Tutorial.

I'd really like to hear "Rexx from OS/2 to macOS - a travel in time & space"!

% sudo port install oorexx
…
% cat hello.rexx
/** hello.rexx */

name = input("What is your name? ")
say "Hello," reverse(name) || "!"
exit

input: procedure
    parse arg prompt
    call charout ,prompt
    return linein()

% rexx hello.rexx
What is your name? Mark
Hello, kraM!

Works fine. The default I/O commands are a little weird so I rewrote that input function almost from muscle memory. PULL var and PUSH x operate on a stack and get uppercased, PARSE PULL var is very powerful and lets you interpolate strings for parsing input (you can also use it on function args, as seen here), but here all I want is a complete line as a function. SAY is a cutesy form of CALL LINEOUT ,whatever, (the missing first arg to CHAROUT or LINEOUT is the output file name/handle). Note the two ways to call a function: y=f(x) or CALL f x depending on if it has a return value. I write everything lowercase, modern REXX is case-insensitive, but historically it was an uppercase-only language, so I always SHOUT THE KEYWORDS when talking about them.

I didn't get far into ooRexx back in the day; I don't think most of its "enhancements" are needed to a nice simple language, but there's places where stems aren't a sufficient abstract data structure and the like.

This looks like fun to play with again, though I wouldn't expect to ship anything useful in it. The one problem is there's no REPL. You can actually make a simple REPL with just DO FOREVER; PARSE PULL line; INTERPRET line; END but there's a lot of error-handling and structured stuff that won't work in that. I think I have a real REPL in my code? I'll go looking in a bit.

Safari 13

So I hit upgrade, and I regret everything.

Obviously, this is the release where they break Safari Extensions, they now have to be apps. uBlock Origin is dead. I've installed Ghostery Lite for the moment but I have no real solution for the future; "uBlock" is an ancient unrelated fork that allows advertising, fuck those criminal scum.

The other thing is tab management is broken: Cmd-T now makes a tab after the current, instead of at the end of the tab list, so now my tabs are all but unmanageable. I'm going to file a Radar, but throwing bits down the black hole isn't going to fix it I think.

Right now it's churning with CPU time being wasted on background tabs, and I'm hoping that's just because I rebooted and reloaded all my tabs, and not something it'll continue doing.

% cat safariCountTabs.applescript
#!/usr/bin/osascript
set output to ""
tell application "Safari"
    repeat with i from first window's index to last window's index
        set w to window i
        set output to output & "Window #" & w's id & " = " & (count w's tabs) & " tabs - "
    end repeat
end tell
do shell script "echo " & quoted form of output
% safariCountTabs.applescript
Window #178 = 125 tabs - Window #170 = 2 tabs - Window #172 = 6 tabs - Window #173 = 18 tabs - Window #174 = 24 tabs - Window #175 = 16 tabs - Window #176 = 35 tabs -

(AppleScript problem: If I use & return or "\n" instead of " - ", they get converted to ^M on output. Is there a setting to make AppleScript not think it's on a '90s-era Mac OS?)

Yeah, obviously I could change to another browser. But I hate every other browser. Chrome's created and operated by a criminal, advertising-supported organization. Firefox is just awful to use, because everyone at Mozilla uses Linux and hates users. Opera's not terrible, but it's not good, and just a wrapper over Chromium so probably just as corrupted as Chrome itself.

Raspberry Pi 4 Setup

In my long-running cyberdeck-building saga, I've tried a few variations. Now trying the new RasPi4-4GB from CanaKit, Rii K12 keyboard, and a very cheap terrible Elecrow 10.1" monitor. When I first got the shipment, the MicroHDMI-HDMI cable was missing, but CanaKit quickly shipped me two, in case I want to run two monitors on my very tiny computer. And then when I got everything hooked up… nothing happened. Turns out, the recommended format tool formats SD cards in ExFat, and RasPi only boots from FAT32. OS X's "new" Disk Utility is awful but did let me pick the right option, copied NOOBS, and was able to boot Raspbian! Yay!

Protip with the Rii keyboard: Little radio adapter is hidden in one of the feet. Took me a bit to figure out why it wouldn't pair. It's a weird layout, keys are a little mushier than I like (I like either classic buckling spring IBM or Northgate keys, or the thin super-crisp "Apple Magic Keyboard" keys), but it's attractive, great for the size, and the trackpad works better than I expected as a real mouse; I do have to hit the left-click keyboard button sometimes to wake it up. I may or may not stick with this forever.

I won't be staying in Raspbian, I hate Linux more than anything, but I don't want to fight with setting up BSD on a new device just yet. What I'd love is if Haiku worked on it, but BSD is probably a saner choice.

Uninstalled all the stupid stuff that's preloaded. LibreOffice can suck it, 10 different "simple IDEs" can fuck off. Mathematica and Wolfram Alpha can stay. "vi" is actually Vim, but "vim" is not installed; anyway I won't be working full-time in Vim. Did an adduser, installed zsh and chsh to zsh; fucking Lintwats still use bash after all the security holes! After some advice from Mastodon, Geany seems tolerable as a GUI editor, and it's cross-platform, good option.

There's a Chez Scheme package, but it didn't install anything and apparently is long out of date. Chicken only has the compiler, not a REPL. But Gauche installs, the "gosh" REPL runs fine, and seems to have working GL & GTK+ libraries, which may be a reasonable way to make some GUI tools for it.

Chromium is the standard browser, which whatever man, but it keeps bugging me to sign in, and I will never do that. It plays Youtubes OK, which is likely to be 75% of its runtime. I'm kind of impressed the sound works and I won't touch anything about that except volume because Linux + sound = 9th level of Hell.

I loathe the very simplistic Windows-like wm it starts with, haven't seen any "setting" to change that, probably have to go fight config files and Raspbian won't be here that long.

Now I need to finish the keyboard box, so I can make this portable, but as just a little desktop computer it's pretty sweet, and my setup cost me $220 total (plus some earlier experiments).