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: * No type or interface declarations to make the compiler happy * List and hashtable literals * Heterogeneous lists (arrays in the example, to be precise) * Embedded DSLs * Dead easy dynamic code loading, no file compilation step needed * Late binding. The interpreter is happy to run the code even if some parts are not type correct. 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 $