Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> In my everyday work, I use Unix commands many times daily to perform diverse and very different tasks. I very rarely encounter tasks that cannot be solved by joining together a couple of commands.

Others just use a REPL instead, where tr, sort, uniq, and sed get to be function calls with a threading macro.



Meh, a shell is just a REPL and a pipe is just a threading macro. What's your point?


The UNIX shell is a primitive REPL, without the capabilities of the REPLs developed at Xerox PARC, TI and Genera, regarding structured data, debugging tools, function composition, inline graphics, ability to directly interact with OS APIs.

A chariot in the age of cars.


A car without infrastructure is just a fancy box. A chariot without infrastructure is a rideable horse.

A rideable horse in the age of broken, disparate, infrastructure.

But, at the end of the day, it all depends on what you're trying to accomplish. I use repls, shells, notebooks, etc, on a regular basis. Unix tools solve some problems. Repls solve other problems. Notebooks another. What's important, to me, is to be able to be able to make the most out of them all, despite their flaws, because they're simply the tools that we have in our toolchain. It would be a shame to not learn our own tools, when they can offer us so, so much.


A repl for a sane language...


No need for language snobbery - the sanity of the "language" isn't what's being discussed here. We're talking about the capabilities of of unix tools within domains where they'd be used. If you want to use a repl within your domain, that's your choice, but understand that in doing so, you're working with a relatively limited domain compared those within the reach of unix binaries.


The threading macro just represents function composition, while a Unix pipe represents buffered streaming I/O (or composition of dataflow operators, if you like). Two related but quite different things.


Function composition can make use of any kind of streaming, better yet, with a debugger at disposal.

The UNIX shell is just a primitive REPL.


It can, but only at the cost of complecting source and sink.

By default, function composition is eager: a function is expected to do its work, and hand the whole return value off to the next function.

We can make this lazy, by setting up an iterator and handing this off. At some expense: our next function must expect an iterator, and therefore can't handle a full data structure. At minimum it must coerce those into an iterator when encountered.

Also, it gets awkward to reason about iterators wrapped in iterators wrapped in iterators, even with a debugger, you get action at a distance, where the fourth function in your thread is failing because the first iterator of three has a flaw in it.

Shell pipes handle all of this for the user, with sensible defaults which can be overridden and modified for special cases. It's a powerful abstraction and I wish more languages offered something like it.


Laziness doesn't require iterators; it can be based on lazy data structures such as lazy lists.

  This is the TXR Lisp interactive listener of TXR 232.
  Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
  1> (defvar N (range 1))
  N
  2> (take 5 N)
  (1 2 3 4 5)
  3> (typeof N)
  lcons
  4> (set (car N) 42)
  42
  5> (take 10 N)
  (42 2 3 4 5 6 7 8 9 10)


Would be interesting to see a comparable solution to these tasks in Lisp.


Using TXR Lisp, obtain the list of words in /etc/fstab on a Ubuntu 18 system, sort them to get identical words into groups which are represented as sublists, then sort by descending length of sublist (i.e. frequency), take the top ten, and turn that into word-frequency pairs:

  This is the TXR Lisp interactive listener of TXR 232.
  Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
  1> [(opip (open-file "/etc/fstab")
            (record-adapter #/[^A-Za-z]+/)
            get-lines
            sort-group
            (sort @1 greater len)
            (take 10)
            (mapcar [juxt car len]))]
  (("defaults" 5) ("a" 3) ("dev" 3) ("ext" 3) ("home" 3) ("opt" 3)
   ("UUID" 2) ("c" 2) ("d" 2) ("e" 2))
Pretty print the list obtained from prompt 1:

  2> (mapdo (do put-line `@(car @1) -> @(cadr @1)`) *1)
  defaults -> 5
  a -> 3
  dev -> 3
  ext -> 3
  home -> 3
  opt -> 3
  UUID -> 2
  c -> 2
  d -> 2
  e -> 2
  nil


Can you illustrate this by providing a concrete example for one of those two problems?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: