Computing Thoughts

Ruby, PHP and a Conference

by Bruce Eckel

January 26, 2006



Summary

My previous posting about Ruby generated a lot of noise and very little light -- that is, not much in the way of compelling reasons to learn the language. So I went to a couple of Seattle.rb Ruby users group meetings and spent time with 3 uber-geeks. Now I at least have the beginning of an understanding of what's interesting about this language.


But first, a conference, and what I learned at the PHP meetup.

Conference: Programming the New Web

I've participated in several OpenSpace events and have held a couple of private ones, and they are easily the most interesting, stimulating and educational ways for people to communicate in a conference setting that I've ever seen. If I could figure out a way to do it, I would spend a large portion of my time running OpenSpace events.

I'm going to be in Crested Butte, Colorado until the end of the ski season, and have decided to hold my first public conference during that time. I debated about the whole skiing issue ("what if it looks like a boondoggle to a manager that has to approve it?"), and finally decided that there's no way around it. If people come to the event, they will ski, so we'll open up the best time of the day to let that happen, and organize the conference around it. If you don't want to ski, it's free time.

You can learn more about the conference, register, and buy a T-shirt here.

PHP

The PHP meetup was helpful, but I seem to have generated more questions than answers, mostly because I haven't figured out the best way to use PHP.

I think PHP is what HTML should have been. That is, not just a markup language, but also something to program with on the server side. Things would have been much easier had this been the case from the beginning (of course, had we had a more-standardized and less-heinous language than Javascript on the client side that would have helped everything an awful lot, too).

I really like how straightforward PHP is. You create one file containing both your server-side programming and your HTML markup, drop it into any directory in your URL-space, and it works. At some point there are probably scaling problems that result from mixing logic and presentation, but up to a certain size and complexity it seems easier to manage than the alternatives, precisely because everything is in one place.

I have an account at GoDaddy, which supports PHP, and all I had to do was drop this file anywhere in the directory tree (not just in a cgi-bin or other special directory), and it worked:

<html> <head> <title>PHP Information</title> </head> <body> <?php phpinfo(); ?> </body> </html>

Basically, all this does is display the information about the PHP installation by calling phpinfo(), but it gives you the idea of the syntax of PHP, which is familiar if you've used anything like ASP or JSP. You can see the output here.

This brought up my first question. When I signed up for GoDaddy roughly a year ago (basically just to support downloads), I talked to their tech support people who made it sound like they'd be updating to PHP5 any day now. But here it is a year later, and when I sent an email to them I got a reply indicating they had no immediate plans to do so.

This is disappointing because PHP5 is the Cadillac of PHPs, and the one that I think got IBM's endorsement. This version has all the object-oriented stuff in it: classes, interfaces, access control, pretty much everything you've seen in Java with a bit of C++ here and there. Basically, it allows you to build serious stuff without feeling like you've been thrown back into C by using PHP.

But GoDaddy's reluctance to upgrade bothers me. Is this typical; are the major web hosts reluctant to upgrade for some reason?

And does it matter that much, or will I mostly just be bashing data into and out of a database, so perhaps I don't need the fancy features anyway? (Or will I get into it and discover that I could really clean up some of the database-bashing by writing a few classes and saving a lot of work?). I don't know the answer to this; maybe a PHP expert can tell me.

I do know I'd like to do more of it, one way or another, because it's so direct. I've seen that a lot of sites use it and do reasonably complex things, and they seem to keep the complexity under control (ITConversations, for example). And there are tools so that you can easily run PHP locally while you build the site. The best one of these for Windows is apparently XAMPP, which also supports Linux, OSX and Solaris.

One thing I've noticed, though, is that I've gotten very attached to Zope's through-the-web interface. I even looked around to see if there was some way to run PHP code through Plone (the popular CMF built on top of Zope) and it would seem that there is, but I have no idea how well it works.

In the process of looking through the GoDaddy setup screens, I came across something that claimed it supported Python. I tried putting a python script, with the #!/usr/bin on the top, in the cgi-bin directory, set permissions, etc. but it wouldn't run. Has anyone tried this?

I also found something even stranger in the GoDaddy setup screens. There was an option to "enable" Java, which apparently took 24 hours. But no information on what you were actually enabling -- like whether it supported Servlets and JSPs or if you were just getting a standard Java installation (and if the latter how you were expected to use it). If anyone has used this feature, please add a note in the comments; thanks.

Ruby

When I went to the PHP meetup, Ryan Davis happened to overhear and came over to join us. Later we got to talking about Ruby and I voiced my current complaints. He asked me a critical question about what bothered me:

"Is it aesthetics?"

At which point I realized that yes, I had gotten hung up on aesthetics and similar issues. For example, looking at all the "end" statements in Ruby is annoying; I find Python's indentation much more natural. I've noticed that the same people who can't cope with indentation to establish code blocks still indent their code to show code blocks, even if their language uses curly braces or the like. And yes, some of the things in Ruby that look like Perl have made me recoil. But Ryan was right, these were aesthetics, and worth getting over, at least temporarily, in order to see what's really interesting about Ruby.

Ryan described meetings of the Seattle.rb Ruby users group and so I ended up coming to a couple of them and getting some great one-on-one tutorial time with Ryan, Evan Webb and Eric Hodel, all of whom have done some very immersive work with the language. They all live and breathe this stuff, which is exactly what I needed to get some actual insights into Ruby.

My perception is that I've been getting very sketchy information up until now. Bruce Tate's "Beyond Java" book came closest to showing some of the especially interesting things about Ruby (continuations, for example), but most discussions just don't seem to have any of the real meat in them that I found by going to the user group meetings.

The first meeting I went to I focused on some of the bigger, fancier things like continuations and threading. But last night Eric Hodel (who considers himself lucky to have been able to go directly from college into a Ruby-only existence four years ago) went through Ryan's Ruby Quick Reference and basically gave me a walkthrough of the language.

The result: there are definitely some very cool things here. I don't know why Ruby fans don't talk about them, but they're here. Whether or not I end up using Ruby for anything, experimenting with some of these things will definitely change my perspective on OO. In the past, I've found that after learning Java I could go back and write C++ that I hadn't been able to think about before. Learning Python affected both my C++ and Java code. And some of these Ruby concepts will certainly improve what I can do with the other languages.

case

In the category of basic language improvements, consider the case statement. Python doesn't have a switch or equivalent, the argument being that it's effectively a sequence of if-elses. Which is not too bad in Python; the language is such that you don't end up typing much more than you would with a builtin switch. But C, C++, Java, and C# (with the exception of string values) all insist on using integral values in their case statements. The reason has traditionally been "efficiency, and because C did it that way."

The problem with the efficiency argument is that if you want to match anything other than integral values -- and you often do -- then you must write a bunch of other code that doesn't use the switch statement, which will end up taking at least as much CPU time, and probably more, than if you could have any kind of case (yes, C# lets you have string cases and that helps, but it's not a general solution). And of course it ends up taking significantly more programmer time, which is the actual scarce resource, anymore.

In Ruby, the case statement can match with any object. It uses the "===" method in Object to perform the match, which can be overridden for your own classes. Since everything is really an object in Ruby, this includes any type of literal, regular expressions, and object types. Here's an example (comment syntax is like Python's):

# File: Case.rb def f(x) case x when "foo" puts "#{x.class} foo" when /\d+.*/ puts "mixed: #{x}" when String puts "string #{x}" when 1000..1500 puts "between 1000 and 1500: #{x}" when Fixnum puts "Fixnum #{x}" when Object puts "object #{x} #{x.class}" end end f("foo") f("123abc") f("bar") f(1066) f(1) f(1.1)

The output for the above program is:

$ Ruby Case.rb String foo mixed: 123abc string bar between 1000 and 1500: 1066 Fixnum 1 object 1.1 Float

You can see that the syntax is kind of backwards; case is the equivalent of switch in other languages, and the branches are delimited by when.

Regular expressions are similar to Perl's in that they can be delimited by '/' (but you can also create more conventional regular expression objects as you do in Python). One way to insert a variable in a string is to use the '#{}' as you see above.

String, Fixnum and Object all represent class objects. Class types are inherited from Object and thus also have '===' defined, so they can be used in a when comparison.

Implied self

One thing I've never quite bought into in Python is the explicit self. This is supposed to be because "explicit is better than implicit," but if that's true then why is it explicit in the method definition:

class PythonClass: def method(self): self.x = 1 # Create a field x if it doesn't exist

but implicit in the method call?:

pc = PythonClass() pc.method()

Ruby follows C++, Java, C#, and most other languages in making 'self' or 'this' implicit everywhere. It appears only if you need it. Since you always have self in a method, this makes more sense to me (albeit it ranks as a small nit).

Here are some examples of classes in Ruby:

class MyClass def method1(arg1) # Like C++ & Java, self is implicit puts arg1 self.f() # ... but still available end def f() puts "in f()" end def method2(arg1, arg2) puts "#{arg1}, #{arg2}" end # Constructor: def initialize(arg) puts "In constructor with arg #{arg}" @field = arg end end obj = MyClass.new("yo!") obj.method1("hi") obj.method2("hi", "howdy") p obj.class # Inheritance: class MyClass2 < MyClass end # Constructor with arguments automatically passed through: obj = MyClass2.new("yow!") class MyClass3 < MyClass2 def initialize(arg) super # Passes argument list through to base constructor super(arg) # Explicit argument passing end end obj = MyClass3.new("bing!") p obj.class.ancestors # Simple reflection

Here's the output:

In constructor with arg yo! hi in f() hi, howdy MyClass In constructor with arg yow! In constructor with arg bing! In constructor with arg bing! [MyClass3, MyClass2, MyClass, Object, Kernel]

Class names must start with a capital letter.

def starts a block (without you having to say begin). initialize() is how you create a constructor, and super can be used to explicitly call the base-class constructor. Inheritance is indicated with <.

The '@' sign is used in place of Python's self. to indicate a field. Although it does make me think of Perl, the brevity is nice.

At the end, note the use of simple reflection, which just calls intuitively-named methods. The p evaluates and prints the result; it's generally used for debugging.

Reflection

As seen above, this is quite intuitive. For an object, for example, you can say:

p obj.class p obj.methods

For a class, you can find the class methods by saying:

p MyClass3.methods

There's lots of other stuff like this, which you can discover by poking around.

Open classes

Here's something quite different. You can modify an existing class. Put another way, classes are not "closed," and anyone's class can be extended.

class Array def dump for x in self puts x end end end a = [1,2,3,4,5] a.dump

Array is a built-in class for the object created for square-bracketed lists. But in the code above it looks like I'm defining my own class called Array. But I'm able to call dump when I create a built-in Array using square brackets. The output is:

1 2 3 4 5

Thus, you can easily add to any class. This eliminates the need for a significant number of tools created for other OO languages that modify existing classes: multimethods, AOP (to some degree, anyway), and even the visitor pattern.

Class Variables and Class Methods

Class variables in Ruby are not too different from the way they work in Python, but they are special denoted in Ruby with a leading '@@' symbol.

Not a huge issue, but Python went through some gyrations to produce class methods, ending with the @classmethod decorator. In Ruby it doesn't require any particular meta-text; it's consistent with everything else:

class ClassVariablesAndMethods @@classVariable = "initialized" def ClassVariablesAndMethods.classMethod() p @@classVariable end end x = ClassVariablesAndMethods.new x.class.classMethod() ClassVariablesAndMethods.classMethod()

Note that you can't call a class method directly through an instance; you must call class first.

Prototyping and singleton classes

You can take a single instance and add methods to it:

class X def f() puts "f()!" end end x = X.new x.f() def x.g() puts "g()!" end x.g() y = X.new y.f() # y.g() # Undefined method

The method g() is only added to x, which you can see because y, created after g() is defined, doesn't have a g() anyway.

You can do something like prototyping with Python, but I don't think it's as obvious.

A singleton class is a slightly more formal way of accomplishing the above:

class X def f() puts "f()!" end end x = X.new class << x def g() puts "g()!" end end x.g()

Modules and mixins

One thing that has been requested, largely by the folks who create Zope, is interfaces in Python. Since Python is a dynamic language and interfaces tend to be more of a compile-time thing, I've almost seen the value sometimes, but it's never pushed me over the edge on the feature.

Modules look like they might be the dynamic language's answer to interfaces. Syntactically, a module is a class with the keyword module instead of the keyword class. So, unlike a Java interface, you can have code in a module (and, unlike Python, there's no pass equivalent in Ruby, so you're basically forced to provide code for all method bodies). Like a Java interface, you can't instantiate a module, you can only use it as a "mixin," to paste in both interface and functionality into another class. So a module distributes a common interface and default behavior across class boundaries. Later, if you change a method definition in that module, it will change in all classes that include that module.

Here's an example:

module CommonStuff def f() puts "f()" end def g() puts "g()" end end class Mixed include CommonStuff def h() puts "h()" end end def takesCommon(arg) if not CommonStuff === arg raise "You have to pass in a CommonStuff!" end arg.f() arg.g() end m = Mixed.new m.h() takesCommon(m) begin CommonStuff.new rescue Exception => e puts e end begin takesCommon([]) rescue Exception => e puts e end

As you can see, a module is written exactly the same way as you write a class, but the way it is used is constrained. If you try to create a new object, it tells you that there is no new for CommonStuff. This attempt uses exception handling (the rescue clause) to prevent the program from aborting; you can also see an Array object passed to takesCommon() at the end. The output is:

$ Ruby Modules.rb h() f() g() undefined method `new' for CommonStuff:Module You have to pass in a CommonStuff!

Exception handling: retry