My first exposure to Smalltalk was around 2001. It was using Smalltalk MT, and I created a simple raytracer with it (that was the typical pet project I did with all new languages at the time). It turned out reasonably well. I didn't really understand much about Smalltalk at the time other than the syntax and how to move around the environment. Once I did enough to feel that I had a "reasonable understanding" of the language, I put it down and moved onto other things....
Over the next 5 years I would periodically check out Squeak. Each time, Smalltalk looked a little more foreign than I remembered it being. Squeak would never last more than 10 minutes on my machine. This was mostly due to it being extremely different from the OS I was working on (Windows XP), and it being very unintuitive to the newcomer (if it truly is "great for kids," then I must be getting old). The other major change in my life was that I went from being an desktop/embedded programmer to a console game developer (believe me, this is a completely different way of thinking when it comes to programming). Subsequently, I never really gave Smalltalk more than a passing glance.
Very recently (mid-2006), I was quite bored, and decided to do a Google search for Smalltalk once more. Perhaps something new was around (funny that something as simple as a programming language could hold such an allure that I'd keep coming back to it). This is when I found an updated version to a very slick looking implementation of Smalltalk: Dolphin X6. They recently published a free (community) edition and so I downloaded and gave it a try.
First, let me say that the boys (Andy Bower and Blair McGlashan) at Object Arts did an absolutely fantastic job with their presentation of the language. All implementations I'd seen to-date were either very foreign (Squeak) or extremely "old" (ObjectStudio, MT). This not only looked and felt modern, it actually had all the features a professional programmer expects from a development environment: syntax highlighting, view composing, source control (built in), tutorials, and more, all presented very elegantly. Andy and Blair really took their time and got it right!
Alright, so I was sold on the presentation. However, over my many (10) years of programming experience (mostly C/C++, but also Forth and lots of assembly) I had slowly come to completely distrust object-oriented programming. OOP is a tool. It can help in some domains, but can also cause more problems than it solves in many others. All too often, some co-workers and I at find ourselves chuckling and commenting, "I've C++'ed myself into a corner again." Ah, the allure of OOP is great.
Now, before I get comments from OOP fanatics and C++ gurus telling me how we do it "wrong" let me asure you that we don't. In the world of game development, the twin evils are Design and Fun. Both of which cause the requirements of the game to change radically from day to day (and sometimes from hour to hour). Because of this, development needs to be fast, fast, fast. This idea was best expressed by Chris Uppal in a comp.lang.smalltalk post:
> Make it work.
> Make it right.
> Make it fast.
Chris disagreed with this when he posted it, but that's okay. In games, making it work is most important during early development. It's all about prototyping. The designers and artists need to see what it's going to be like before deciding to keep it, throw it away, or do something different. With a heavy OO philosophy, a programmer will spend far too long working out the perfect class hierarchy just to get a triangle drawing on the screen. And God forbid when the designer sees it, he decides that he'd like stars instead. Suddenly half the code is now useless (and the other half needs restructuring to be useful).
Alright. So what's this got to do with Smalltalk? Well, I was skeptical. I was still curious, but I really didn't want to spend my free time I had relearning a language that was all about something I was almost entirely against.
But then it occured to me - what if the phylosophy (OOP) wasn't the problem, but the implementation (language) was? Admittedly, almost all OO languages in existance are derivatives of C++. But, Smalltalk is very different! Also, Smalltalk was the first (well, second, but who's counting?) OO language. Certainly any inherent problems should have been solved by now. Perhaps there was a gem here, and I just wasn't giving it the complete attention it deserved.
I decided to write a complete program in Smalltalk. And, just to give myself an added challenge, it had to be a game: a complete DirectX driven game with sound, graphics, controls, etc. To be honest, I didn't care about the result, what I cared about was how it got there. Could it interface well with DirectX? Was there an advantage to the Smalltalk environment (Forth and Lisp both wowed me with their interactive developing)? And most importantly, would "true" OOP be a benefit or a hinderance?
For the first 2-3 weeks of playing with Dolphin, I was clearly unimpressed. The community was great (everyone at comp.lang.smalltalk[.dolphin] was extremely helpful). When learning Forth and Lisp, the biggest hurdle was getting around the community to ask questions. On comp.lang.lisp, try asking "how do I get the type of a variable?" and you'll get a barrage of snide replies reminding you just how naive you are, because "variables don't have types; values have types!" Of course, this has nothing to do with the evaluation of a language, but certainly made the experience a more pleasurable one.
That said, I was still unimpressed. In all the toy problems I'd try to learn the language in more depth, I kept coming back to my original concerns. I just couldn't see anything that would be helpful. In fact, I had an ah-ha! moment when I discovered how to actually define global constants in Smalltalk. Something as simple as a constant needs to be a string object, in a pool object, in a singleton Smalltalk object. And that felt like some serious over-engineering to me. But, I decided to chalk this up to the fact that I still hadn't done anything of any reasonable size and still didn't understand the language all that well.
And about one week later something happened. I don't exactly recall how it happened (reading a post or just trying something out), but I learned something that wasn't in any Smalltalk tutorials on the web (note to Smalltalk tutorial writers: this needs to be there):
Everything in Smalltalk is an object!
Okay. That's in every tutorial to Smalltalk on the web. However, those tutorials don't show you just how far the rabbit hole goes! Let me explain. Coming from C++, a class has a constructor: the place where data is initialized. Likewise, in Smalltalk, objects have an instance method called #initialize. However, in C++, one uses the new operator to create an instance of the class, and it (in turn) calls the constructor for me. That isn't the case in Smalltalk. In Smalltalk, when you define your class, there is suddenly a new object created in the image. That object is the "class object" for that class. The class object is a kind of singleton where class data and methods are.
So? How is this different from static data and static methods in a C++ class? That's what I initially wondered as well. And, let me tell you, there is a world of difference. But, let me reiterate again this key point: when you use the class name in your source code, you are sending a message to an object! In Smalltalk, if I type:
ShellView new
This calls the #new method of the class object for the ShellView class. ShellView is not a keyword or a type. It's an actual object, and you are sending it a message. That message is resposible for the creation of an instance of the ShellView class, and returning it. This is very important. Because each class has a class object, this gives Smalltalk some unprecedented reflection capabilities. And I'm only just beginning to scratch the surface of them. As an example, open up the Worksheet; we're going to try a few things:
ShellView allSubclasses
There is a list of all the class objects which constitute that classes which derive from ShellView.
ShellView allInstances
There are all the instances of the ShellView class that are currently in existance right now.
Pretty slick. And that's just 2 of the many functions that class objects have available to them. Experienced programmers should already be drooling. This alone provides some impressive power. But, let's show a practicle use for someone still a little confused or not yet convinced.
In my game engine, I have certain functionality that is required in a subclass of ShellView that I need the window to have. So, I created GameView, a subclass of ShellView. However, I don't want the end-programmer to actually use GameView. Instead, I want them to subclass it and override some important functions that describe the behavior of the window, etc. At the same time, for various reasons, I don't want the end-programmer to actually create and instance of this view (for DirectX reasons I'd like only one at a time to ever exist).
So, what I was able to do was in my GameEngine object, I created the method #createGameWindow:fullscreen:. This method's first parameter is a class object, which is of the GameView subclass you want to use. But this poses a problem: how can I be sure that the class object passed is a subclass of GameView? After all, I need to make sure that it will work. However, this is a trivial problem to solve:
self assert: [class inheritsFrom: GameView].
Done. The C++ solution would have been to have the end-programmer create the view and pass it in. However, the Smalltalk method has an added bonus: I don't have to create the window right then! I can just hold onto the class object and create it later when I really want to. Or, I might never create it (in the event that some other initialization code failed).
Alright. You get it. The reflective power of Smalltalk is awesome. This was my "the sleeper has awaken" moment in Smalltalk. And consequently, one hour later I purchased a copy of Dolphin X6 Professional. But, this still didn't answer the question of whether OOP would hinder more than help in the development of a game, and subsequently, whether the OOP problem was one of phylosophy or implementation.
At this point, I think it's fair to say that if I hadn't purchased Dolphin, I have thought the OOP was more of a hinder than a help. And this isn't because it's true. It's because the professional version of Dolphin comes with some very nice features, most importantly the Idea Space. Up until this point, I had painstakingly been putting together Direct3D and DirectInput wrappers for Dolphin. Not only did I have no less than 7 Dolphin windows open at once at any given time, but no matter how friendly the UI was, jumping around between objects, copying, pasting, package browser, etc, was becoming a monstrous headache. The Idea Space made that headache go away with a single click. To anyone on the edge of purchasing Dolphin, just know now, the added features in the Pro version are well worth the purchase price.
Back on point. One of the major reasons someone can "C++ themselves into a corner" is that if their class hierarchy is wrong (and it always is). Rearchitecting can be a major hassle. And if you discover the problem(s) well into development, you may just be stuck with them. Likewise, whole companies have succum to the "rewrite it right" bug (summary: in rewriting, you lose a lot of fixes). So, does Smalltalk suffer from this as well?
Honestly, I don't know. Going into this, I would have felt very comfortable saying "yes." But there have been a few things happen that are making me wonder.
As part of my "make it work" step, the first thing I wanted to do was get a 2D texture on the screen. Without going into morbid details, this required a specific vertex format (one of many) with a RHW vector component, texture coordinates, diffuse color, etc. Once I got the texture rendering, it was now time to implement the other vertex formats. However, in a perfect world, the old format would be a subclass of one of these new ones. Behold, all I need to do is drag the old class onto the new one, it's now subclassed. I rename it to a more appropriate name, and Dolphin opens a new window showing me every place in code that referenced the old name so I can change them promptly.
Later, in further development of the game engine, I have no less than 5 times changed hierarchies and inserted new objects to simplify the current (working) ones. These changes have taken minutes (not hours as it would have been in C++). I found that the "make it right" step wasn't so clearly a distinct step from "make it work" any more. Once the code was working, I was immediately able to make it right.
From past experience programming GameBoy Advance games in Forth, I know that interactive development is a monumental win for any programmer. I've already been able to have my game engine up and running, and change the render loop on the fly and see updates without ever stopping execution. And while the engine is running, I can inspect it at any time and modify any data within it, all without bringing down the program. While this has nothing to do with OOP, it certainly is a testament to Smalltalk (and to a great implementation).
I'm only at the beginning of the road. I'll be posting more thoughts and findings as I continue down it. But I'm feeling very good, and I look forward to finding what else Smalltalk has lurking under the hood for me.
Jeff M.
P.S. A hearty thanks to everyone at comp.lang.smalltalk[.dolphin] for taking the time to answer my questions, answer them thoroughly, and without the typical "why would you want to do that?!" usenet response. :-)
03 September, 2006
Subscribe to:
Posts (Atom)