Dynamic Typing Example Code

Some people deny any usefulness of dynamic typing. This little program below is my attempt to show that dynamic typing can be a big help to get working code as fast as possible. Nothing more. This is especially not an argument that dynamic typing is universally "better" than static typing.

It's a minimal build tool, essentially how the Rake and Rant programs (both can be found on http://rubyforge.org) started out. It's written in Ruby, but I'm sure a straightforward translation to Python, Perl and Lisp is possible.

It shows the following features/characteristics usually found in dynamic languages:

All of the points listed above have advantages and disadvantages, but they are all very handy for prototyping.

#!/usr/bin/env ruby
class Task
def self.valid_name?(name)
name.kind_of?(Symbol)
end
attr_reader :name
# Array of symbols and strings. A symbol names a regular task
# whereas a string names a file (for which a FileTask may exist).
attr_reader :prerequisites
def initialize(name, prerequisites, block)
@name = name
@prerequisites = prerequisites
@done = nil
@block = block
end
def done?
@done
end
def run
@block.call(self) if @block
@done = true
end
def uptodate?
done?
end
end
class FileTask < Task
def self.valid_name?(name)
name.kind_of?(String)
end
def uptodate?
done? or
File.exist?(name) &&
prerequisites.all? { |pre|
File.mtime(name) >= File.mtime(pre)
}
end
end
class Build
attr_reader :tasks
def initialize
@tasks = {}
end
def load_build_file(filename)
instance_eval(File.read(filename), filename)
end
def make(taskname)
t = @tasks[taskname]
if t.nil?
if taskname.kind_of?(String)
unless File.exist? taskname
raise "Don't know how to make file #{taskname}."
end
else
raise "No such task: #{taskname}"
end
else
t.prerequisites.each { |pre| make pre }
t.run unless t.uptodate?
end
end
def task_(klass, args, &block)
case args
when Symbol, String:
name = args
prerequisites = []
when Hash:
name = args.keys.first
prerequisites = args.values.first
unless args.size == 1 && prerequisites.kind_of?(Array)
raise "Invalid task argument syntax."
end
else raise "task syntax error"
end
raise "Invalid name #{name.inspect}." unless klass.valid_name? name
@tasks[name] = klass.new(name, prerequisites, block)
end
def task(*args, &block)
task_(Task, *args, &block)
end
def file(*args, &block)
task_(FileTask, *args, &block)
end
def sh(cmd)
puts cmd
system cmd or raise "command failure"
end
end
if $0 == __FILE__
build = Build.new
build.load_build_file "makefile.rb"
taskname = ARGV[0] || :default
taskname = taskname.to_sym if build.tasks[taskname.to_sym]
build.make(taskname)
end

To try the code, save it in a file called make.rb and set the executable bit. An example build description (save it in a file called makefile.rb) is below:

require "fileutils"
task :default => ["hello"]
file "hello" => ["hello.c"] do |t|
sh "cc -o #{t.name} #{t.prerequisites.join(' ')}"
end
task :rebuild => [:clean, "hello"]
task :clean do
FileUtils::Verbose.rm_f "hello"
end

A shell session might look like:

$ cat hello.c
#include
int main() {
printf("Hello, world!\n");
return 0;
}
$ ./make.rb
cc -o hello hello.c
$ ./make.rb
$ touch hello.c
$ ./make.rb
cc -o hello hello.c
$ ./make.rb rebuild
rm -f hello
cc -o hello hello.c
$