`tac` : Implementation of UNIX's line reverser

I retconned this from the old style UNTIL to INSIST, because I believe that it's truly better to have UNTIL as arity-2.

Writing out a BLOCK! as lines is probably common enough that it should be offered:

write-lines stdout reverse collect [
    insist [not keep opt read-line stdin]
]

[] is an edge case there. Unless someone can put together a convincing argument otherwise, I'd say it shouldn't write anything... and [""] should be required if you want to write a single newline. That would give it parity with the implementation as I had it.

Speaking in the negative with INSIST is kind of weird. I'd rather read:

write-lines stdout reverse collect [
    while [keep opt read-line stdin] [
        noop
    ]
]

I'm of course trying to not use variables here as part of the "puzzle", but using variables makes it more comprehensible:

write-lines stdout reverse collect [
    let line
    while [line: read-line stdin] [
        keep line
    ]
]

WHILE passes its condition to a body that's a function, so you could do:

write-lines stdout reverse collect [
    while [read-line stdin] (line -> [keep line])
]

And of course, that function is just KEEP:

write-lines stdout reverse collect [
    while [read-line stdin] keep/
]

There's also ATTEMPT. ATTEMPT is particularly interesting if you want to throw in error-handling, because you're not so concerned about where your loop conditions wind up:

write-lines stdout reverse collect [
    attempt [
        let line: read-line stdin except e -> [
            ... your error handing here ...
        ]
        if line [
            keep line
            again
        ]
    ]
]

And you could be creative with CYCLE, getting rid of the need for OPT on the KEEP by just putting a BREAK on the NULL-yielding READ-LINE case:

write-lines stdout reverse collect [
    cycle [keep (read-line stdin else [break])]
]

The parentheses actually aren't necessary there, because it runs one complete expression on the left, so it connects that way naturally:

write-lines stdout reverse collect [
    cycle [keep read-line stdin else [break]]
]

Whether that reads intuitively to people or not is subject to your acclimation to the feature. You could also say the more traditional:

write-lines stdout reverse collect [
    cycle [keep any [read-line stdin, break]]
]

Most Readable Is WHILE w/LINE Variable

It would be even better If LET variables could be visible in WHILE bodies, which I think is definitely worth pursuing:

write-lines stdout reverse collect [
    while [let line: read-line stdin] [
        keep line
    ]
]

I kind of feel like there's a comprehensibility threshold with "don't use any intermediate variables", and testing the result of a KEEP is kind of counterintuitive.

Best power-user version is likely:

write-lines stdout reverse collect [
    while [read-line stdin] keep/
]
1 Like