Hacker News new | past | comments | ask | show | jobs | submit login
The Universal Design Pattern (steve-yegge.blogspot.com)
127 points by mqt on Oct 20, 2008 | hide | past | favorite | 44 comments



While he is focusing on Javascript and only makes a passing reference to Lua, it's worth noting that it has a very flexible and efficient implementation of property maps (called "tables") as its primary abstract data structure. You can quite easily set up inheritance behavior for prototyping, along with read-only tables and several other things, by defining access hook functions in "metatables" (property tables for the tables themselves).

See also: Chapter 16 (OO) in _Programming in Lua_. The first edition is available online: http://www.lua.org/pil/16.2.html


Indeed. Lua lets you do practically anything you want, because it only gives you the building blocks of an object system.


It's not an elaborate undertaking, either: you can make an object system in just under a page of code.

Here's a good example, with lots of commentary: http://www.erix.it/progutil/gpeddler2/help/oo.html


I always find myself loving Lua, while continually wishing that it had a larger standard library like Python's. Without all the batteries included, it makes a great embeddable language, but stands very poorly on its own. It'd be my language of choice otherwise.


Same here. Have you looked at LuaRocks, though? (http://www.luarocks.org/) It automatically grabs and installs packages, a la CPAN. It will take some time for there to be libraries for everything, of course, but the infrastructure is there.


...none of these modeling schools is "better" than its peers. Each one can model essentially any problem. There are tradeoffs involved with each school...

Who are you and what have you done with Steve Yegge!?

The Pulitzer Prize it won doesn't nearly do it justice. It's one of the greatest and most unique works of imagination of all time.

Ah, there he is :)


A thing I have noted is that every time I code in Java I feel handicapped for the lack of proper syntactic sugar for maps (I mostly code in JS/Python where maps are used for _all_ kinds of things (and hacks)). Anyway, great post.


This is a key failing of Java, but you can make it hurt a little less with syntax like:

    Map<String,Integer> map = new HashMap<String,Integer>() {{
        put("x", 1);
        put("y", 2);
    }}
This abuses the fact that a brace after a constructor invocation indicates the creation of an anonymous inner subclass, and the second brace indicates code to be executed at instantiation time. So this "double brace initialization" has become a Java idiom. Still pretty awful, I know.

I'm not sure if anyone has been able to use generics and varargs to create a map generating static function, but it might be possible. Something like:

    Map<String,Object> m = map("x", 1, "y", 2);
I think the trouble is with automatically extracting the key value types from the varargs array. I think I have seen the approach:

    Map<String,Object> m = map(kv("x", 1), kv("y", 2));
where kv would just create a key value pair. That way you can make the generics inference come out right.

All of this does make a pretty good argument for Python, Ruby, Javascript, etc.

UPDATE: just found this:

http://nicolas.lehuen.com/index.php/post/2006/10/07/113-java...

    // Example usage :
    import static Literals.map;

    Map<String,Integer> example = map("hello",1).map("world",2).map("!",3);
Pick your poison.


Check out: http://gleichmann.wordpress.com/2008/01/13/building-your-own... for more tricks with varargs, generics and static imports to "pimp" things up a bit.


Ditto with Lisp, actually. Maybe something good could be done with read macros, but |(table 'key) -> (lookup table 'key) just doesn't do it for me. Any ideas? Lisp has alists, but (as noted elsewhere) they search in linear time.

Python, Lua, and JS all have syntactic sugar for maps, and I think it makes a really big difference. They're quite versatile. Lua's table.key -> table["key"] sugar for all string keys is particularly nice.


Clojure has squiggly brace syntax for map literals, and multiple shortcuts for map lookups. E.g. both (map :key) or (:key map) return the value for :key in map (I think).

Arc has similar syntax shortcuts for hashes (but sticks with parentheses):

http://ycombinator.com/arc/tut.txt

    arc> (let h (obj x 1 y 2)
           (h 'y))
    2


Don't know if this will do what you want, but these two reader macros add syntactic sugar for Common Lisp's aref and gethash forms. You should put these definitions in an eval-when. Disclaimer: I haven't used these in a while (not programming in Lisp at the moment, alas), so they may have some limitations which I have forgotten.

  (set-macro-character
    #\] (get-macro-character #\)))

  ;; Array syntax: [array-name array-references] is equivalent to
  ;; (aref array-name array-references)
  (set-macro-character
    #\[
    #'(lambda (stream char)
        (declare (ignore char))
        (destructuring-bind (array-name &rest array-references)
            (read-delimited-list #\] stream t)
          `(aref ,array-name ,@array-references))))

  ;; Hash table syntax: #[hash-table hash-key &optional default] is
  ;; equivalent to (gethash hash-key hash-table &optional default)
  (set-dispatch-macro-character
    #\# #\[ #'(lambda (stream subchar arg)
                (declare (ignore subchar arg))
                (destructuring-bind (hash-name hash-key &optional (default nil))
                    (read-delimited-list #\] stream t)
                  `(gethash ,hash-key ,hash-name ,default))))


Actually, would there be any real problem with keeping the map in a closure, and passing around a function that gets (or, with a second optional argument, sets) the value for a given key?

  (h 'key) and (h 'key 'new-val)
That's not too bad syntax-wise. Or, taking a cue from actor languages:

  (h 'get 'key) and (h 'set 'key 'new-val)


JavaScript also supports obj.id == obj["id"]. Python on the other hand does not support this (which is a shame, because obj.id is more readeable than obj['id']). But it's possible to hack this on dicts ( http://pastie.org/296506 ).

Syntactic sugar in languages is quite convenient, especially for things that are used a lot. Thought a lot of syntactic sugar (like in Perl) can ruin a language and it's readability.


I think syntactic sugar can make some things so convenient that people find novel uses for them. The ranged slicing operations in Python are really nice, for example; so is templating Lisp code with ` , ,@ once you get used to it.

Adding SS also adds to what you need to remember to use the language effectively (and to read other peoples' code), but a little bit of syntactic sugar can go a really long way.


Also Scala:

val m = Map('name -> "Bob", 'age -> 30, 'color -> Colors.Red)


Every time I read one of Steve's blog posts, I realize how little I know about software development.


Here are a couple more links with good discussion about prototype-based programming:

* http://www.c2.com/cgi/wiki?PrototypeBasedProgramming * http://www.c2.com/cgi/wiki?ClassesPrototypesComparison

SY is mostly talking about prototype-based programming in terms of Javascript, but it is also well-supported in some other languages, especially Lua (The chapter on OO in _Programming in Lua_ is IMHO the clearest explanation of prototypes I've found.), and it's probably possible to implement an inefficient-but-good-enough-for-brainstorming version in most languages.


Thanks for the links... I love the old c2 wiki.

There are some real gems in there. :)


It's only old in the sense that it has been there for a long time. It's still active and updated frequently.


Hooray! Some of my faith in Hacker News is restored. Every time I see a Yegge posted with a couple comments, I'm afraid that they will be complaints about how long it is. This time, they're about the article. Thank you for keeping it positive!


but well, it is looooong, isn't it?

;)


Be grateful:

"I considered splitting it into 3 articles, but instead I just cut about half of the material out. (Jeff and Joel: seriously. I cut 50%.)"


but it comes with a table of contents. ;) maybe he should always include one


It's a very short book.


tl;dr


You should look at "Io," a language based entirely on this modeling philosophy: http://www.iolanguage.com/

I have to disagree with people who say class based modeling is "intuitive". It only seems intuitive after you've been steeped in OO practices for years. (It certainly isn't intuitive to many first or second year CS students!) But prototype based modeling, on the other hand (prototype modeling is Io's term for yegge's Universal Design Pattern) is something that I think is intuitive, because there are no classes, only objects, and making a new object is just a matter of cloning an object you already have and adding to it.


What I have always thought is dangerous is that class modeling only seems intuitive when discussing everyday concrete objects. Your average CS student does not have a problem understanding that a Car has an engine and 4 wheels and is a type of automobile. However, when modeling more abstract concepts getting the design correct is usually very nuanced.


That's a neat post, the talk about Wyvern gets me really excited because game programming is my primary domain of interest.

Someone please enlighten me; is the property model pretty much just reverse or non-hierarchical OO? Instead of saying "J.T. inherits from football player" you say "football player doesn't really exist, J.T. just inherits from a bunch of objects which are instances that have the properties of a football player"? The difference here is that inheritance is more like a node graph and a not top-down hierarchy, where the only real root object is the super-generic "object" object.

If this is true, what about the "Chieftain" example Yegge said he left out? Is there a prototype called "Chieftain" which objects can inherit, but which alone doesn't fully implement a game object? Is it the case that you can't have "Chieftain" objects running around, as they only have all the properties needed when they are, for example, an "Orc Chieftain"?


If game programming is your interest, you might want to also look at Scott Bilas presentation for a "data-driven object system": http://www.drizzle.com/~scottb/gdc/game-objects.ppt

They implemented pretty much what Steve is describing for Dungeon Siege. (Presentation was in 2002, so they started on it probably around 2000. Take that for "no earlier references", Steve ;)

I vaguely recall that Thief used a similar system, too. There's also at least on "NextGen" game that uses that system that I'm aware of. It's pretty powerful (and makes it incredibly easy to shoot yourself in the foot)

As for the "Chieftain" example - it's usually solved by providing multiple prototypes for an object. With all the ensuing uglyness.


Thanks for the link! I actually saw something almost exactly like this by someone who was working for Looking Glass, but it didn't go in depth about the scripting layer, it just mentioned that you could either go with "static" classes, "data-driven" classes, or a hybrid. In my own development, I've generally gone with hybrid just because the value of flexibility surpasses any sort of performance hit, but I still keep some of the very simple stuff (object position, velocity, etc.) in the static layer.


> The difference here is that inheritance is more like a node graph and a not top-down hierarchy, where the only real root object is the super-generic "object" object.

Right, although there isn't necessarily an Object that everything inherits from.

You have an object, whose properties (both value fields and methods) are contained in a string-keyed table. If you want to inherit from it, you create another (probably via a method called "new") which defers to it when anything tries to find keys the new object hasn't overridden.

If you think of the initial object as a Class object that instantiates instances, that's the idea, but you don't need to have an explicit class for something you'll only ever have one instance of - you can just build it in place. (Want a singleton? Don't give it a method that spawns new instances. Done.) It's more generalized than OO systems in which Classes and instances are two fundamentally different things.

It may be helpful to read the chapter on OO in _Programming in Lua_ (linked above): It has a very concise intro to prototype-based OO. Lua may also be worth a look since you're interested in game development -- it is quite popular as an embedded scripting language in the game industry (and a pretty cool language in general, I think :) ).

Also, see: http://c2.com/cgi/wiki?ClassesPrototypesComparison


Whoa, what a coincidence. I thought of using this for a major programming project I'm doing, without any idea whether it was a good idea or not. I've been having some serious doubts as I've been implementing it, so this article gives me a sense of validation.


I just bought GEB cause its the 3rd time I've been suggested to read it.

Good ol used books.


Just a note, there is a python library implementing something very similar to this. It's quite nice, I use it regularly.

http://code.enthought.com/projects/traits/


The post reminded me of Will Thimbleby's MISC, an experimental Lisp-like language with maps instead of lists.

Linky: http://will.thimbleby.net/misc/ (Java alert!)


This is very informative... great post.


Unfortunately Emacs doesn't support any notion of prototypes.

Sure it does - just cons stuff onto assoc lists. I think PG mentioned this at the end of his "Arc lessons" essay.


True, but note that alists run in linear time. They're great for some things, but you probably don't want one with thousands of entries.

You could use hashtables instead, but the syntax for hashtables in Emacs Lisp is cumbersome.


I've only had a small amount of experience with Lisp, so I'm just curious: would macros solve syntactic problems like that?


Not really. There isn't really any major reason why you'd need to wrap it in a macro, because it's not enough code to be worth running at compile time, and you don't need to control evaluation order.

The bigger problem in elisp is that it doesn't have any kind of remotely sane namespacing, so instead of being something like Hashtable.make and Hashtable.set (which you could alias to HT.make and HT.set if you like), functions are either scattered all over the one global namespace or are all prefixed with cumbersome-package-name-. You could alias them all individually, but at that point it really doesn't contribute much to readability.

The potential solution probably involves read macros, which (for example) convert 'STATE to (quote STATE) at read-time. Having read macros that could convert e.g. x[23] to (nth 23 x), x['key 'val] to (puthash 'key 'val x), etc. would be quite enough, but read macros are prefix, and [23]x really doesn't do it for me. (I'm experimenting with adding postfix read macros to a Scheme-ish interpreter of mine, but I haven't found a good way to make it fit the rest of Lisp comfortably yet.)


Perhaps Greenspun's Tenth Rule ("any sufficiently complex C program contains an.. implementation of half of Common Lisp") applies as well to Java programs containing JavaScript. The question is whether Lisp and JavaScript contain each other.


My comment on how this is related to epistemology:

http://curi.us/blog/post/1339-steve-yegge-on-epistemology


Damn, did someone actually read this whole thing?




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: