{ |one, step, back| } 91 to 100 of 193 articles Syndicate: full/short

Kali Code for Dense Stellar Systems   23 Apr 05
[ print link all ]

Ruby and celestial mechanics… two great tastes that taste great together.

I saw this mentioned on the Ruby-Talk mailing list. The Kali Code for Dense Stellar Systems by Piet Hut and Jun Makino is a conversation between two astrophysists as they build a computer simulation of the N-Body problem. And they are using Ruby to write the simulation. It is a great introduction to the Ruby language in addition to being a fun problem to solve.

My first job out of college was at Cape Canaveral working on deep space trajectory programs. It has been many years since I flexed my math muscles in celestial mechanices, so it was kinda fun to see that stuff again. I’ve only read through the second chapter, but I’m eagerly looking forward to the rest.

If you are looking to learn Ruby and aren’t frightened by a bit of math (well, perhaps more than a bit), you might enjoy this.


comments

Rake Tutorial -- Handling Common Actions   05 Apr 05
[ print link all ]

Rake is a tool for controlling builds. In this part of the Rake tutorial, we see how to organize the Rake actions to apply to many similar tasks.

In the RakeTutorialIntroduction, we talked about the basics of specifying dependencies and associating actions to build the files. We ended up with a nice Rakefile that built our simple C program, but with some duplication in the build rules.

But First, Some Extra Rake Targets

But before we get into all that, lets add some convience targets to our Rakefile. First of all, it would be nice to have a default target that is invoked when we don’t give any explicit task names to rake. The default target looks like this:

   task :default => ["hello"]

Until now, the only kind of task we have seen in Rake are file tasks. File tasks are knowledgable about time stamps on files. A file task will not execute its action unless the file it represents doesn’t exist, or is older than any of its prerequisites.

A non-file task (or just plain “task”) does not represent the creation of a file. Since there is no timestamp for comparison, non-file tasks always execute their actions (if they have any). Since the default task does not represent a file named “default”, we use a regular non-file task for this purpose. Non-file tasks just use the task keyword (instead of the file keyword).

Here are a couple of other really useful tasks that I almost always include in a Rakefile.

clean:

Remove temporary files created during the build process.

clobber:

Remove all files generated during the build process.

clean tidies up the directories and removes any files that generated as part of the build process, but are not the final goal of the build process. For example, the .o files used to link up the final executable hello program would fall in this category. After the executable program is built, the .o files are no longer needed and will be removed by saying “rake clean”.

clobber is like clean, but even more aggressive. “rake clobber” will remove all files that are not part of the original package. It should return a project to the “just checked out of CVS” state. So it removes the final executable program as well as the files removed by clean.

In fact, these tasks are so common, Rake comes with a predefined library that implements clean and clobber.

But every project is different, how do we specify which files are to be cleaned and clobbered on a per project basis?

The answer is File lists.

File Lists to the Rescue

A file list is simply a list of file names. Since a lot of what Rake does involves files and lists of those files, a file list has some special features to make manipulating file names rather easy.

Suppose you want a list of all the C files in your project. You could add this to your rake file:

  SRC = FileList['*.c']

This will collect all the files ending in ”.c” in the top level directory of your project. File lists understand glob patterns (i.e. things like "*.c") and will find all the matching files.

By the way, no matter where you invoke it, rake always executes in the directory where the Rakefile is found. This keeps your path names consistent without depending on the current directory the user interactive shell.

The clean and clobber tasks use file lists to manage the files to remove. So if we want to clean up all the .o files in a project we could try …

  CLEAN = FileList['*.o']

(CLEAN is the file list associated with the clean task. I’ll let you guess the name of the file list associated with clobber).

The Rakefile So Far …

With the addtion of a few extra tasks, our Rakefile now looks like this. Notice the require ‘rake/clean’ line used to enable the clean and clobber tasks.

  require 'rake/clean'

  CLEAN.include('*.o')
  CLOBBER.include('hello')

  task :default => ["hello"]

  file 'main.o' => ["main.c", "greet.h"] do
    sh "cc -c -o main.o main.c" 
  end

  file 'greet.o' => ['greet.c'] do
    sh "cc -c -o greet.o greet.c" 
  end

  file "hello" => ["main.o", "greet.o"] do
    sh "cc -o hello main.o greet.o" 
  end

Ok, now its time to address the redundant compile commands.

Dynamically Building Tasks

The command to compile the main.c and greet.c files is identical, except for the name of the files involved. The simpliest and most direct way to address the problem is to define the compile task in a loop. Perhaps something like this …

  SRC = FileList['*.c']
  SRC.each do |fn|
    obj = fn.sub(/\.[^.]*$/, '.o')
    file obj  do
      sh "cc -c -o #{obj} #{fn}" 
    end
  end

Just a couple things to note about the above code.

  • The dependencies are not specified. This is a common where we specify the dependents at one place and the actions in another. Rake is smart enough to combine the dependencies with the actions.
  • Although the task was named after the .o (which is, after all, what we want to generate), the file list is defined in terms of the .c files. Why?

The simple reason is that file lists search for file names that exist in the file system. We have no guarantee that the .o files even exist at this point (indeed, the will not after invoking the clean task). The .c are source and will always be there.

Rake Can Automatically Generate Tasks

Defining tasks in a loop is pretty cool, but is really not needed in a number of simple cases. Rake can automatically generate file based tasks according to some simple pattern matching rules.

For example, we can capture the above logic in a single rule … no need to find all the source files and iterate through them.

  rule '.o' => '.c' do |t|
    sh "cc -c -o #{t.name} #{t.source}" 
  end

The above rule says that if you want to generate a file ending in .o, then you if you have a file with the same base name, but ending in .c, then you can generate the .o from the .c.

t.name is the name of the task, and in file based tasks will be the name of the file we are trying to generate. t.source is the name of the source file, i.e. the one that matches the second have of the rule pattern. t.source is only valid in the body of a rule.

Rules are actually much more flexible than you are led to believe here. But that’s an advanced topic that we will save for another day.

Final Rakefile

Here is our final resule. Notice how we use the SRC and OBJ file lists to manage our lists of scource files and object files.

  require 'rake/clean'

  CLEAN.include('*.o')
  CLOBBER.include('hello')

  task :default => ["hello"]

  SRC = FileList['*.c']
  OBJ = SRC.ext('o')

  rule '.o' => '.c' do |t|
    sh "cc -c -o #{t.name} #{t.source}" 
  end

  file "hello" => OBJ do
    sh "cc -o hello #{OBJ}" 
  end

  # File dependencies go here ...
  file 'main.o' => ['main.c', 'greet.h']
  file 'greet.o' => ['greet.c']

Up Next

In our next tutorial, we will look at using Rake to handle some tasks other than compiling C code.


comments

Rake Tutorial -- Getting Started   03 Apr 05
[ print link all ]
Received via EMail:
I have just started using the excellent Rake tool (thanks, Jim!) and I am at a bit of a loss on how to proceed. I am attempting to create unit test for some C++ code I am creating, […]
Several people recently have made similar comments, they really like rake, but have had trouble getting started. Although the Rake documentation is fairly complete, it really does assume you are familiar with other build tools such as ant and make. It is not really material for the newbie.

To adderess this lack, I’m going to post several Rake tutorial articles that will take you through some of the basics. Eventually, I’ll organize the articles into a document somewhere.

Here’s the first one!

The Problem

We will start with a very simple build problem, the type of problem that make (and now rake) were desiged to deal with.

Suppose I am a C programmer and I have a simple C program consisting of the following files.

main.c

  #include "greet.h"
  int main() {
      greet ("World");
      return 0;
  }

greet.h

  extern void greet(const char * who);

greet.c

  #include <stdio.h>
  void greet (const char * who) {
      printf ("Hello, %s\n", who);
  }

(Yes, it really is the old standard "Hello, World" program. I did say we were starting with the basics!)

To compile and run this collection of files, a simple shell script like the following is adequate.

build.sh

  cc -c -o main.o main.c
  cc -c -o greet.o greet.c
  cc -o hello main.o greet.o
For those not familiar with compiling C code, the cc command is the C compiler. It generates an output file (specified by the -o flag) from the source files listed on the command line.

Running it gives us the following results …

  $ build.sh
  $ ./hello
  Hello, World

Building C Programs

Compiling C programs is really a two step process. First you compile all the source code file into object files. Then you take all the object files and link them together to make the executable.

The following figure illustrates the progression from source files to object files to executable program.

Our program is so small that there is little benefit in doing more than the three line build script above. However, as projects grow, there are more and more source files and object files to manage. Recompiling everything for a simple one line change in a single source file gets old quickly. It is much more efficient to just recompile the few files that change and then relink.

But how do we know what to recompile? Keeping track of that would be quite error prone if we tried to do that by hand. Here is where Rake become useful.

File Dependencies

First, lets take a look at when files need to be recompiled. Consider the main.o. Obviously if the main.c file changes, then we need to rebuild main.o. But are the other files that can trigger a recompile of main.o?

Actually, yes. Looking at the source of main.c, we see that it includes the header file greet.h. That means any changes in greet.h could possibly effect the main.o file as well.

We say that main.o has a dependency on the files main.c and greet.h. We can capture this dependency in Rake with the following line:

  file "main.o" => ["main.c", "greet.h"]

The rake dependency declaration is just regular Ruby code. We take advantage of the fact that we can construct hash arguments on the fly, and that Ruby doesn’t require parenthesis around the method arguement to create a file task declaration that reads very naturally to the humans reading the rake file. But its still just Ruby code.

Likewise, we can declare the dependencies for creating the "greet.o" file as well.

  file "greet.o" => ["greet.c"]

greet.c does include stdio.h, but since that is a system header file and not subject to change (often), we can leave it out of the dependency list.

Finally we can declare the dependencies for the executable program hello. It just depends on the two object files.

  file "hello" => ["main.o", "greet.o"]

Notice that we only have to declare the direct dependencies of hello. Yes, hello depends on main.o which in turn depends on main.c. But the .c files are not directly used in building hello, so they can safely be omitted from the list.

Building the Files

We have carefully specified how the files are related. Now we need to say what Rake would have to do to build the files when needed.

This part is pretty simple. The three line build script that we started with contains all the commands needed to build the program. We just need to put those actions with the right set of dependencies. Use a Ruby do / end block to capture actions …

The result looks like this:

Rakefile

  file 'main.o' => ["main.c", "greet.h"] do
    sh "cc -c -o main.o main.c"
  end

  file 'greet.o' => ['greet.c'] do
    sh "cc -c -o greet.o greet.c"
  end

  file "hello" => ["main.o", "greet.o"] do
    sh "cc -o hello main.o greet.o"
  end

Trying it out

So, let’s see if it works!

  $ rake hello
  (in /home/jim/pgm/rake/intro)
  cc -c -o main.o main.c
  cc -c -o greet.o greet.c
  cc -o hello main.o greet.o

The command line rake hello instructs rake to look through its list of tasks and find one called "hello". It then checks hello’s dependencies and builds them if required. Finally, when everything is ready it builds hello by executing the C compiler command.

Rake dutifully reports what it is doing as it goes along. We can see that each compiler invocation is done in the correct order, building the main program at the end. So, does the program work? Let’s find out.

  $ ./hello
  Hello, World

Success!

But what happens when we change a file. Lets change the greet function in greet.c to print "Hi" instead of hello.

  $ xemacs greet.c
  $ rake hello
  (in /home/jim/pgm/rake/intro)
  cc -c -o greet.o greet.c
  cc -o hello main.o greet.o
  $
  $ ./hello
  Hi, World

Notice that it recompiles greet.c making a new greet.o. And then it needs to relink hello with the new greet.o. Then it is done. There is no need to recompile main.c since it never changed.

What do you think will happend if we run Rake again?

  $ rake hello
  (in /home/jim/pgm/rake/intro)
  $

That’s right … nothing. Everything is up to date with its dependencies, so there is no work for Rake to do.

Ok, sure. Rake is a bit of overkill for only two source files and a header. But imagine a large project with hundreds of files and dependencies. All of a sudden, a tool like Rake becomes very attractive.

Summary

What have we learned? Building a Rakefile involves identifying dependencies and the actions required to create the target files. Then declaring the dependencies and actions are as simple as writing them down in standard Ruby code. Rake then handles the details of building

What’s Up Next

We notice that even our small example has a bit of duplication in it. We have specify how to compile both C file separately, even though the only difference is the files that are used. The next installment will look at fixing that problem as well as introduce non-file based tasks, rules and file lists.

Until then … Code Red, Code Ruby.


comments

Rake 0.5.0 Released   26 Mar 05
[ print link all ]
It has been a long time in coming, but we finally have a new version of Rake available.

Changes

  • Fixed bug where missing intermediate file dependencies could cause an abort with —trace or —dry-run. (Brian Chandler)
  • Recursive rules are now supported (Tilman Sauerbeck).
  • Added tar.gz and tar.bz2 support to package task (Tilman Sauerbeck).
  • Added warning option for the Test Task (requested by Eric Hodel).
  • The jamis rdoc template is only used if it exists.
  • Added fix for Ruby 1.8.2 test/unit and rails problem.
  • Added contributed rake man file. (Jani Monoses)
  • Fixed documentation that was lacking the Rake module name (Tilman Sauerbeck).

What is Rake

Rake is a build tool similar to the make program in many ways. But instead of cryptic make recipes, Rake uses standard Ruby code to declare tasks and dependencies. You have the full power of a modern scripting language built right into your build tool.

Availability

The easiest way to get and install rake is via RubyGems …

  gem install rake    (you may need root/admin privileges)

Otherwise, you can get it from the more traditional places:

Home Page:rake.rubyforge.org/
Download:rubyforge.org/project/showfiles.php?group_id=50

Thanks

Lots of people provided input to this release. Thanks to Tilman Sauerbeck for numerous patches, documentation fixes and suggestions. And for also pushing me to get this release out. Also, thanks to Brian Chandler for the finding and fixing —trace/dry-run fix. That was an obscure bug. Also to Eric Hodel for some good suggestions.

— Jim Weirich


comments

Web Programming with Continuations   25 Mar 05
[ print link all ]
An easy introduction to a mind bending technique.

Playing with continuations is great fun, but thinking deeply about them does tend to stretch the brain cells. If you always wanted to understand continuations better, but found them a bit hard to comprehend, Mikael Brockman has written a wonderful little introduction on the topic (via Tim Bray).

If you are interested in real world uses of continuations, see SeaSide and Borges.


comments

Running Back Versions of Gem-installed Applications   04 Mar 05
[ print link all ]
I keep forgetting that RubyGems has this capability. In this example, I have 7 (yes, seven, really) versions of rake installed.
  $ gem list --local rake

  *** LOCAL GEMS ***

  rake (0.4.15.1, 0.4.15, 0.4.14, 0.4.13, 0.4.12.1, 0.4.12, 0.4.4)
      Ruby based make-like utility.
  $
  $ rake --version
  rake, version 0.4.15.1
  $
  $ rake _0.4.12_ --version
  rake, version 0.4.12

Since rake is installed as a gem, you can run the executable of any installed version. By default, gems will run the latest one. But if you wish to run an older version, gems will accomodate you.


comments

XP Cincy Looks at Ruby   10 Feb 05
[ print link all ]
So, what happened when the Cincinnati XP Users group started looking at Ruby? Here's a quick report on the evening (with pictures!). See XpCincyAndRuby for background information.

XP and Ruby

As I mentioned in XpCincyAndRuby, the Cincinnati XP Users Group is all set to take a look at Ruby this month.



Mark Windholtz got us started with introductions (we had some newcomers to the group!) and laid out the plan for the evening.



We thought we would start off with something a bit fun, and something that would allow us to get into Ruby fairly quickly. I had adapted the "Paper, Rock, Scissors" RubyQuiz of the week before into something that could be run over DRb. The goal was to run Paper/Rock/Scissors players on our laptops, coordinating with a central game server (using DRb and Rinda).



The result was quite fun. As you can see, at least one of the programs was quite explicit about the state of its own playing ability (click to enlarge the pictures).



Mystery Guest

We did have one guest who was obviously a bit camera shy. Must have something to do with the project he is currently working on. It seems Chris has reached some good milestones recently and had time to come over and check out the Rails crowd. Welcome Chris! And don't worry, we won't tell any of your Java friends (wink, wink).


After the Meeting

Afterwards, we all headed to Unos and enjoyed some appetizers and drinks. This is where all the important discussions happen.

Next month we will be diving into Rails itself. If you are in the Cincinnati area, feel free to stop by and join us. We meet the first Tuesday of every Month.

Cheers!


comments

Web Applications   10 Feb 05
[ print link all ]
What’s up with all the cool web apps lately? Here’s some more that I ran across in the past few days.
TagSurf (tagsurf.com)
I’m not sure how to describe this. It is kinda like a web forum with tags applied to each message. What makes it really interesting is that if you use a URL for a tag, then the posting is "about" that web page. So, if you wish to talk about this posting (yes, the one you are reading right now), then create a message with the link onestepback.org/index.cgi/Tech/Web/MoreWebApps.rdoc as a tag. Or, even easier, click the "tagsurf-it" link in the article banner above and be taken immediately to TagSurf with the tags prefilled and ready to go. (I’ve also added a TagSurf link below marked for feedback). Give it a try.

Yes, you will have to sign up for TagSurf, but it is free. It is also very alpha, so be warned. But it is definitely cool. Read about how TagSurf came to be at Russell Beattie's Blog.

Google Maps: (maps.google.com/)
Try it, you’ll like it!

Nuff said.

Ta-Da Lists: (tadalists.com)
I’ve mentioned Ta-Da lists before, but I started using it as a temporary holding area for some ideas for an article on "10 Things Java Programmers Should Know About Ruby". I just dumped suggestions on to the list with minimal editting. Then I made the list public so that folks could see what had already been suggested.

The list wasn’t up two days before it was noticed by some Python guy who picked up on the "Fixes what’s wrong in Python" link (ack … exactly the message I was not trying to send, and it wasn’t even aimed at Python). Then a Smalltalk guy (hi Avi!) blogs about it. I’ve gotten more attention on this article (which I haven’t written yet) than most articles I actually do write.

Part of the problem was that there was no place in the Ta-Da list to explain its purpose. It was just a raw list of items without any context. Given the title, I can see why some folks assumed it was the finished product.

So I bemoaned the problem of no list description in Ta-Da. And then today, as I was editing another list, I noticed that the Ta-Da lists now do have a description field. Wow! Did I miss that? I would have sworn it wasn’t there before. Perhaps did the Ta-Da folk read my mind and knew exactly what I wanted. Anyways, I’m impressed. Thanks guys.

So, anyways, my apologies to the Python crowd. The "10 Things …" list was never targeted at Python and was an unedited compilation of feedback I had received. As Ian (the Python guy) points out a comment …

For Python and Ruby to try to take users from each other is a losing game — we’re both small players, and there’s much better and bigger pools of developers we should be trying to attract.

Ok, that’s enough for this rather rambling post.

Code Red

Code Ruby (or Python).


comments

Cheating at Computer Games   27 Jan 05
[ print link all ]
One of the most enjoyable programming exercises is writing a computer program that plays a game.

Paper Rock Scissors …

The weeks Ruby quiz involves writing a simple program to play the game "Paper, Rock, Scissors". Soon after the comments started on the quiz, someone submitted this 12 line "cheating" program:

  class Cheater < Player
    def initialize opponent
      Object.const_get(opponent).send :define_method, :choose do
        :paper
      end
    end

    def choose
      :scissors
    end
  end

It cheats in that it dynamically modifies its opponent object so that it always returns :paper, making it trivial to beat. While it does cheat, it does show some clever "thinking outside the box" approach to playing Paper, Rock, Scissors.

This reminds me of a story …

Greed

A number of years ago, I used to teach C++ in GE’s after hours education program. One of the projects assigned to the class was writing a player for the game of Greed. Greed is a simple dice game where you score points by rolling a set of 5 dice. You can continue to roll as long as you continue to make points on each roll. However, a row that has no point value causes you to lose all the points for a turn, so there is some value in stopping while you are ahead (hence the name Greed).

The game framework handles the dice rolling. All the student’s prgram had to do was decide whether or not to roll again given the the current state of the dice.

As part of the course, I pointed out that the encapsulation and data hiding features of C++ made it difficult to "cheat" by effecting the framework or other player objects. One particular student took this as a challenge and announced that he had a "cheating" player program that stayed within the "rules" setup by the C++ langauge, but yet would always win.

How did he do it? This cheating player would record the state of the seed of the standard library’s random number generator. It would then do trial rolls of the dice until it got a high scoring roll. Then it would reset the random number seed to the value that produced the high scoring roll, and indicate to the game framework that it wanted to roll again. The framework would comply and give the player the high scoring roll that it was setup to do. The cheating player would win the game in a single turn.

The cheat was easily defeated (once the cheat was understood) by using a private random number generator that wasn’t accessible to the player programs. But I learned two things from this exercise:

  1. Programmers love challenges, especially the kind that say "You can’t do this …"
  2. Secure software is hard to create, particularly because of point 1.


comments

Statically Typed Closures In Groovy   21 Jan 05
[ print link all ]

Closures in Groovy

There is an interesting debate going on about closures in the Groovy language. You can catch up on the discussion here:

For those who don’t want to read the whole conversation, I’ll summarize the bit I wish to address. Mike is advocating simplification of the Groovy language syntax to remove ambiguities and make have special cases. An admirable goal, no doubt.

In addition revoking the optional parenthesis and semi-colons currently sported by Groovy, Mike wants to make the closure syntax more explicit. Currently closures in Groovy look very similar to blocks in Ruby.

   list.each { item | println(item) }

Mike would like to see it changed to …

   list.each (clos(item) { println(item) });

Where clos is now a keyword that introduces the closure and the closure argument list is given explicitly in a way that is more Java-like. One of the reasons he likes this is that it opens the possibility of declaring the types of the arguments to the closure.

Quoting from the third link above:

[…] And for Groovy at least, all closures look alike - two closures which do radically different things and take different parameters can be used interchangably from a static typing perspective. Clashes are only detected at Runtime. By comparison an AIC is much more verbose in its definition, but it is statically typed, so you can’t use two AICs created against different interfaces interchangably. Dynamic typers will scoff at this, but in larger programs being able to define a definite type, parameters, and return type can be a huge boon. […]

Wow, a "huge boon". Let’s see how this boon works out in practice.

Static Closure Types

We will start this Mike’s suggested syntax add a return type declaration. This gives us the ability to declare closures like this:

  # A closure that takes an integer and returns an integer value.
  clos(int i):int { i + 1 }

  # A closure that takes a string and returns a integer.
  clos(String s):int { s.length() }

  # A closure that takes a string and returns no value
  clos(String s):void { println(s) }

I think you get the idea.

Note:
I’m using vaguely Groovy like syntax here, modified with a variation of Mike’s suggested closure syntax. I am not a Groovy expert and may get some details wrong. However, I don’t want to get hung up on the syntax here, but want to concentrate on the ideas behind them. So bear with me. Thanks.

For static typing to work, we need the ability to declare variables of a given closure type. Here we declare three variables that match the types of the three closures above.

  clos(int):int     integer_func;
  clos(String):int  string_func;
  clos(String):void handler;

Given the above declarations, we should be able to do the following:

  integer_func = clos(int i):int { i + 1 }
  string_func  = clos(String s):int { s.length() }
  handler      = clos(String s):void { println(s) }

In all cases, the type of the closure exactly matches the declared type of the variable. We will be able to invoke the closures and be certain that there will be no runtime type errors, which is certainly the goal of static typing.

Relaxing the Exact Match Rule

Consider the following …

  interface Animal { void talk(); }
  class Cat implements Animal { void talk() { println("Meow") }

Should the following assignment be allowed?

  clos(Cat):void cat_handler
  cat_handler = clos(Animal a):void { a.talk(); }

Let’s think about this. The closure can handle any type of Animal. The closure variable cat_handler is declared in such a way that only Cat objects can be passed to the closure referenced by cat_handler. Since Cats are Animals, this should cause no type errors at run time.

One more example, then we well summarize the rules.

  clos():Animal factory;
  factory = clos():Cat { new Cat() }

Again, this looks to be type safe. Closures called through the factory variable are guaranteed to return Animals. The closure in question returns a Cat object, which is certainly an Animal. This will cause no type errors at run time.

Summarizing the Closure Compatibility Rules

We can summarize the rules for assigning closures to closure variables as follows:

A closure can be assigned to a closure variable when:

  1. The closure and the closure variable take the same number of arguments, and
  2. the values passed in the argument list given to the closure variable can be assigned to the formal parameters of the closure, and
  3. the return type of the closure can be assigned to a variable of the return type of the closure variable.

The above language is way too informal for a real language definition, but I’m trying to get at understanding rather than exact semantics. The rules should do for our purposes.

A Real Life Example

We now have enough to try some real life examples. Consider the following transform function that takes a list and returns a new list built from an arbitrary transformation on each element. The transform is specified by a statically typed closure.

  List transform(List list, clos(Object):Object transformation) {
    List result = [];
    for (Object current_obj in list) {
      Object new_obj = transformation(current_obj);
      result.append(new_obj);
    }
    return result;
  }

Our closure is specified in terms of Object because we want it to work on any kind of list.

How would we use it? We might like to do the following:

  clos(int):int t = clos(int i):int { i + 1 };
  transform([1,2,3], t);

Oops! This doesn’t work. The closure t is not compatible with the closure needed by the transform function (it breaks rule 2 above). The closure t only takes arguments of type int, but transform could possibly pass any type to t.

Ok, it is pretty disappointing that the direct approach does not work. So let’s try something else. What if we tried this.

  clos(Object):int u = clos(Object obj):int { ((int)obj) + 1 };
  transform([1,2,3], u);

This works! But at what cost? Are you bothered that we needed a cast to get our closure to work properly? You probably should be, for what we have done is lie to the compiler, telling it our closure takes an arbitrary object when in fact it must have an integer (or something that can be converted to an integer).

If fact, we can now write code that is statically type valid, but fails at run time (with a class cast exception):

   u("hello")

If our statically typed closures can’t lead to code that is runtime type-safe, then what is the advantage of bothering with static declarations. We might as well stay with the dynamically typed closures that offer the same amount of type safety and are much less complex.

The Example Was Rigged!

Well, maybe. The problem lies in trying to use statically typed closures with functions that take generic arguments. But isn’t that exactly the situation where static type safety is most needed? If statically declared closures can’t handle the hard problems, why bother with them on the easy ones?

Can We Fix This?

Perhaps. I believe it is possible to define statically typed closures. In fact, this article lays out how to do it in Eiffel (the agents described in the article are essentially closures). But the Eiffel language supports generics (and in particular generic Tuples) to work around the problems outlined here. Adding that to the language makes it even more complicated.

Summary

At first glance, statically typed generics look really attractive, but add a great deal of complexity without achieving static type safety for a very common class of problems where closures are commonly used. I just don’t see the advantage.

Mike Spille says "On typing, it’s a double-edged sword if you’re coming from a Java perspective.", and he is right. And you really have to be careful not to cut yourself on that sword.



comments

 

Formatted: 19-May-13 13:15
Feedback: jim@weirichhouse.org