defmatch 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 050ab21f9d8461b8945a213a2f454fda069a44b2
4
+ data.tar.gz: 21be01216d2b525b3451436bdd920170b3d51480
5
+ SHA512:
6
+ metadata.gz: c1bdc6c310cc0a49cb809d64ed77dfa9073ac55124e4b4adc365ec5608f222ffea9558c20ba40225552895e08a60f99064e1db9ecec8d1741be4014b23a6eff8
7
+ data.tar.gz: 8f65bddf87a83e0bc890adac30dd073118de2bc66afebbb622b9ab506f4a3f54fef64cc243f22bb191936ca558949a3b9d10698086621e9f8668286fb848eb41
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in defmatch.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Max Warnock
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Defmatch
2
+
3
+ Switching between erlang and ruby a fair amount has me missing erlang's function definition features. Particularly dispatching based on pattern matching. In erlang it's common to write a function that handles both a single item or a list of those items like this:
4
+
5
+ ```erlang
6
+ times_two(List) when is_list(List) ->
7
+ [times_two(I) || I <- List]; %% This is a basic list comprehension (think collect if you're a ruby person)
8
+ times_two(I) when is_number(I) ->
9
+ I * 2.
10
+
11
+ times_two(4). % Returns 8
12
+ times_two([1,2,3,4]). % Returns [2,4,6,8]
13
+ times_two("asdf"). % Throws a bad match (function clause) error
14
+ ```
15
+
16
+ To do the same type of operation in ruby I'd have to write something like this:
17
+
18
+ ```ruby
19
+ def times_two(number_or_list)
20
+ if number_or_list.class == Fixnum
21
+ number_or_list * 2
22
+ elsif number_or_list.class == Array
23
+ number_or_list.collect(&:times_two)
24
+ else
25
+ throw "Not a valid argument type"
26
+ end
27
+ end
28
+ ```
29
+
30
+ Functionally these two are identical, but from a readability stand point I'd take erlang's version every time; even more so when this type of dispatching gets really complicated.
31
+
32
+ So how would you write the same thing using Defmatch?
33
+
34
+ ```ruby
35
+ class TestMe
36
+ extend(Defmatch)
37
+
38
+ defmatch(:times,Fixnum) {|num| num * 2 }
39
+ defmatch(:times,Array) {|list| list.collect {|i| times(i) } }
40
+ end
41
+
42
+ x = TestMe.new
43
+ x.times(4) # -> 8
44
+ x.times([1,2,3,4]) # -> [2,4,6,8]
45
+ ```
46
+
47
+ ## How does it work and how do I use it?
48
+ Defmatch is written as a module and when it's used to extend a class it creates a ```defmatch``` class method. The ```defmatch``` method takes one required argument as the name of the method you're defining. The remaining arguments are the pattern to match on when the method you're calling that method. Those arguments can be classes, literals, or procedures (lambdas). It also takes a block which is the actual function body that will run when the pattern matches. Those with a java background think method overloading, but more powerful. Those with an erlang background will feel right at home. But here are some concrete examples.
49
+
50
+ ```ruby
51
+ class TestMe
52
+ extend(Defmatch)
53
+
54
+ # Run this function if the argument passed to magic is of type Fixnum
55
+ defmatch(:magic,Fixnum) {|number| "I got a number #{number}" }
56
+ # Run this function if the argument passed to magic is of type Array
57
+ defmatch(:magic,Array) {|a| "I got an Array #{a.inspect}" }
58
+ # Run this function if the argument passed to magic is the symbol :literally
59
+ defmatch(:magic,:literally) {|duh| "This literally matched #{duh}" }
60
+ # Run this function when there are two fixnums passed to magic
61
+ defmatch(:magic,Fixnum,Fixnum) {|a,b| "Found two numbers #{a}:#{b}" }
62
+ # Run this function when there is a single argument that is equal to "banana" (not a great example as this could be done with a literal)
63
+ defmatch(:magic,lambda {|arg| arg == "banana" }) {|arg| "I matched using a procedure that made sure \"banana\" == #{arg}" }
64
+ end
65
+
66
+ #Now you have an instance method called magic that dispatches what runs based on the patterns you defined and their associated block
67
+ x = TestMe.new
68
+ x.magic(10) # -> Matches the first
69
+ x.magic([1,2,3]) # -> Matches the second
70
+ x.magic(:literally) # -> You get the idea
71
+ x.magic(2,3)
72
+ x.magic("banana")
73
+ ```
74
+
75
+ This can come in very handy, but remember that the order in which you define things matters. Lets say I define my magic function like this:
76
+
77
+ ```ruby
78
+ #...
79
+ defmatch(:magic,Fixnum) {|num| num * 2 }
80
+ defmatch(:magic,1) {|num| "I got me a 1" }
81
+ #...
82
+ ```
83
+
84
+ Even if I run ```x.magic(1)``` I will get ```2``` as the result. The second defmatch will never be matched because there is a more general match case above it. Order matters. Define your most specific matches first.
85
+
86
+ ## Roadmap
87
+
88
+ * Add defclassmatch for class methods
89
+ * Add parameter deconstruction
90
+ * Add it to the Kernel so it's available without having to include things. This will require ruby 2.0 and I'm not prepared to kill backwards compatability yet.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ # Default directory to look in is `/specs`
5
+ # Run with `rake spec`
6
+ RSpec::Core::RakeTask.new(:spec) do |task|
7
+ task.rspec_opts = ['--color']
8
+ end
9
+
10
+ task :default => :spec
data/defmatch.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'defmatch/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "defmatch"
8
+ spec.version = Defmatch::VERSION
9
+ spec.authors = ["Max Warnock"]
10
+ spec.email = ["mwarnock@analytical.info"]
11
+ spec.summary = %q{Defmatch provides a method for classes to define and dispatch methods off of pattern matching.}
12
+ spec.description = %q{Switching between erlang and ruby a fair amount has me missing erlang's function definition features. Particularly dispatching based on pattern matching. Defmatch is my way of bringing some of that functionality into ruby.}
13
+ spec.homepage = "http://github.com/mwarnock/defmatch"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "rspec-nc"
25
+ spec.add_development_dependency "guard"
26
+ spec.add_development_dependency "guard-rspec"
27
+ end
@@ -0,0 +1,3 @@
1
+ module Defmatch
2
+ VERSION = "0.0.1"
3
+ end
data/lib/defmatch.rb ADDED
@@ -0,0 +1,41 @@
1
+ module Defmatch
2
+
3
+ def self.signiture_match(method,args)
4
+ tests = args.collect do |arg|
5
+ if arg.class == Proc
6
+ arg
7
+ elsif arg.class == Class
8
+ lambda {|param| param.class == arg }
9
+ else
10
+ lambda {|param| param == arg }
11
+ end
12
+ end
13
+ lambda do |*args|
14
+ test = true;
15
+ param_test_pairs = args.zip(tests)
16
+ param_test_pairs.each {|pair| if pair[1].call(pair[0]) == false; test = false; break; end; }
17
+ return test
18
+ end
19
+ end
20
+
21
+ def defmatch(method,*args,&block)
22
+ @defmatch_dispatch_info ||= {} # setup the methods in an instance variable
23
+ @defmatch_dispatch_info[method] ||= [] # setup the ordered array for the method the first time
24
+ @defmatch_dispatch_info[method] << {:test => Defmatch.signiture_match(method,args), :block => block} # add the hash for the test proc and the run proc (block given) to the list of matchers
25
+
26
+ # define dispatch method the first time
27
+ unless respond_to?(method)
28
+ self.send(:define_method,method) do |*args|
29
+ self.class.instance_variable_get(:@defmatch_dispatch_info)[method].each do |hash|
30
+ if hash[:test].call(*args)
31
+ return self.instance_exec(*args,&hash[:block])
32
+ end
33
+ end
34
+ throw "No function clause matching arguments" #This should be a real Exception
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Defmatch do
4
+
5
+ class Tester
6
+ extend(Defmatch)
7
+
8
+ defmatch(:times,Fixnum) {|num| num * 2 }
9
+ defmatch(:times,Array) {|list| list.collect {|i| times(i) } } #how do I refer to the instance method in this context?
10
+ defmatch(:times,lambda {|asdf| asdf == :asdf }) {|asdf| :asdf }
11
+ defmatch(:times,1) {|num| puts "this should never get run"; num }
12
+ defmatch(:times,"matchme") {|string| "matched literal #{string}" }
13
+ defmatch(:times,String) {|string| string*2 }
14
+ end
15
+
16
+ it 'creates a method' do
17
+ expect(Tester).to respond_to(:defmatch)
18
+ end
19
+
20
+ it 'should have an instance method "times"' do
21
+ expect(Tester.new).to respond_to(:times)
22
+ end
23
+
24
+ instance = Tester.new
25
+
26
+ describe instance do
27
+
28
+ it 'should handle an integer' do
29
+ expect(instance.times(4)).to equal(8)
30
+ end
31
+
32
+ it 'should handle a list of integers' do
33
+ expect(instance.times([1,2,3,4])).to eq([2,4,6,8])
34
+ end
35
+
36
+ it 'should match a basic proc matcher' do
37
+ expect(instance.times(:asdf)).to equal(:asdf)
38
+ end
39
+
40
+ it 'should match on literals' do
41
+ expect(instance.times("matchme")).to eq("matched literal matchme")
42
+ end
43
+
44
+ it 'should run the first valid match based on defmatch declaration order' do
45
+ expect(instance.times(1)).to equal(2)
46
+ expect(instance.times("matchme")).to eq("matched literal matchme")
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1 @@
1
+ require 'defmatch'
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: defmatch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Max Warnock
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: '1.5'
25
+ prerelease: false
26
+ type: :development
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ prerelease: false
40
+ type: :development
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ prerelease: false
54
+ type: :development
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-nc
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ prerelease: false
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ prerelease: false
82
+ type: :development
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ prerelease: false
96
+ type: :development
97
+ description: Switching between erlang and ruby a fair amount has me missing erlang's function definition features. Particularly dispatching based on pattern matching. Defmatch is my way of bringing some of that functionality into ruby.
98
+ email:
99
+ - mwarnock@analytical.info
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - defmatch.gemspec
110
+ - lib/defmatch.rb
111
+ - lib/defmatch/version.rb
112
+ - spec/defmatch_spec.rb
113
+ - spec/spec_helper.rb
114
+ homepage: http://github.com/mwarnock/defmatch
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.2.2
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Defmatch provides a method for classes to define and dispatch methods off of pattern matching.
138
+ test_files:
139
+ - spec/defmatch_spec.rb
140
+ - spec/spec_helper.rb