[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)