| 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:
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:
- Programmers love challenges, especially the kind that say "You
can’t do this …"
- 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:
- The closure and the closure variable take the same number of arguments,
and
- the values passed in the argument list given to the closure variable
can be assigned to the formal parameters of the closure, and
- 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
|
| Pennsylvania Dutch
|
|
20 Jan 05 |
|
[ print
link
all
] |
Eckel on Ruby
Recently Bruce Eckel addresses
the "Ruby Issue" yet again. Evidently a number of years ago Bruce
brushed off Ruby based on a quick perusual of a Ruby book. Ever since then,
the Ruby community has been trying to convert him, finding it hard to
believe that someone as intelligent and well spoken as Bruce didn’t
immediately fall in love with our little language.
In the article, he says:
-
- Look at one of the first examples from "Why’s Poigniant
Guide," where he’s asserting that Ruby is "the language of
our thoughts":
5.times { print "Odelay!" }
Or this:
exit unless "restaurant".include? "aura"
This makes sense if you used to be a Smalltalk (or perhaps Forth)
programmer, and I know one who started with Python and has moved to Ruby.
It also makes sense if you grew up Pennsylvania Dutch, where they say
things like "Throw Papa down the stairs his hat," and "Throw
the horse over the fence some hay."
Well that explains everything! You see, my father was Amish (until he was about
13 years old) and my mother’s parents were Amish. The Amish speak
Pennsylvania Dutch (a distant dialect of German) in their homes and use
High German in their church services. Often Amish children will enter first
grade before they learn to speak english.
So I grew up in a community where Pennsylvania Dutch was often spoken, and
influenced the way english was spoken. This "backwards" way of
speaking sounds very familiar to me.
No wonder I am so attracted to the Ruby language.
An Aside
I do find it humorous that Bruce is (mildly) poking fun at the Ruby (and
Perl) technique of using if and unless as statement
modifiers. Bruce makes it sound as if putting the if condition at the end
of a statement (e.g. return if data.nil?) is somehow awkward
english.
Now go reread the first two sentences of the paragraph that begins
"This makes sense if …".
Interesting.
Coming Around?
Actually, from the tone of the article (and some of the later comments), I
get the feeling that Bruce respects that fact that a lot of people find
Ruby attractive and productive.
Its just that it doesn’t click for him.
That’s OK. As my Amish relatives would say, "It takes all
kinds, the world to make".
Increase Your Ruby Skills
If you really want to increase your Ruby skills, I recommend that you spend
a good part of the day listening to John Schmid
sing a few Amish folk songs. My favorite is "Maydly vit du
hayra?" where the Amish father queries his young daughter on the
type of man she wants to marry. I actually remember the song
"Reide, Reide, Geile" (Ride, Ride the Horse) sung in my
house while growing up (usually as we were bouncing on Dad’s leg).
Good Stuff!
comments
|
|
|