Old Man in the Woods Way of Argument Parsing

So, my premise is that only developers use command lines anymore. And after years of corporate enslavement, it's nice to run off to the woods, make a log cabin and all your tools yourself.

Therefore the best way to parse arguments in Scheme is:

(define debug #f)
(define outfile #f)
(define infiles '())

(define (main argv)
  (set! debug (if (member "--debug" argv) #t #f))  ;; boolean
  (set! outfile (if (member "--out" argv) (cadr (member "--out" argv)) #f))  ;; key-value
  (set! infiles (if (member "--" argv) (cdr (member "--" argv)) #f))  ;; all after --
  (unless outfile (error 'main "No outfile given")) ;; maybe show a whole usage & exit
)

This has some disadvantages. It's only discoverable by reading docs or even code, and you have to write the docs yourself. If you want short args, you have to duplicate lines and maybe set the arg twice.

But it's trivial to set up, you can't really get it wrong, and the amount of effort is appropriate to a developer interface.

(You might complain I'm using globals, you can just change those to let)

For the young over-engineering crowd, there are a variety of arg parsing libraries. And I'm too lazy to demonstrate each of them. But in the set of generates usage, is easy to use, and you'll be able to remember how it works in a year, they all get maybe 1, and need to be 3.

Formatting Strings in Scheme

Most of the time I use primitive display, or print functions:

;; displays a series of args to stdout
(define (print . args) (for-each display args) (flush-output-port) )

;; displays a series of args to stdout, then newline
(define (println . args) (for-each display args) (newline) (flush-output-port) )

;; displays a series of args to given port
(define (fprint port . args) (for-each (λ (x) (display x port)) args) (flush-output-port port) )

;; displays a series of args to given port, then newline
(define (fprintln port . args) (for-each (λ (x) (display x port)) args) (newline port) (flush-output-port port) )

;; displays a series of args to stderr, then newline
(define (errprintln . args)  (let [ (port (current-error-port)) ]
    (for-each (λ (x) (display x port)) args) (newline port) (flush-output-port port)
))

but sometimes I actually need to format things:

(import (prefix (srfi s19 time) tm: ))

(format #f "~8a ~10:d ~20a" name score
    (tm:date->string (tm:current-date) "~Y-~m-~d ~H:~M:~S") )

Common Lisp format works as described in Chez Scheme, using /#f for destination, and some other Schemes as well; but most Schemes only have the nearly-useless SRFI 28. I'm aware of cat/fox/etc combinatorial formatters, but they're very verbose.

Chez also has date/time functions, but no formatter, so using SRFI 19 - nicely, SRFI 19 mostly does sane things, it's not like C's strftime.

Reinventing the Wheel

Sure, there's existing code. Somebody Else's Code. It works fine, maybe not as fast as you'd like, or the interface isn't quite right. That's how it often is with me and SRFI-13. Olin Shivers is a skilled Schemer, back when that wasn't cool (OK, it's still not cool), but some of his APIs enshrined in early SRFIs drive me a little nuts, and the implementation is slow because it's so generalized.

So after a few false starts and failed tests, I now have these pretty things: (updated 2020-11-10, enforced hstart, hend boundaries)

;; Returns index of `needle` in `haystack`, or #f if not found.
;; `cmp`: Comparator. Default `char=?`, `char-ci=?` is the most useful alternate comparator.
;; `hstart`: Starting index, default 0.
;; `hend`: Ending index, default (- haystack-length needle-length)
(define string-find (case-lambda
    [(haystack needle)  (string-find haystack needle char=? 0 #f)]
    [(haystack needle cmp)  (string-find haystack needle cmp 0 #f)]
    [(haystack needle cmp hstart)  (string-find haystack needle cmp hstart #f) ]
    [(haystack needle cmp hstart hend)
        (let* [ (hlen (string-length haystack))  (nlen (string-length needle)) ]
            (set! hstart (max 0 (min hstart (sub1 hlen))))
            (unless hend (set! hend (fx- hlen nlen)))
            (set! hend (max 0 (min hend hlen)) )
            (if (or (fxzero? hlen) (fxzero? nlen))
                #f
                (let loop [ (hi hstart)  (ni 0) ]
                    ;; assume (< ni nlen)
                    ;(errprintln "hi=" hi ", ni=" ni ", hsub=" (substr haystack hi hlen) ", bsub=" (substr needle ni nlen))
                    (cond
                        [(cmp (string-ref haystack (fx+ hi ni)) (string-ref needle ni))  (set! ni (fx+ ni 1))
                            ;; end of needle?
                            (if (fx>=? ni nlen)  hi  (loop hi ni) )
                        ]
                        [else  (set! hi (fx+ hi 1))
                            ;; end of haystack?
                            (if (fx>? hi hend)  #f  (loop hi 0) )
                        ]
        ))))
    ]
))

;; Test whether 'haystack' starts with 'needle'.
(define (string-has-prefix? haystack needle)
    (let [ (i (string-find haystack needle char=? 0 0)) ]
        (and i (fxzero? i))
))

;; Test whether 'haystack' ends with 'needle'.
(define (string-has-suffix? haystack needle)
    (let* [ (hlen (string-length haystack))  (nlen (string-length needle))
            (i (string-find haystack needle char=? (fx- hlen nlen)))
        ]
        (and i (fx=? i (fx- hlen nlen)))
))

Written for Chez Scheme, caveat implementor. BSD license, do what thou wilt. If you find a bug, send me a failing test case.

I don't normally bother with fx (fixnum) operations, but in tight loops it makes a difference over generic numeric tower +, etc.

Adult Engineer Over-Optimization as the Motie Problem

Looking at my Scheme code and the way I customize it, I'm starting to see the real reason evil megacorps (and wannabe evil startups) won't hire even middle-aged programmers or use your favorite weirdo language, they just want young idiots who code Java or Go.

If you think about a standard software career, there's maybe 10 years of a submissive fool badly coding crap languages ^1 like Java, Go ^3, PHP, JavaScript ^4. They just got out of college or self-trained, and can barely copy existing algorithms, let alone think of one for themselves. This is why FizzBuzzTest ^5 is such a good novice coder test: It requires following directions exactly, and slightly competent logic skills, but not much more.

Then maybe 10 years of them being project managers and "architects", running waterfall and GANTT charts; they'll say they're "agile" but then have a giant JIRA repo of "backlog" features which have to be implemented before shipping, weekly 4-hour planning "backlog grooming" meetings, and unrealistic estimates. This is sufficient to build all kinds of horrible vertical prisons of the mind like Azkaban Facebook.

Then they either retire, or are "downsized", and now what? So they work on their own code, do maintenance on old systems, or leave the industry entirely.

If they work on their own, freed of evil megacorp constraints, they're going to end up in something idiosyncratic and expressive, like Scheme, LISP, Forth, or a custom language. Make their own weirdo environment that's perfectly fit to themself, and unusable/unreadable by anyone else.

Case in point, I needed an object model. There's one I like in Gerbil, and Gerbil's blazing fast, but I can't make a full SDL2 library for it yet (Gambit's FFI is hard, I've hit some bugs, and there's a LOT of library to interface to), and I'm using a bunch of other Chickenisms anyway, so I can't really move to it yet. Instead I just made my own simple object libary, with a couple macros to hide the ugly reality behind it:

(test-group "Object"
    (test "Object" 'Object (class-name Object))
    (let [ (obj (@new Object))  (bug #f)  (cow #f)  (duck #f) ]
        (test "Object-to-string" "[Object]" (@call obj 'to-string))

        (define-class Animal Object)
        (define-field Animal 'legs 0)
        (define-field Animal 'color #f)
        (define-method Animal 'init (self legs color)
            (set! (@field self 'legs) legs)
            (set! (@field self 'color) color) )
        (define-method Animal 'speak (self)
            (sprintf "The ~A ~A with ~A legs says " (@field self 'color) (class-name (@class self)) (@field self 'legs)) )

        (set! bug (@new Animal 6 "green"))
        (test "bug-legs" 6 (@field bug 'legs))
        (test "bug-color" "green" (@field bug 'color))
        (test "Bug speak" "The green Animal with 6 legs says " (@call bug 'speak))

        (define-class Cow Animal)
        (define-method Cow 'init (self color)
            (@super self 'init 4 color) )
        (define-method Cow 'speak (self)
            (string-append (@super self 'speak) "MOO!") )
        (set! cow (@new Cow "brown"))

        ;; second class to make sure classes don't corrupt shared superclass
        (define-class Duck Animal)
        (define-method Duck 'init (self color)
            (@super self 'init 2 color) )
        (define-method Duck 'speak (self)
            (string-append (@super self 'speak) "QUACK!") )
        (set! duck (@new Duck "black"))

        (test "Cow speak" "The brown Cow with 4 legs says MOO!" (@call cow 'speak))
        (test "Cow to string" "[Cow color:brown;legs:4]" (@call cow 'to-string))
        (test "Duck speak" "The black Duck with 2 legs says QUACK!" (@call duck 'speak))
        (test "Duck to string" "[Duck color:black;legs:2]" (@call duck 'to-string))

        (test "instance-of?" #t (instance-of? cow Cow))
        (test "instance-of? parent" #t (instance-of? cow Animal))
        (test "instance-of? grandparent" #t (instance-of? cow Object))
        (test "instance-of? cousin-false" #f (instance-of? cow Duck))
        (test "instance-of? not an obj-false" #f (instance-of? "wtf" Cow))
    )
)

The implementation code's not much longer than the tests, but it's not quite done for me to show off; I need to switch my macros into non-hygeinic forms so I can get rid of the (self) in define-method, and introduce an Objective-C-like _cmd field for self-reflection, and message-not-understood handling. There's always more tinkering to do.

Which is great for me, but makes my code an undocumented (mostly) new language, unusable by anyone normal. A giant pile of crap Java program, no matter how old, can be "worked on" (more crap piled on top) by any teenage Bro Coder.

All of which brought to mind The Mote in God's Eye, where the Motie Engineers over-optimize everything into a tangled mess, and the Watchmaker vermin are even worse, wiring up everything to everything to make new devices. The threat posed by and solution to Scheme programmers, in your usual authoritarian megacorp scenario, is similar to Watchmakers.


^1 Swift is intended to fit this niche much more than weirdo expressive Smalltalk+C Objective-C was, BDSM ^2 to prevent one from writing "bad" code, but it's not there yet; the reality of low-level software dev can't be simplified as much as Apple wants, and their C++ developers weren't up to the task anyway.

^2 Bondage-Domination-Sado-Masochism; aka strict type systems and code flow analysis, that prevent one from writing "bad" code at the cost of annotating everything with types instead of doing useful work. I'm not kink-shaming people who do that for sex, only those who do it to their own software.

^3 Rob Pike has openly said they can't give a powerful language to newbie Googlers, they mostly just know Java, C, C++, which is why Go is so limited and generic.

^4 Oddly, JS is basically a LISP with really shitty syntax. It's easy to make trivial, broken junk in it, but it's also powerful and expressive if you're an old maniac who understands the Self-based object system.

^5 Oh, fine, but only so I can demonstrate something:

(define (fizzbuzz-test i n s)  (if (zero? (modulo i n))  (begin (display s) #t)  #f) )
(define (fizzbuzz i)
    (unless (any identity (list (fizzbuzz-test i 3 'Fizz) (fizzbuzz-test i 5 'Buzz)))  (display i))
    (newline) )
(for (i 1 100) (fizzbuzz i))

Totally different structure from the usual loop-if-else repetition and hardcoding of everything, because Scheme encourages coding in small pieces. Of course I wrote my own for macro which expands to a named let loop; there's many like it but this one is mine. More Motie engineering.