The Artima Developer Community
Sponsored Link

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.

Advertisement

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

As you can see in the example above, basic exception handling is fairly consistent with other languages with the exception of the structure. Any begin-end block can include an exception handler, which uses the keyword rescue instead of Python's except or Java/C++'s catch. If you want to do something with the exception object, you extract it into a local variable using ==>. To throw an exception, you use the raise kewyord just like Python. There's the equivalent of a "finally" mechanism using the ensure keyword.

Unfortunately there doesn't seem to be an assert as there is in Python and Java. Effectively this is syntactic sugar, but I still like it.

Instead, in the syntactic sugar department, there's retry, which appears at first to be an attempt to resurrect the idea of resumptive exception handling. This means that the exception handler gets a chance to catch the exception and then continue at the point of failure. Resumption was tried for systems in the 60's, but failed, most likely because of the resulting high coupling between the various points an exception could be thrown and the points where it was caught.

If you use the retry keyword, on the other hand, the point of execution goes back to the beginning of the block that threw the exception, so it's basically the same as if you set up a while-not-succeeded loop. Here's an example:

count = 0
begin
  puts "at beginning"
  if count == 0
    count = count + 1
    raise "First time through"
  else
    puts "second time through"
  end
rescue Exception
  puts "In handler"
  retry
end

Here's the output:

$ Ruby Retry.rb
at beginning
In handler
at beginning
second time through

You can see that retry sends the point of execution all the way to the beginning of the block.

Comment Documentation

Ruby also has a standardized comment-documentation system and a utility called rdoc to build help from the source code. It seems likely that this was inspired by Java's, as a number of other systems appear to have been. But there are some situations where it's very nice if rules are blessed by the language so that people are less likely to get stuck arguing about them (another reason I like Python's indenting -- all source code looks the same, so I don't have to use brain cycles sorting it out). Although the resulting Java documentation isn't always the best you could hope for, I consider standardized comment-documentation one of the number of contributions that Java has made to programming world.

Conclusion

This is just a few of the features I found interesting. Note that I haven't even touched on blocks (although the uber-geeks gave me plenty of coaching on these, I haven't internalized them yet) or continuations, threads and processes. All these look quite interesting as well, but require more time and space than I have right now.

Am I going to drop Python for Ruby because of the very interesting way that Ruby does things, and because of some of the additional features it has? No -- I have a great deal invested in Python and am very productive in that language. Also, there are places where Ruby still has a kind of early feel to it -- the libraries are not nearly as filled-out as Python's, Ruby can seem slow at times, and the error checking can be primitive (for example, I left off an end statement on a method in the middle of a file, and all it would say is "syntax error" for the last line of a file -- in a larger file that could be a very hard bug to find).

However, for starters, I'll now be looking more closely at Python to see how it handles some of the less obvious issues, especially classes and reflection. I've also got a foothold in Ruby so I will be learning more about it (and going to more user group meetings if I get the chance). And for me, most importantly, I've got some new ways to think about object-oriented problem solving, which I have always found to come in handy.

Good resources:

Talk Back!

Have an opinion? Readers have already posted 43 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Bruce Eckel adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Bruce Eckel (www.BruceEckel.com) provides development assistance in Python with user interfaces in Flex. He is the author of Thinking in Java (Prentice-Hall, 1998, 2nd Edition, 2000, 3rd Edition, 2003, 4th Edition, 2005), the Hands-On Java Seminar CD ROM (available on the Web site), Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003), C++ Inside & Out (Osborne/McGraw-Hill 1993), among others. He's given hundreds of presentations throughout the world, published over 150 articles in numerous magazines, was a founding member of the ANSI/ISO C++ committee and speaks regularly at conferences.

This weblog entry is Copyright © 2006 Bruce Eckel. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use