1. 19
  1.  

  2. 4

    Back in college (early to mid 90s) I wrote a Unix shell that used a Forth-like syntax as its base. Forth words (functions) are composable because the actual data is implicit in the stack. It works fine for two or fewer parameters; at three the actual word can get quite difficult to follow and with four or more, the majority of code starts to have noise words to swizzle the data stack. This is what gives Forth its “read-only” nature (in my opinion). And spoiler: I never did use the shell I wrote much. Nor the language I wrote. I spent more time swizzling the data stack than doing real work.

    But there was one good idea in my shell—Unix commands were a first-class type! You could (for lack of a better term) build a command line, store it in a variable and later execute the command via dereferencing the variable. As an example:

    variable ll ( create a variable to store a Unix command )
    { '-l' '/bin/ls' } ll ! ( create the command, and store it in ll )
    variable more { '/bin/more' } more ! ( do the same with more )
    ll @ more @ | (do) ( fetch ll and more, pipe them together and run the resulting command )
    

    You could also easily pipe stderr and stdout to separate sequences of commands and while easy enough to do, the resulting code could get quite messy to read (especially since you could pipe stderr and stdout per program). I wouldn’t even know how to syntactically specify something like Bernstein chaining with this. But this one feature I think was the take away from my experiment—making a Unix command an actual “type” that could be passed around (as a function can in most scripting languages these days).

    1. 2

      Hm that is cool. I agree about Forth – I’m enamored with its simplicity in certain situations, but it just doesn’t work in others. The language Factor kinda “took it too far”, adding an object system to Forth and much more!

      I’m going to make command lines a first class type in my shell as well – it’s just an array of args, which is hard in shell right now (blog post).

      I always use the pattern where “$@” is the last line of the script to do dispatch, but I think it would make a lot of shell scripts simpler if you didn’t have just one array with decent syntax.

      I’m not sure if I will have pipelines and && || chains as first class types. That’s more along the lines of metaprogramming which I need to think about.

      1. 1

        Pipes weren’t their own type—they were operators (think concatenation). In my language, you could do:

        variable uppermore
        { '[a-z]' '[A-Z]' '/bin/tr' } { '/bin/more' } | 
        uppermore !
        
        { '-l' 'ls' } uppermore | (do)
        

        The pipe “operator” leaves a Unix command on the stack that is comprised of two Unix commands pipped together. And while I never got around to allowing one to pipe to/from shell functions (written by the user in the language) it wouldn’t have been hard to add.

    2. 4

      I would also point out that stack based languages, if you use locals to make the shuffling mostly optional, start to develop a lisp-like quality. At least that has been my experience with PISC.

      1. 3

        Yes I mentioned that in my HN comment but not in the blog post… It’s been observed that Forth is roughly the complement of Lisp – everything is in postfix syntax rather than prefix.

        And a few people have noticed that shell has a prefix syntax like Lisp, and are trying to bring more Lisp into shell. Actually Tcl is probably the best example, but also the mostly defunct “es shell” and the new oh shell, which is inspired by it:

        https://github.com/michaelmacinnis/oh