[HN Gopher] Show HN: Cosh - concatenative command-line shell
       ___________________________________________________________________
        
       Show HN: Cosh - concatenative command-line shell
        
       Author : tomhrr
       Score  : 61 points
       Date   : 2023-01-17 11:39 UTC (11 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | waffletower wrote:
       | Closh is an interesting functional shell as well -
       | https://github.com/dundalek/closh While not an interactive shell,
       | I find find babashka - https://babashka.org to be elegant for
       | scripting.
        
       | LorenDB wrote:
       | I find it interesting that, while this is marketed as "simpler"
       | to work with data than bash or sh, over 50% of the examples have
       | the sh/bash command _shorter_ than the equivalent cosh command.
        
         | jodrellblank wrote:
         | The article says:
         | 
         | > A small set of versatile primitives means that less needs to
         | be remembered when compared with typical shells [...] though
         | some commands may be longer as a result
        
       | etatoby wrote:
       | I'm very puzzled by this language. Why put the verbs at the end
       | of the sentence, for instance? Was this shell inspired by
       | Japanese or German?
        
         | layer8 wrote:
         | It makes for strict left-to-right evaluation/execution.
        
         | gpderetta wrote:
         | Because that's the very definition of a concatenative language.
        
           | JadeNB wrote:
           | I don't think that there's anything inherent about
           | concatenative languages that forces them to be postfix. The
           | Wikipedia definition (not authoritative, surely, but one we
           | can all access) is:
           | 
           | > A concatenative programming language is a point-free
           | computer programming language in which all expressions denote
           | functions, and the juxtaposition of expressions denotes
           | function composition.
           | 
           | As long as all functions are fixed arity--admittedly a
           | serious limitation--there doesn't seem obviously to be any
           | reason you couldn't write the function first (though of
           | course it's fair to ask why you would want to).
        
             | cyberbanjo wrote:
             | Om is a prefix concatenative language
             | 
             | https://www.om-language.org/index.html
        
       | pjerem wrote:
       | It's the same idea as Powershell, no ?
        
         | tomhrr wrote:
         | Yep, at least as far as working with structured data rather
         | than streams of text is concerned. Apart from the syntax
         | differences, this is generally much lighter weight, though, in
         | that it doesn't have classes, in-depth system integration, or
         | similar (not that those features are bad in Powershell).
        
       | KMnO4 wrote:
       | Seems interesting, but I'm already lost at the first example:
       | lsr; [test m] grep; [f<; [data m] grep] map
       | 
       | I'm guessing the lsr is a recursive ls, and presumably map is a
       | HOF (functional) map. I know what grep means. So that means
       | "test" and "data" are strings, and I have no idea what "m" is
       | for. And "f<" could be anything.
       | 
       | Perhaps it's more parseable to someone who speaks lisp, but I
       | still think not having clear (simple) examples is a nonstarter
       | for a project like this.
        
         | yesenadam wrote:
         | It appears to be extremely similar to Forth, surprisingly. The
         | docs sections on Variables, Functions, Loops, at least, are all
         | pretty much exactly Forth.
         | 
         | the docs: https://github.com/tomhrr/cosh/blob/main/doc/all.md
        
           | cmrdporcupine wrote:
           | That's specified right in the article title. The
           | "concatenative" language family includes Forth, but also
           | PostScript, Joy, etc. Languages in which expression reduction
           | is expressed concatenatively via e.g. a stack, RPN syntax,
           | etc.
           | 
           | Consistent and powerful and terse, but IMHO hard to read.
           | 
           | I think it's a great idea for a shell/script language, maybe,
           | but not universally easily readable.
        
             | yesenadam wrote:
             | > That's specified right in the article title.
             | 
             | My point was that a lot of the grammar seems exactly the
             | same as Forth's, and different to, say, Postscript's.
        
               | cmrdporcupine wrote:
               | Ah, ok, fair point
        
         | tomhrr wrote:
         | The 'sh' command that it corresponds to is above it:
         | find . -iname '*test*' -print0 | xargs -0 grep data
         | 
         | but possibly a bad assumption on my part that the mapping was
         | clear. In any event, 'm' is for regex string matching, and 'f<'
         | is for reading a file into a generator (basically an iterator
         | over the lines in the file). It's a good point that more,
         | simpler examples would help.
        
           | ktpsns wrote:
           | The examples in the README are really not the best.
           | 
           | Anyway, given from the examples for me this reads as a
           | language to write hacky onliners of code which are probably
           | easy to write once but hard to read anytime after. One
           | moment... we had such a language in the past: Perl! :-D
           | 
           | Perl5 onliners were infamous for their expressive power but
           | sometimes crazy to understand even if you thought you were
           | fluent in perl ;-)
        
             | silon42 wrote:
             | We have 2 now, perl and jq
        
               | etatoby wrote:
               | Even after perusing jq's manual multiple times and having
               | written several complex incantations, I still have no
               | idea how to properly combine `|` and `.[]` except by
               | trial and error, or why `select()` needs to be used
               | inside `map(select(...))`
               | 
               | Recently I needed to extract some data, and after
               | fighting with jq and its manual for half an hour, I
               | solved the problem in 30 seconds with node.js
               | 
               | I appreciate the idea behind jq, but its language is
               | horrible. Even XPath was easier and cleaner.
        
               | mananaysiempre wrote:
               | Hmm. I also think jq is more awkward than it needs to be,
               | but I don't think the points you mention are a problem.
               | Maybe explaining them would help?
               | 
               | (Note: the following explains jq's operation using the
               | smallest possible subset of the language, it doesn't aim
               | to use the most natural programs possible.)
               | 
               | So jq's data model (much like XPath's actually) is that
               | everything is a (possibly empty) stream of (JSON) values.
               | On input (unless you use -s), it accepts any number of
               | concatenated JSON objects (usually separated by newlines
               | or ACSII RS, but as JSON is self-delimiting that isn't
               | strictly required) and makes a stream out of them.
               | 
               | That is then fed into the program, a pipeline of
               | |-separated transforms, each of which can generate zero
               | or more output elements from each input element. For
               | example, .foo is a one-to-one transform that, when it
               | accepts an object, emits the value of its foo property
               | (and fails otherwise):                 $ echo '{"foo":
               | null}{"bar": 1, "foo": 42}' | jq .foo       null       42
               | 
               | And .[] is a one-to-zero-or-more transform that, on
               | accepting an array, emits each array element separately
               | (and fails otherwise):                 $ echo
               | '[false,1][][2]' | jq '.[]'       false       1       2
               | 
               | While select(F) is a one-to-zero-or-one transform that,
               | on accepting an element, feeds it into F and lets its
               | pass through if it got a truthy value or rejects if it
               | got a falsy one:                 $ echo '{"foo":
               | null}{"bar": 1, "foo": 42}' | jq select(.foo)
               | {"bar": 1, "foo": 42}
               | 
               | OK, that last one was a bit of a lie. Because we don't
               | want to introduce functions into the language as a
               | separate kind of thing to transforms, F is also a
               | transform, so it might possibly emit more than one value
               | in response to whatever select fed it. The full truth is
               | that select(F) is a one-to-zero-or-more transform that
               | emits each input value as many times as there are truthy
               | values in F's response to it:                 $ echo
               | 'false 42' | jq 'select([true, "also truthy"] | .[])'
               | false       false       42       42
               | 
               | That might have not been terribly useful, but it
               | illustrates two points. First, a JSON literal is a valid
               | transform: one that emits itself every time it gets
               | something. (That's why you need to write .[] for flatten:
               | plain [] is the empty array literal.) Second, while jq
               | cannot do many-to-one transforms, on pain of losing its
               | streaming nature, it can do something like nested
               | contexts, where it launches a subordinate pipeline and
               | does something with the results.
               | 
               | And it _is_ willing to collect those results instead of
               | streaming them: if you have a pipeline P, [P] is a _one-
               | to-one_ transform that, for each input element, runs P on
               | it, collects all the results from them, puts them into an
               | array and emits that. For example:                 $ echo
               | '[[0,1],[2]] [[]] [] [[3]]' | jq '[.[] | .[]]'
               | [0,1,2]       []       []       [3]
               | 
               | Or:                 $ echo '[false,1][][2]' | jq '[.[] |
               | select(.)]'       [1]       []       [2]
               | 
               | (here . is the one-to-one identity transform). Instead of
               | [.[] | P] you can write map(P).
               | 
               | What this boils down to select(C) will go through the
               | input stream and pare down its elements to those that
               | satisfy C, while map(select(C)) will go through the input
               | stream _of arrays_ and pare down _each array's_ elements
               | to those that satisfy C.
               | 
               | Final point: if you want to give up streaming, the -s /
               | --slurp flag will slurp the input stream into an array,
               | then feed it to your program as a single element. That
               | is, jq -s '.[] | P' is a worse synonym of jq P.
        
               | AndyKluger wrote:
               | Some nice alternatives for querying JSON via CLI include
               | jello, yamlpath, and dasel.
        
           | tsegratis wrote:
           | I love the premise. I've got be your market audience, but the
           | examples are too hard for me: both sh & cosh
           | 
           | I don't use `find` and I would have to look up `-0`
           | 
           | Why does cosh use ; the syntax kinda looks like they're not
           | needed?
        
             | tomhrr wrote:
             | > but the examples are too hard for me: both sh & cosh
             | 
             | Thanks, I'll look at adding some simpler examples.
             | 
             | > I would have to look up `-0`
             | 
             | The thing about '-0' is that it's not required in cosh,
             | because you're dealing with proper values instead of text
             | streams. The problem that '-0' (and '-print0') is
             | addressing doesn't arise.
             | 
             | > Why does cosh use ; the syntax kinda looks like they're
             | not needed?
             | 
             | ';' needs to be used to denote the previous string (token)
             | as a function where that can't be determined from context.
             | For example, if you type '1 2 +' and press enter, the shell
             | will assume that because there's a function called '+', the
             | intention is to run that function, but you could also enter
             | '1 2 +;' (or '1 2 + ;') to get the same result. Whereas '1
             | 2 + 1 2 + +' doesn't work, because the shell doesn't know
             | if the first two '+' symbols are meant to be interpreted as
             | function calls or just plain strings. The other place where
             | it assumes that a function call is intended is at the end
             | of an anonymous function, so `[1 2 +]` and [1 2 +;]` have
             | the same effect.
        
       | recuter wrote:
       | I feel like somebody should mention Nushell.
       | 
       | So, err, hey, did you hear about Nushell? You should check out
       | Nushell: https://www.nushell.sh
        
       | rout39574 wrote:
       | Consider JSON output formats, and jq as your swiss army knife
       | between operations.
       | 
       | The disadvantage of "first class values" is that now you need a
       | type system to help determine what values can be used in what
       | way, and it becomes complicated to use a data stream in a manner
       | which was not anticipated when it was authored.
       | 
       | If you keep the exchange format as close to plain text as
       | possible, it's much more straightforward to do new things with
       | it.
        
       | floatinglotus wrote:
       | It reads like yoda said the command. And I don't mean that in a
       | good way.
        
       | MobiusHorizons wrote:
       | The elephant in the room for me is that
       | 
       | > implemented as functions that return first-class values, as
       | opposed to relying on executables that return text streams. This
       | makes working with the results simpler
       | 
       | Is explicitly disregarding several Unix philosophies, but not
       | really discussing why the author believes those philosophies are
       | wrong. It proceeds to produce code without the (at least in my
       | view) nice properties of Unix shell style pipelines, namely the
       | ability to compose them in novel ways due to a high compatibility
       | between formats.
       | 
       | I would welcome a discussion of how the Unix philosophies break
       | down, or what they prevent. But I didn't find that here.
        
         | tomhrr wrote:
         | > Is explicitly disregarding several Unix philosophies, but not
         | really discussing why the author believes those philosophies
         | are wrong.
         | 
         | I'm not sure I'd characterise these parts of the Unix
         | philosophy as 'wrong'. For example, if you are writing a shell
         | program for use by somebody else, then having that program work
         | with text streams makes sense. The focus here is interactive
         | use and short functions/programs for local use, though, which
         | means that the text stream part of the philosophy becomes a
         | less pressing consideration.
         | 
         | > I would welcome a discussion of how the Unix philosophies
         | break down, or what they prevent. But I didn't find that here.
         | 
         | At least as far as text streams go, the readme talks about
         | awkward considerations like '-0' and '-print0', but more
         | generally, when the command doesn't output a known format like
         | XML or JSON, parsing the output can be fun (e.g. per
         | https://stackoverflow.com/a/15643939). Oftentimes the response
         | is "the command has flags for getting the data that you want",
         | but it's simpler (IMHO) not to build this sort of functionality
         | into every command, and instead just have the shell support it
         | more nicely, whether by providing a function that produces a
         | first-class value (e.g. 'ls', 'ps' here) or by providing more
         | generic parsing functions (e.g. 'split', 'splitr' here).
        
       ___________________________________________________________________
       (page generated 2023-01-17 23:02 UTC)