Hacker News new | ask | show | jobs
by gordianknot 6657 days ago
The power of a programming language is proportional to its capability for innate abstraction. If that's true, it follows list-oriented languages are inherently inferior to their hashtable-oriented brethren. (These orientations are often misguidedly referred to as "functional" and "object-oriented" paradigms, which I find to be useless, over-overloaded terms.) Basically, with list-oriented langs the primary abstraction is a tree, whereas with hashtable-orientation, it's a graph. I'm talking about the -primary abstraction- (i.e. what you "think in" when hacking); obviously you can implement any structure in any powerful enough language. If others don't see it this way please, illuminate me.

Lists confine one to rigid hierarchies, which have to be compensated for with dirty (but sexy) hacks like in-language macros. Meanwhile the index of hashtables is arbitrary, which allows you to do naturally the things you have to patch in Lisp.

Though hashtables are more powerful and easier to grok, for one reason or another nearly all (popular) hashtable-oriented languages are total crap (C++, Java, C#), but in some regard a step up from deformed languages like C (in all seriousness, how does anybody get by without first-class functions and hashtables?). My tentacles have only found two decent hashtable-oriented languages: JavaScript >~1.5 and Io.

Anyway, why is Lisp unpopular? Because it's harder for most programmers to think in lists than hashtables. Then, why does the lang have a cult following? Because it's well crafted and consistent, which seems to cause some people to overlook its shortcomings, even to the extent of seeing design flaws as features.

But as far as I'm concerned the Language War is over anyway. JavaScript won.

5 comments

I think that your comparison of the relationship of lists to lisp and hashtables to OOP languages is rather superficial. Lists relate to lisp differently than hashtables (or full objects actually,) relate to languages such a Java or Ruby.

In those languages, everything is an object, that is everything construct you define, whether data or code stared as an object, a set a behaviors and properties. (Assuming it is fully OOP, unlike Java.)

In common lisp, everything is an object as well, with it's own set of properties and behaviors. Cons cells are basic, for example, but you can still add properties to them and define methods for them. Encapsulation is not enforced, but that's for what closures and packages are.

Lists are dominant in lisp for another reason: the language itself is represented in them, not just the data and the methods, but the pre-compiled language, so that you can use lisps meta-programming facilities to generate lisp code itself. In most other programming languages, the code is represented to the compiler as text and to use such meta-programming facilities would require string parsing. Macros aren't a 'hack' but an actual paradigm shift. Attempts to do the same thing in other languages have largely been very clumsy, witness C++ macros. (Template Haskell apparently has managed to do it properly though.) Nearly Lisp's entire syntax is for defining the structure of the code; everything else is done with operators, functions, and macros.

If languages like Java or Ruby were represented in hashmaps the same way that Lisp is represented by lists, they would probably be incomprehensible. if you were to attempt to use a structure to represent the language, it would probably end up being something of a tree format. Code, is naturally hierarchical, even class definitions, and if I were to do the same kind of thing that one does with Lisp in C++ or Java, I would end up using lists. So I don't think that this comparison is really correct.

(BTW, I love C. It's not deformed, it was designed like that to A- make it easy to implement and B- give the programmer as low a level access as he needed. You are meant to define your own data structures and implement them in an efficient way using algorithms that make sense for the usage. You are not confined to a preset, possibly inefficient implementation. This is a level of control not available in a lot of other languages which is why C is so commonly used to write interpreters and compilers for other programming languages. Those highly efficient Python hashtables are implemented in C (and maybe a bit of assembler))

My tentacles have only found two decent hashtable-oriented languages: JavaScript >~1.5 and Io.

I'm not sure whether or not you'd find it "decent", but you might consider adding Lua to your list. It's small, relatively fast, and uses hashtables as its composite data structure. It also has some other neat stuff, like tail-calls and coroutines.

Thanks for the tip. I've heard some good things about Lua, but never gone further than Wikipedia.
I will second the suggestion of Lua. Here's my raw beginner's introduction to Lua tables:

The basic structure is the hashtable. You can use any first-class value as a key (number, string, function, another table) and similarly any first-class value as a value. Creating a table is done with the table constructor "{ }" ('> ' is the repl prompt):

  > a = { }
So now we have an empty table named 'a'. Let's say we wanted to have a table containing a list of colors - this is represented in Lua as a table with ascending integers as the keys. (Starting at 1, rather than 0, which is a bit unconventional)

  > a = { 'red', 'green', 'blue' }
This is equivalent to saying

  > a = { [1] = 'red', [2] = 'green', [3] = 'blue' }
If we use strings as keys, we start seeing some of the syntactical sugar Lua offers. Let's look at favorite foods:

  faves = { bob = 'pizza', george = 'cake', mary = 'pie' }
Note that no quotation marks are needed around string keys. (Well, unless the key is a language keyword. That's a bit annoying, but is related to the single-pass compilation, which is valuable. { if = 'can't do it' } fails. { ["if"] = "can do it" } succeeds.)

We can use numeric indices along with string ones in the same table:

  mixed = { 'a', 'b', 'c'; state = 'NC', city = 'Charlotte', county = 'Mecklenburg' }
If we want to access values from a table, we can use a subscript notation.

  > print(a[1])
  red
  > print(faves['bob'])
  pizza
Here's a winner though. If we want to subscript a string, we just use the dot notation.

  > print(faves.mary)
  pie
What happens if we subscript a nonexistent entry in a table?

  > print(faves.james)
  nil
No error is thrown, which is handy. Tables are defined as having the unique value nil as the value for all nonexisting keys. In fact, if you wanted to remove an entry from the table, you just set the key to nil:

  faves.george = nil -- the cake is a lie! And the key 'george' is removed.
One nice thing about Lua tables is that they are extremely regular. There aren't special cases in their behavior. They're easy to construct, inspect, and manipulate. They are the fundamental data type of the language, and everything is done in terms of tables. Objects are created out of tables. Namespaces are tables. Modules are tables. Configuration files are tables. It's an extremely clean and convenient design.

In addition to the tables, you get first-class functions:

  > function a() print 'hello' end
  > a()
  hello
Is syntax sugar for:

  > a = function() print 'hello' end
  > a()
  hello
These syntax sweeteners we've seen work together, too:

  > function a.foo() print 'world' end
  > print(a["foo"])
  function: 0x807f2e8
  > a.foo()
  world
Ok, we're almost to objects. The next sugar we see is the ':' notation.

  > a = { color = blue }
  > function a:fave_color()
  >   print('My favorite color is ' .. self.color) -- .. is concatenation
  > end
If a function is defined with the ':' notation then it has an implicit local value called 'self' which is set to the containing table.

  > a:fave_color()
  My favorite color is blue
At this point it's pretty easy to create simple prototype-based objects.

What brings even more power to the table is that we can define custom behavior on each one. We can set a 'metatable' which defines how the table responds to subscripting, the various mathematical operators, and being called in the functional position. In a quick script I worked on I implemented a prototype-based class system in under 20 lines of code, all with the power of metatables. Lua tables are very powerful and though similar to the ones in Javascript are even cleaner and more pleasant to work with.

Anyway, it's all nifty stuff. Other features you get: fast incremental garbage collection, first-class functions, asymmetric coroutines (isomorphic to one-shot continuations), lexical scoping, closures. Lua is also one of the fastest interpreted languages and has an excellent API for binding to C if you want to do something performance sensitive, or want to use a library written in C. The language itself is written in 100% pure ANSI C. It runs on everything from embedded microprocessors to mainframes, in lego robots and on space satellites. And the community is very friendly.

If you want macros, there are several community-run variants of the language with macro facilities... I haven't gotten very deep into those though.

Good documentation for getting started:

  http://www.lua.org             -- official lua home page
  http://www.lua-users.org       -- lua community wiki
  http://www.lua.org/docs.html   -- lua documentation
  http://www.lua.org/manual/5.1/ -- lua language reference
  http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf -- a fascinating article on the history and features of lua
Check Lua out and see how you like it. I find it to be a very pleasant language which is fun to work in.
And what about CLOS? Does not make Lisp, in your words, a "hash programming language"?

Really I don't get the difference you state between list/hash programming languages, I've work a little with EcmaScript and I don't know anything about Io, but I'll put it in my to-do list. Can you, please, extend it? What's the main difference between them?

Because you say that the primary abstraction of a "list" programming language is a tree, which I think not. Since a tree is a directed graph without cycles, you wouldn't have loops. So, at least is a directed graph. But then, which property has the "hash" language graph that the "list" language graph has not? It's an undirected graph? It think it is not possible, because if it's undirected how do make your program to go "forward" and not "backward"?

I hope I'm not too obfuscated and my questions make some sense.

I've never looked at CLOS. But, what I mean is the "dominant metaphor," which I suspect is still lists.

I'll just go through my probably plebeian understanding. Arrays are to lists as hashtables are to objects. An array, in my mind, is a list that only contains one type and is indexed with enumerated integers. On the other hand a list can contain any type, but is also indexed with enumerated integers.

In JavaScript:

  array = [1, 2, 3]
  list = ["one", [[array], 3]]
  array[0] == 1
  list[20] = 23 // list indices aren't necessarily a linear enumeration
Of course, arrays and lists are both technically Arrays in JavaScript (a bad naming choice; I'd have called them Lists). Now a hashtable is typically just a list that uses strings for indices instead of integers.

  hashtable = {
    "today":20080403,
    future:function(x) { return this.today + x }
  }
A "method" is just a value that happens to be a function. Usually hashtable-oriented languages choose to abstract away the string, and treat it as a variable.

  hashtable["today"] == hashtable.today
  hashtable.method(23)
Like with lists/arrays, JavaScript gets hashtables/objects almost exactly right, but again is subject to some questionable naming choices.

RE: trees and graphs -- I was getting at the relationships between nodes, not the actual computations, but I'm not comfortable enough with the terminology to explain exactly what I meant.

Ok, I think I get you. But then I think you can't categorize programming languages this way, the default data-structures provided by the language don't characterize it. In fact, it seems to me that you like most JavaScript because it's object oriented, it's dynamic typing and functions are first-class. Features that others languages lack, and not because the "dominant metaphor" is the hash. Casually in JavaScript the objects are built with hashes (at least apparently), and can be easily extended.

It's a fun thread :D

Built-in data structures are a big point of categorization of langs on my end. To me, the defining feature of Java is its classical structure. If you got rid of classes, you'd have a different language (in the interface sense). And interface is really all I'm talking about.

I'm basically just asserting that objects (should) == hashtables. This is quite literal in JavaScript. Other languages bend the metaphor in different directions, and obscure it to that no one even knows what "object-oriented" really means beyond particular idiosyncratic syntax in this language or that.

PG of course talked about this before, in Why Arc Isn't Especially Object-Oriented:

> I've done a lot of things (e.g. making hash tables full of closures) that would have required object-oriented techniques to do in wimpier languages ...

I'd argue that he was employing genuine object-oriented techniques, but just didn't have classical syntax and didn't consider what he had an "object." Other languages make a point about it, and use special syntax, which fogs the whole thing. Perhaps some people in "OO" mindsets have the kind of naivete that C-only hackers I've met have about first-class functions.

Actually, I just realized the whole reason C++, Java, C#, and co. have "methods" in the first place is just compensation for not having first-class functions you can stick in a hashtable.

If objects == hash tables, any language with decent hash tables, including lisp, has objects that you're perfectly happy with. Lisp also has other data types, but their existence means that lisp has more power, not less.
> But, what I mean is the "dominant metaphor," which I suspect is still lists.

That tells us more about the basis for your suspicions than it does about lisp.

It's okay to like javascript more than you like lisp. It's also okay to be mostly ignorant of lisp. However, it's poor form to make up things to "support" those positions.

"And what about CLOS? Does not make Lisp, in your words, a "hash programming language"?"

I think in the "hash" languages, you usually consider the method to "belong" to an object in some way.

With multi-method dispatch, the relationship between objects and methods is more fluid. So I think there is a difference here, too.

It's not that "list" languages only use tree structures for everything, just that trees are the more "natural" choice in those languages.

While lisp code is represented as lists, other user data can be represented with hash tables, vectors, arrays, classes, etc, including lists. (Yes, the name "lisp" refers to lists, but the language has grown since the name was picked.)

Also, it's easy to represent arbitrary graphs with lists, in much the same way that you'd do so with hash tables, structs, etc. Yes, the way that a node refers to other nodes differs but there's no restriction on the relationship of the nodes or the overall structure. (The difference between different kinds of graphs has nothing to do with how one node refers to another.)

And, macros have nothing to do with any of this because they are "just" code that turns code into other code. Perhaps another code representation would be better than lisp's, but since few languages have one, and some of those that do break it with every release....

In short, Gordianknot's thesis and examples are wrong, he doesn't understand graphs, and he has no idea what macros do.

I was only talking about the code itself, the primary metaphor of the programming language, not what the language actually represents. Most langs that I've encountered are structured as semi-formalized strings (i.e. C-derived langs), where as Lisp is structured in "physical" lists. I shouldn't have talked about "abstractions," because that wasn't what I meant. I was getting at the "concretions" of the actual lingual interface.
> was only talking about the code itself, the primary metaphor of the programming language, not what the language actually represents. Most langs that I've encountered are structured as semi-formalized strings (i.e. C-derived langs), where as Lisp is structured in "physical" lists.

Huh? Let's review.

>>>If that's true, it follows list-oriented languages are inherently inferior to their hashtable-oriented brethren.

Javascript code is semi-formalized strings or ASTs.

The careful reader has noticed that lists that represent code are ASTs with context-dependent field names. Since the nodes provide the context, said dependence isn't a big deal.

I don't know how many javascript programs manipulate their ASTs. (Lisp programs with macros are manipulating their ASTs.) The vast majority of javascript hash table operations are on data. (Yes, lisp code can be data, but not all lisp data is code.) In that, they're no different than any other language that has decent hash tables, such as lisp.

I don't understand why Java is "hashtable-oriented"...

What design flaws of Lisp are seen as features?

Really what are classes other than sugary hashtables?
sets of related closures over common state?
Okay, and I would look to hashtables as the best way of describing these "sets."