defmatch 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +10 -0
- data/defmatch.gemspec +27 -0
- data/lib/defmatch/version.rb +3 -0
- data/lib/defmatch.rb +41 -0
- data/spec/defmatch_spec.rb +51 -0
- data/spec/spec_helper.rb +1 -0
- metadata +140 -0
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
data/Gemfile
ADDED
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
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
|
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|