3 ms·
Sure, you can write bash like that. Actually, if you shift the parenthesis a bit, you can write almost any language like that: (add 4 (mul 2 5)) vs. a
by captainmuon 12y ago
Sure, you can write bash like that. Actually, if you shift the parenthesis a bit, you can write almost any language like that:
(add 4 (mul 2 5)) vs.
add(4, mul(2, 5))
But that doesn't mean you should write bash like that. In fact, it is (IMO) very bad style to nest $(). When people complain about Lisp syntax, it's only partially about the RPN or the parens, its about the incredible amount of nesting.
Funnily enough, of all things bash offers an alternative paradigm. Instead of nesting, you use filters:
# pseudocode
echo 2 | mul 5 | add 4
For example, you can write:
nerrors=$(mycommand | grep error | sort | uniq | wc -l)
instead of:
# pseudocode
nerrors=wc("-l", uniq(sort(grep("error", mycommand))))
I find this paradigm of "do A, then do B, then do C" much easier to think about than the nested "do C on the result of doing B on A". I wish more programming languages would embrace that kind of coding, which is basically functional, but in imperative or data-flow order. You have aspects of that in C# (LINQ), SQL, and some others. I know you can achive something like that in Haskell though Monads and `do`, though it feels a bit bolted on as a concession to having to do imperative stuff in a purely functional language. (And as nice Haskell is, it will probably never have the use base that bash has...)
- Jach 12y agoIndeed having a chain is often nicer than a lot of nested stuff. And when your base language is an abstract syntax tree, it becomes pretty easy to write a macro to let you flatten a lot of things out (among a bunch of other neat things). If your complaint with a lisp is that there's too much nesting, you haven't seen enough lisp. Pretty much every lisp has an infix-math library somewhere, for the popular lisps there's probably at least a dozen, and they usually allow mixing in prefix-math as well because nuts to typing "a + b + c + d + e" when I can just type "(+ a b c d e)" or if I want to call functions anywhere in the middle. There's no reason you couldn't write your example using a threading macro in Clojure, assuming your functions are defined such that they take input explicitly as the last argument instead of reading a common stdin. (Or if they take it as the first argument, you use a different macro but the same syntax.) Your code just becomes (->> (command) (grep "error") sort uniq (wc :-l) Does anyone think (wc :-l (uniq (sort (grep "error" (command))))) is a better option? And instead of typing that out manually, I just used the REPL. user=> (use 'clojure.walk) nil user=> (macroexpand-all '(->> (command) (grep "error") sort uniq (wc :-l))) (wc :-l (uniq (sort (grep "error" (command))))) Even without macros you can still setup nice-looking chaining with generators and coroutines. "Too much nesting" seems like the wrong complaint.
- bjourne 12y agoTake a look at factor: http://factorcode.org/ http://factorcode.org/ Your example would be written as: "C:/tmp/adinst.log" utf16 file-lines [ "SEARCH" swap start ] filter members length File is opened into a sequence of lines, then the sequence is filtered so that only lines containing "SEARCH" remain, then members removes duplicates, then length gets the number of elements left in the sequence. In the shell, "|" is the function composition operator, in haskell it's "." In Factor, any whitespace is function composition!
- zokier 12y agoYou mentioned C#/LINQ, but F# has very nice looking pipeline operator, which does exactly that. Here is a blog post explaining it: http://lorgonblog.wordpress.com/2008/03/30/pipelining-in-f/ http://lorgonblog.wordpress.com/2008/03/30/pipelining-in-f/
- notduncansmith 12y agoYou might really enjoy Elixir[0], a language that runs on the Erlang VM. They provide (what I find to be) a nice feature called the pipe, which looks like this: |> and works basically like a bash pipe (use the output of A as the input of B). I've played around with Elixir a bit and while I don't quite have a nail to hit with that particular hammer, it has been really fun and mind-expanding (my first functional language!). [0] http://elixir-lang.org/ http://elixir-lang.org/
- jerf 12y ago"I know you can achive something like that in Haskell though Monads and `do`" The closer equivalent to UNIX pipelining in Haskell is actually the simple "." operator, function composition. That pipeline would generally look more like countLines = wc LineCount countLines . uniq . sort . grep "error" $ mycommand It's not a direct translation, because that is exactly neither legal Haskell (I'm munging program vs. some sort of shell here), nor exactly how you'd really do that in Haskell, however, pipelines of several functions composed together is idiomatic and frequently-used. "though it feels a bit bolted on as a concession to having to do imperative stuff in a purely functional language" It really isn't. IO is only one particular monadic thing; check out Parsec for some more fun. It only looks bolted on if you look very superficially, and you darned near have to bring the preconception in the first place to even briefly come to that conclusion. That idea basically comes from the misconception that "Haskell can't do side effects", then "Oh, well, this way of doing side effects must be a bad idea or something"... the first statement is false. Honestly, I miss the way Haskell does IO in other languages far more than I miss the way other languages do IO in Haskell. (To be clear, I do have both feelings, but the former is way more common.)
- thinkpad20 12y agoFor what it's worth, I often do something like this: (!) = flip ($) Which then allows me to do things like mycommand ! grep "error" ! sort ! uniq ! wc lineCount Which is nice because it visually resembles `|` (which is a reserved symbol in Haskell). Similarly for fmap: (<!>) = flip (<$>) result <- get <!> table <!> lookup "foo" print result ! liftIO Or for function composition: (~>) = flip (.) doStuff = reverse ~> head ~> negate ~> print doStuff [1,2,3] That being said, lisp does seem far more suited to shell programming than Haskell, because every function is variadic. What would probably be nice (and probably has been done) is a Lisp with syntax extended to include a few symbols to allow bash commands like |, >, etc: $ def nth-line (n) (read a | head -(n) | tail -n 1) $ ssh -i .ssh/some.pem (python show_servers.py | grep 'cool server' | cut -d ' ' -f 3 | nth-line 1)
- mcguire 12y ago"It only looks bolted on if you look very superficially, and you darned near have to bring the preconception in the first place to even briefly come to that conclusion." ...or read any of the older Haskell books and tutorials, which all seemed to start in pure expression-land and attempt to stay there as long as possible (which is good programming style, don't get me wrong; but learning is different). IO and other monadic things are treated like huge and scary monsters that must be left until the very last possible moment, if treated at all, because they're HUGE AND SCARY!!!!1! Aaaaahhhhhh!
- Symmetry 12y agoIt's way easier than that in Haskell, you just need to do: x -: f = f x and boom, a Unix style pipe. (Courtesy of Learn You a Haskell, http://learnyouahaskell.com/a-fistful-of-monads http://learnyouahaskell.com/a-fistful-of-monads, but using no actual monads).