The Universal Design Pattern
steve-yegge.blogspot.com
steve-yegge.blogspot.com
See also: Chapter 16 (OO) in _Programming in Lua_. The first edition is available online: http://www.lua.org/pil/16.2.html
Here's a good example, with lots of commentary: http://www.erix.it/progutil/gpeddler2/help/oo.html
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 :)
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.
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 (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)))) (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)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.
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.
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.val m = Map('name -> "Bob", 'age -> 30, 'color -> Colors.Red)
* 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.
There are some real gems in there. :)
;)
"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%.)"
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.
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"?
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
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.
Good ol used books.
Linky: http://will.thimbleby.net/misc/ (Java alert!)
Sure it does - just cons stuff onto assoc lists. I think PG mentioned this at the end of his "Arc lessons" essay.
You could use hashtables instead, but the syntax for hashtables in Emacs Lisp is cumbersome.
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.)