forked from dyoder/functor
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Dan Yoder
committed
Jun 7, 2008
0 parents
commit b8e904e
Showing
9 changed files
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
require 'rubygems' | ||
require 'date' | ||
require 'rake/rdoctask' | ||
require 'rake/testtask' | ||
require 'fileutils' | ||
|
||
Gem::manage_gems | ||
include FileUtils | ||
|
||
require 'rake/gempackagetask' | ||
|
||
spec = Gem::Specification.new do |s| | ||
s.platform = Gem::Platform::RUBY | ||
s.required_ruby_version = '>= 1.8.6' | ||
s.name = "functor" | ||
# s.rubyforge_project = 'autocode' | ||
s.version = "0.2" | ||
s.authors = ["Dan Yoder"] | ||
s.email = 'dan@zeraweb.com' | ||
s.homepage = 'http://dev.zeraweb.com/' | ||
s.summary = "Pattern-based dispatch for Ruby, inspired by Topher Cyll's multi." | ||
s.files = Dir['*/**/*'] | ||
#s.bindir = 'bin' | ||
#s.executables = [] | ||
s.require_path = "lib" | ||
s.has_rdoc = true | ||
end | ||
|
||
task :package => :clean do | ||
Gem::Builder.new(spec).build | ||
end | ||
|
||
task :clean do | ||
system 'rm -rf *.gem' | ||
end | ||
|
||
task :install => :package do | ||
system 'sudo gem install ./*.gem' | ||
end | ||
|
||
desc "create .gemspec file (useful for github)" | ||
task :gemspec do | ||
filename = "#{spec.name}.gemspec" | ||
File.open(filename, "w") do |f| | ||
f.puts spec.to_ruby | ||
end | ||
end | ||
|
||
#desc 'Publish rdoc to RubyForge.' | ||
#task :publish do | ||
# # `rsync -avz --delete doc/rdoc/ dyoder67@rubyforge.org:/var/www/gforge-projects/autocode/` | ||
# `scp -r doc/rdoc dyoder67@rubyforge.org:/var/www/gforge-projects/autocode/` | ||
#end | ||
|
||
Rake::RDocTask.new do |rdoc| | ||
rdoc.rdoc_dir = 'doc/rdoc' | ||
rdoc.options << '--line-numbers' | ||
rdoc.rdoc_files.add([ 'README', 'HISTORY', 'lib/*.rb' ]) | ||
end | ||
|
||
Rake::TestTask.new(:test) do |t| | ||
t.test_files = FileList["test/*.rb"].exclude("test/helpers.rb") | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
0.1 - Initial implemention of Functor class. | ||
0.2 - Added method dispatch, to_proc support, tests. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
Functor provides pattern-based function and method dispatch for Ruby. To use it in a class: | ||
|
||
class Repeater | ||
attr_accessor :times | ||
include Functor::Method | ||
functor( :repeat, Integer ) { |x| x * @times } | ||
functor( :repeat, String ) { |s| [].fill( s, 0..@times ).join(' ') } | ||
end | ||
|
||
r = Repeater.new | ||
r.times = 5 | ||
r.repeat( 5 ) # => 25 | ||
r.repeat( "-" ) # => "- - - - -" | ||
r.repeat( 7.3 ) # => RuntimeError! | ||
|
||
Warning: This defines a class instance variable @functors behind the scenes as a side-effect. Also, functors won't inherit properly at this point. | ||
|
||
You can also define Functor objects directly: | ||
|
||
fib ||= Functor.new do | ||
given( 0 ) { 0 } | ||
given( 1 ) { 1 } | ||
given( Integer ) { |n| self.call( n - 1 ) + self.call( n - 2 ) } | ||
end | ||
|
||
You can use functors directly with functions taking a block like this: | ||
|
||
[ *0..10 ].map( &fib ) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] | ||
|
||
You can explicitly bind self using #bind: | ||
|
||
fun.bind( obj ) | ||
|
||
which is actually how the method dispatch is implemented. | ||
|
||
Arguments are matched first using === and then ==, so anything that supports these methods can be matched against. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
Gem::Specification.new do |s| | ||
s.name = %q{functor} | ||
s.version = "0.2" | ||
|
||
s.specification_version = 2 if s.respond_to? :specification_version= | ||
|
||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= | ||
s.authors = ["Dan Yoder"] | ||
s.date = %q{2008-06-07} | ||
s.email = %q{dan@zeraweb.com} | ||
s.files = ["doc/HISTORY", "doc/README", "lib/functor.rb", "lib/object.rb", "test/fib.rb", "test/functor.rb", "test/helpers.rb"] | ||
s.has_rdoc = true | ||
s.homepage = %q{http://dev.zeraweb.com/} | ||
s.require_paths = ["lib"] | ||
s.required_ruby_version = Gem::Requirement.new(">= 1.8.6") | ||
s.rubygems_version = %q{1.0.1} | ||
s.summary = %q{Pattern-based dispatch for Ruby, inspired by Topher Cyll's multi.} | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
require 'lib/object' | ||
class Functor | ||
|
||
module Method | ||
|
||
def self.included( k ) | ||
k.module_eval { @functors = {} } | ||
def k.functor( name, *args, &block ) | ||
unless @functors[name] | ||
@functors[name] = Functor.new | ||
eval("def #{name}( *args, &block ) ; self.class.module_eval { @functors[ :#{name} ] }.bind( self ).call( *args, &block ) ; end" ) | ||
end | ||
@functors[name].given( *args, &block ) | ||
end | ||
end | ||
end | ||
|
||
def initialize( &block ) ; @patterns = []; instance_eval(&block) if block_given? ; end | ||
|
||
def given( *pattern, &block ) ; @patterns.push [ pattern, block ] ; self ; end | ||
|
||
def bind( object ) ; @object = object ; self ; end | ||
|
||
def call( *args, &block ) | ||
args.push( block ) if block_given? | ||
pattern, action = @patterns.find { |pattern, action| match?( args, pattern ) } | ||
raise "argument mismatch for argument(s): #{args.inspect}." unless action | ||
@object ? @object.instance_exec( *args, &action ) : action.call( *args ) | ||
end | ||
|
||
def to_proc | ||
lambda { |*args| self.call(*args) } | ||
end | ||
|
||
private | ||
|
||
def match?( args, pattern ) | ||
pattern.zip(args).all? { |x,y| x === y or x == y } if pattern.length == args.length | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
class Object | ||
# This is an extremely powerful little function that will be built-in to Ruby 1.9. | ||
# This version is from Mauricio Fernandez via ruby-talk. Works like instance_eval | ||
# except that you can pass parameters to the block. This means you can define a block | ||
# intended for use with instance_eval, pass it to another method, which can then | ||
# invoke with parameters. This is used quite a bit by the Waves::Mapping code. | ||
def instance_exec(*args, &block) | ||
mname = "__instance_exec_#{Thread.current.object_id.abs}" | ||
class << self; self end.class_eval{ define_method(mname, &block) } | ||
begin | ||
ret = send(mname, *args) | ||
ensure | ||
class << self; self end.class_eval{ undef_method(mname) } rescue nil | ||
end | ||
ret | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
require 'test/helpers' | ||
|
||
fib ||= Functor.new do | ||
given( 0 ) { 0 } | ||
given( 1 ) { 1 } | ||
given( Integer ) { |n| self.call( n - 1 ) + self.call( n - 2 ) } | ||
end | ||
|
||
describe "Dispatch on a functor object should" do | ||
|
||
specify "be able to implement the Fibonacci function" do | ||
[*0..10].map( &fib ).should == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] | ||
end | ||
|
||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
require 'test/helpers' | ||
|
||
class Repeater | ||
attr_accessor :times | ||
include Functor::Method | ||
functor( :repeat, Integer ) { |x| x * @times } | ||
functor( :repeat, String ) { |s| [].fill( s, 0, @times ).join(' ') } | ||
end | ||
|
||
describe "Dispatch on instance method should" do | ||
|
||
before do | ||
@r = Repeater.new | ||
@r.times = 5 | ||
end | ||
|
||
specify "invoke different methods with object scope based on arguments" do | ||
@r.repeat( 5 ).should == 25 | ||
@r.repeat( "-" ).should == '- - - - -' | ||
end | ||
|
||
specify "should raise an exception if there is no matching value" do | ||
lambda { @r.repeat( 7.3) }.should.raise | ||
end | ||
|
||
end | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
require 'rubygems' | ||
%w{ bacon }.each { |dep| require dep } | ||
Bacon.summary_on_exit | ||
|
||
module Kernel | ||
private | ||
def specification(name, &block) Bacon::Context.new(name, &block) end | ||
end | ||
|
||
Bacon::Context.instance_eval do | ||
alias_method :specify, :it | ||
end | ||
|
||
require 'lib/functor' |