Programing Language: A Ruby Illustration of Lisp Problems

By Xah Lee. Date: . Last updated: .

Here's a interesting toy problem posted by Drew Krause to comp.lang.lisp:

OK, I want to create a nested list in Lisp (always of only integers) from a text file, such that each line in the text file would be represented as a sublist in the 'imported' list.

Example of input

3 10 2
4 1
11 18

example of output:

((3 10 2) (4 1) (11 18))

Here's a emacs lisp version:

(defun read-lines (file)
  "Return a list of lines in FILE."
    (insert-file-contents file)
     (buffer-string) "\n" t) ) )

 (lambda (x)
    (lambda (y) (string-to-number y) )
    (split-string x " ") ) )
 (read-lines "blob.txt") )

The above coding style is a typical maintainable elisp.

In a show-off context, it can be reduced to by about 50%, but still far verbose than ruby or say perl (which is 1 or 2 lines. (python would be 2 or 3)).

Ruby, Python

# -*- coding: utf-8 -*-
# ruby
# by w_a_x_…, William James
IO.readlines("blob.txt").map{|line|{|s| s.to_i }}
# -*- coding: utf-8 -*-
# python
# by Михаил Осипов and other
[map(int, line.split()) for line in open("blob.txt").readlines()]

That's really the beauty of Ruby.

This Ruby code illustrates 2 fundamental problems of lisp, namely, the cons problem, and the nested syntax pain. Both of which are practically unfixable. [see Fundamental Problems of Lisp]

The lisp's cons fundamentally makes nested list a pain to work with. Lisp's nested syntax makes functional sequencing cumbersome.

In the Ruby code, its post-fix sequential notation (as a side effect of its OOP notation) brings out the beauty of functional sequencing paradigm (sometimes known as functional chain, sequencing, filtering, unix piping). [see OOP Dot Notation, Dot Before Data or After?]

more example: LISP Syntax Problem of Piping Functions

Daniel Weinreb wrote:

Your claim that this shows something wrong with lists is completely unclear, although if I read your paper (I'll try) I might have some idea what you're getting at.

More specifically, 2 fundamental problems of lisp i feel this ruby example illustrates well:

Here's a short summary of the nesting problem:

(map f x)                 ; 1 function
(map g (map f x))         ; a sequence of 2 functions
(map h (map g (map f x))) ; a sequence of 3 functions


x | f | g | h      unix pipe

x // f // g // h   Mathematica

h @ g @ f @ x      Mathematica

x.f.g.h            various OOP langs, especially Ruby, JavaScript

h g f x            some functional langs, Haskell, Ocaml

The way the above works is that each of f, g, h is a lambda themselves that maps. (that is, something like “(lambda (y) (map f y))”)

Note, that any of the f, g, h may be complex pure functions (aka lambda). Because in lisp, each lambda itself will in general have quite a lot nested parens (which cannot be avoided), so this makes any chaining of functions of 2 args, for more than 2 or 3 levels of nesting, unusable for practical coding. One must define the functions separately and just call their names, or use function composition with lambda (which gets complex quickly). One major aspect of this problem is that the scope of vars becomes hard to understand in the deep nested source code. This is worse in elisp, because emacs is dynamically scoped, so you have to avoid using var of same name.

For more detail on the cons problem, see the section “The Cons Business” at Fundamental Problems of Lisp .

For more detail on the nested syntax problem for function chaining, see the section “How Purely Nested Notation Limits The Language's Utility” at Concepts and Confusions of Prefix, Infix, Postfix and Lisp Notations .

For a pespective on the fact that cons prevents lisp from developing a coherent list/tree processing library, see: Why Lisp Do Not Have A Generic Copy-List Function .

Lisp Cons Problem

Its list, like all modern high level langs such as Perl, PHP, JavaScript, Python, don't have the lisp's cons problem. The cons destroys the usability of lists up-front, untill you have some at least 2 full-time years of coding lisp to utilize cons properly. (and even after that, it is still a pain to work with, and all you gain is a bit of speed optimization in rare cases that requires largish data, most of which has better solutions such as a database.)