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 <stdio.h>
    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
    $


EditText of this page (last edited April 9, 2010) or FindPage with title or text search