Shell and programming paradigms

21 May 2012
— en 
 

The shell is usually the interface through which the user communicates with applications and files (graphically as in Gnome Shell or textually as with bash for example). It is quite important to be able to compose the activities. In bash composing essentially means using pipes to use the output of a program as the input of another one.

Let us illustrate how important this concept is with two citations:

A long time ago, the original neckbeards decided that it was a good idea to chain together small programs that each performed a specific task, and that the universal interface between them should be text.

Ted Dziuba http://teddziuba.com/2011/10/node-js-is-cancer.html

Text streams are a valuable universal format because they’re easy for human beings to read, write, and edit without specialized tools. These formats are (or can be designed to be) transparent.

http://catb.org/~esr/writings/taoup/html/textualitychapter.html Eric S. Raymond in The Art of Unix Programming

However text is quickly problematic when data is structured: the formatting in columns for human reading is not the best when you want to select a given column and the width is not consistent or some cells can be empty). So composing commands often goes through some text plumbing (making commands such as vipe terribly useful. Also, the scripting language associated with shells can be quite painful to use (inconsistency: fi ends if statements but done for while and for).

To account for that latter problem, two approaches have been tried:

  • Creating a shell with a coherent language: fish
  • Using a shell rooted into already put to test languages: python for ipython, perl for zoidberg

For the former problem, having a better programming language mitigates the problem but does not solve it. ipython has an interesting way of putting output data in structured tables for intelligent grepping and cutting. Other projects solve it in their own way and explore what a modern shell can be e.g. termkit, hotwire or powershell.

Object or typed Shell

Powershell is probably the first to have ventured in what an object shell can be. This makes it possible to have pipelines such as:

Get-ChildItem C:\Scripts | Where-Object {$_.Length -gt 200KB} | Sort-Object Length

Get-ChildItem lists the content of a directory (like dir or ls). The result has a length attributes on which it is possible to filter or to sort.

Obviously, commands need to be rewritten, to expose an API, to define the equality of objects, which attributes are comparable and how…

ipython achieves the same effect but may be used with normal commands as it tries to be smart in interpreting what a tabular output means.

I began implementing my own shell programmed in lua (I already did a C one as a student project years ago) with the objective of having typed answers. Some choices:

  • output of commands is a table of hashtables (the only structure in lua)
  • to get stdout, it goes through a generic pretty-printer
  • in a linux system, ps is no more work than ls, as ps is just a specific exploration of /proc.

RDBM Shell

Grepping is a selection. Cutting (and analogous awk commands) is projection. Sorting is sorting. Join, combine are joins.

A shell could very well be controlled as a Relational Database.

Functional Shell

shell is curryfied! Aliases are partial evaluations. Why should shell not be functional?

Type of commands:

  • cd: path -> ()
  • ls: Maybe path -> [[infos]]

Type of command separators:

  • ;: ('a -> 'c) -> (() -> 'c) -> 'a -> 'c

    ; is probably what Haskell’s >> stands for.

  • pipe: ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c

    pipe is composition of commands: in haskell, pipe would be written >>=

This is better detailed in UNIX Pipes as IO Monads.

But shell is essentially variadic, which is not a functional trait. Look at the signature of zipor tar for example:

	zip: path -> path -> path -> path ... -> path -> ()

Or logically zip: path -> [path] -> ().