tco_method 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 74462b42288aee51eb1533d2a233c9a39c4bea07
4
- data.tar.gz: 8a4ebe308889ee0edf86489e4491ea3d9d656f52
3
+ metadata.gz: fa6f5ef52346f81e6dda4617f58c78d8ea6268fd
4
+ data.tar.gz: 3d38f3813816bc489c5329fe006e679f67041432
5
5
  SHA512:
6
- metadata.gz: b3cdc64cac48fd98ca99c52fdc076b9e58e2f209bd8bbb7d5b10bf4feb81af392c4077a891652fadad165d5c3086e200254ef51753eabd45e1e01ec0049210c1
7
- data.tar.gz: 807f4ae4f07dbaf15412c9b4dbfe66e1e856e06e647dcd2028b498bac587f4b8dddba890de212ef4250f188ea51a162e796164d3010c09ce22a6a6e50564f32c
6
+ metadata.gz: 3ab604141152bb7c7fc76846a4ecb7edd216b73dd4d134bb07faa13bda12901aa780a5dc34262ec0259d04ab9123a2b486228980dca9684440b3443c16a057e7
7
+ data.tar.gz: 7f1ced9785c6aebf644253d52a2a1c0973d94a1efe2fcc84e6aef3ca099c57e54bbc72c5767b0ea61ffb5b33dcc9d34e85b154843ff0a664881097d22fc05281
data/README.md CHANGED
@@ -8,15 +8,15 @@
8
8
 
9
9
  Provides `TCOMethod::Mixin` for extending Classes and Modules with helper methods
10
10
  to facilitate evaluating code and some types of methods with tail call
11
- optimization enabled. Also provides `TCOMethod.tco_eval` providing an easy means
12
- to evaluate code strings with tail call optimization enabled.
11
+ optimization enabled. Also provides `TCOMethod.tco_eval` providing a direct and
12
+ easy means to evaluate code strings with tail call optimization enabled.
13
13
 
14
14
  ## Installation
15
15
 
16
16
  Add this line to your application's Gemfile:
17
17
 
18
- ```bash
19
- gem 'tco_method'
18
+ ```ruby
19
+ gem "tco_method"
20
20
  ```
21
21
 
22
22
  And then execute:
@@ -33,13 +33,18 @@ $ gem install tco_method
33
33
 
34
34
  ## Usage
35
35
 
36
- Require the `TCOMethod` library:
36
+ Require the [`TCOMethod`](http://www.rubydoc.info/gems/tco_method/TCOMethod)
37
+ library:
37
38
 
38
39
  ```ruby
39
40
  require "tco_method"
40
41
  ```
41
42
 
42
- Extend a class with the `TCOMethod::Mixin` and enjoy!
43
+ Extend a class with the [`TCOMethod::Mixin`](http://www.rubydoc.info/gems/tco_method/TCOMethod/Mixin)
44
+ and let the fun begin!
45
+
46
+ To redefine an instance method with tail call optimization enabled, use
47
+ [`tco_method`](http://www.rubydoc.info/gems/tco_method/TCOMethod/Mixin:tco_method):
43
48
 
44
49
  ```ruby
45
50
  class MyClass
@@ -51,12 +56,31 @@ class MyClass
51
56
  tco_method :factorial
52
57
  end
53
58
 
54
- MyClass.new.factorial(10_000).to_s.length
59
+ puts MyClass.new.factorial(10_000).to_s.length
55
60
  # => 35660
56
61
  ```
57
62
 
58
- Or, use `TCOMethod.tco_eval` directly. Cumbersome, but much more flexible and
59
- powerful:
63
+ Or alternatively, use [`tco_module_method`](http://www.rubydoc.info/gems/tco_method/TCOMethod/Mixin:tco_module_method)
64
+ or [`tco_class_method`](http://www.rubydoc.info/gems/tco_method/TCOMethod/Mixin:tco_module_method)
65
+ for a Module or Class method:
66
+
67
+ ```ruby
68
+ module MyFibonacci
69
+ extend TCOMethod::Mixin
70
+
71
+ def self.fibonacci(index, back_one = 1, back_two = 0)
72
+ index < 1 ? back_two : fibonacci(index - 1, back_one + back_two, back_one)
73
+ end
74
+ tco_module_method :fibonacci
75
+ end
76
+
77
+ puts MyFibonacci.fibonacci(10_000).to_s.length
78
+ # => 2090
79
+ ```
80
+
81
+ Or, for more power and flexibility (at the cost of stringified code blocks) use
82
+ [`TCOMethod.tco_eval`](http://www.rubydoc.info/gems/tco_method/TCOMethod/Mixin:tco_eval)
83
+ directly:
60
84
 
61
85
  ```ruby
62
86
  TCOMethod.tco_eval(<<-CODE)
@@ -71,15 +95,63 @@ MyClass.new.factorial(10_000).to_s.length
71
95
  # => 35660
72
96
  ```
73
97
 
74
- ## Gotchas
98
+ You can kind of get around this by dynamically reading the code you want to
99
+ compile with tail call optimization, but this approach also has downsides in
100
+ that it goes around the standard Ruby `require` model. For example, consider the
101
+ Fibonacci example broken across two scripts, one script serving as a loader and
102
+ the other script acting as a more standard library:
103
+
104
+ ```ruby
105
+ # loader.rb
106
+
107
+ require "tco_method"
108
+ fibonacci_lib = File.read(File.expand_path("../fibonacci.rb", __FILE__))
109
+ TCOMethod.tco_eval(fibonacci_lib)
75
110
 
76
- The list so far:
111
+ puts MyFibonacci.fibonacci(10_000).to_s.length
112
+ # => 2090
77
113
 
78
- - Currently only works with methods defined using the `def` keyword.
79
- - Class annotations use the [`method_source` gem](https://github.com/banister/method_source)
114
+
115
+ # fibonacci.rb
116
+
117
+ module MyFibonacci
118
+ def self.fibonacci(index, back_one = 1, back_two = 0)
119
+ index < 1 ? back_two : fibonacci(index - 1, back_one + back_two, back_one)
120
+ end
121
+ end
122
+ ```
123
+
124
+ If you really want to get crazy, you could include the `TCOMethod::Mixin` module
125
+ in the Module class and add these behaviors to all Modules and Classes. To quote
126
+ VIM plugin author extraordinaire Tim Pope, "I don't like to get crazy." Consider
127
+ yourself warned.
128
+
129
+ ```ruby
130
+ # Don't say I didn't warn you...
131
+
132
+ Module.include(TCOMethod::Mixin)
133
+
134
+ module MyFibonacci
135
+ def self.fibonacci(index, back_one = 1, back_two = 0)
136
+ index < 1 ? back_two : fibonacci(index - 1, back_one + back_two, back_one)
137
+ end
138
+ tco_module_method :fibonacci
139
+ end
140
+
141
+ puts MyFibonacci.fibonacci(10_000).to_s.length
142
+ # => 2090
143
+ ```
144
+
145
+ ## Gotchas
146
+
147
+ Quirks with Module and Class annotations:
148
+ - Annotations only work with methods defined using the `def` keyword.
149
+ - Annotations use the [`method_source` gem](https://github.com/banister/method_source)
80
150
  to retrieve the method source to reevaluate. As a result, class annotations
81
151
  can act strangely when used in more dynamic contexts like `irb` or `pry`.
82
-
152
+ - Annotations reopen the Module or Class by name to redefine the given method.
153
+ This process will fail for dynamic Modules and Classes that aren't assigned to
154
+ constants and, ergo, don't have names.
83
155
 
84
156
  I'm sure there are more and I will document them here as I come across them.
85
157
 
@@ -4,18 +4,18 @@ module TCOMethod
4
4
  # call optimization enabled and re-evaluating existing methods with tail call
5
5
  # optimization enabled.
6
6
  module Mixin
7
- # Class annotation causing the class or module method identified by the
8
- # given method name to be reevaluated with tail call optimization enabled.
9
- # Only works for methods defined using the `def` keyword.
7
+ # Module or Class annotation causing the class or module method identified
8
+ # by the given method name to be reevaluated with tail call optimization
9
+ # enabled. Only works for methods defined using the `def` keyword.
10
10
  #
11
11
  # @param [String, Symbol] method_name The name of the class or module method
12
12
  # that should be reeevaluated with tail call optimization enabled.
13
13
  # @return [Symbol] The symbolized method name.
14
14
  # @see TCOMethod.reevaluate_method_with_tco
15
- def tco_class_method(method_name)
16
- TCOMethod.reevaluate_method_with_tco(self, method_name, :class)
15
+ def tco_module_method(method_name)
16
+ TCOMethod.reevaluate_method_with_tco(self, method_name, :module)
17
17
  end
18
- alias_method :tco_module_method, :tco_class_method
18
+ alias_method :tco_class_method, :tco_module_method
19
19
 
20
20
  # Evaluate the given code String with tail call optimization enabled.
21
21
  #
@@ -39,7 +39,6 @@ module TCOMethod
39
39
  # @see TCOMethod.reevaluate_method_with_tco
40
40
  def tco_method(method_name)
41
41
  TCOMethod.reevaluate_method_with_tco(self, method_name, :instance)
42
- method_name.to_sym
43
42
  end
44
43
  end
45
44
  end
@@ -1,4 +1,4 @@
1
1
  module TCOMethod
2
2
  # The version of the TCOMethod gem.
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
data/lib/tco_method.rb CHANGED
@@ -47,8 +47,9 @@ module TCOMethod
47
47
  if method_info.type != :method
48
48
  raise TypeError, "Invalid method type: #{method_info.type}"
49
49
  end
50
+ receiver_class = receiver.is_a?(Class) ? :class : :module
50
51
  code = <<-CODE
51
- class #{receiver.name}
52
+ #{receiver_class} #{receiver.name}
52
53
  #{existing_method.source}
53
54
  end
54
55
  CODE
@@ -3,37 +3,67 @@ require "test_helper"
3
3
  class MixinTest < TCOMethod::TestCase
4
4
  include TCOMethod::TestHelpers::FactorialStackBusterHelper
5
5
 
6
- TestClass = Class.new do
7
- extend TCOMethod::Mixin
8
- end
6
+ TestClass = Class.new { extend TCOMethod::Mixin }
7
+ TestModule = Module.new { extend TCOMethod::Mixin }
8
+
9
9
 
10
- subject { TestClass }
10
+ context "Module extensions" do
11
+ subject { TestModule }
11
12
 
12
- [:tco_class_method, :tco_module_method].each do |method_alias|
13
- context "##{method_alias}" do
13
+ context "#tco_module_method" do
14
14
  should "call TCOMethod.reevaluate_method_with_tco with expected arguments" do
15
15
  method_name = :some_method
16
- args = [TestClass, method_name, :class]
16
+ args = [subject, method_name, :module]
17
17
  TCOMethod.expects(:reevaluate_method_with_tco).with(*args)
18
- subject.send(method_alias, method_name)
18
+ subject.tco_module_method(method_name)
19
+ end
20
+ end
21
+
22
+ context "#tco_eval" do
23
+ should "call TCOMethod.eval with expected arguments" do
24
+ code = "some_code"
25
+ TCOMethod.expects(:tco_eval).with(code)
26
+ subject.tco_eval(code)
19
27
  end
20
28
  end
21
- end
22
29
 
23
- context "#tco_eval" do
24
- should "call TCOMethod.eval with expected arguments" do
25
- code = "some_code"
26
- TCOMethod.expects(:tco_eval).with(code)
27
- subject.tco_eval(code)
30
+ context "#tco_method" do
31
+ should "call TCOMethod.reevaluate_method_with_tco with expected arguments" do
32
+ method_name = :some_method
33
+ args = [subject, method_name, :instance]
34
+ TCOMethod.expects(:reevaluate_method_with_tco).with(*args)
35
+ subject.tco_method(method_name)
36
+ end
28
37
  end
29
38
  end
30
39
 
31
- context "#tco_method" do
32
- should "call TCOMethod.reevaluate_method_with_tco with expected arguments" do
33
- method_name = :some_method
34
- args = [TestClass, method_name, :instance]
35
- TCOMethod.expects(:reevaluate_method_with_tco).with(*args)
36
- subject.tco_method(method_name)
40
+ context "Class extensions" do
41
+ subject { TestClass }
42
+
43
+ context "#tco_class_method" do
44
+ should "call TCOMethod.reevaluate_method_with_tco with expected arguments" do
45
+ method_name = :some_method
46
+ args = [subject, method_name, :module]
47
+ TCOMethod.expects(:reevaluate_method_with_tco).with(*args)
48
+ subject.tco_class_method(method_name)
49
+ end
50
+ end
51
+
52
+ context "#tco_eval" do
53
+ should "call TCOMethod.eval with expected arguments" do
54
+ code = "some_code"
55
+ TCOMethod.expects(:tco_eval).with(code)
56
+ subject.tco_eval(code)
57
+ end
58
+ end
59
+
60
+ context "#tco_method" do
61
+ should "call TCOMethod.reevaluate_method_with_tco with expected arguments" do
62
+ method_name = :some_method
63
+ args = [subject, method_name, :instance]
64
+ TCOMethod.expects(:reevaluate_method_with_tco).with(*args)
65
+ subject.tco_method(method_name)
66
+ end
37
67
  end
38
68
  end
39
69
  end
@@ -5,13 +5,21 @@ class TCOMethodTest < TCOMethod::TestCase
5
5
 
6
6
  Subject = TCOMethod
7
7
 
8
- TestClass = Class.new do
8
+ test_subject_builder = proc do
9
9
  extend TCOMethod::Mixin
10
10
 
11
- class << self;
12
- define_method(:class_block_method) { }
11
+ class << self
12
+ define_method(:singleton_block_method) { }
13
13
  end
14
14
 
15
+ # Equivalent to the below, but provides a target for verifying that
16
+ # tco_module_method works on Classes and tco_class_method works on Modules.
17
+ def self.module_factorial(n, acc = 1)
18
+ n <= 1 ? acc : module_factorial(n - 1, n * acc)
19
+ end
20
+
21
+ # Equivalent to the above, but provides a target for verifying that
22
+ # tco_module_method works on Classes and tco_class_method works on Modules.
15
23
  def self.class_factorial(n, acc = 1)
16
24
  n <= 1 ? acc : class_factorial(n - 1, n * acc)
17
25
  end
@@ -23,6 +31,9 @@ class TCOMethodTest < TCOMethod::TestCase
23
31
  end
24
32
  end
25
33
 
34
+ TestModule = Module.new(&test_subject_builder)
35
+ TestClass = Class.new(&test_subject_builder)
36
+
26
37
  subject { Subject }
27
38
 
28
39
  context Subject.name do
@@ -67,73 +78,105 @@ class TCOMethodTest < TCOMethod::TestCase
67
78
  context "::reevaluate_method_with_tco" do
68
79
  subject { Subject.method(:reevaluate_method_with_tco) }
69
80
 
70
- context "validation" do
71
- should "raise ArgumentError unless receiver given" do
72
- assert_raises(ArgumentError) do
73
- subject.call(nil, :nil?, :instance)
74
- end
75
- end
81
+ [TestClass, TestModule].each do |method_owner|
82
+ method_owner_class = method_owner.class.name.downcase.to_sym
76
83
 
77
- should "raise ArgumentError unless method name given" do
78
- assert_raises(ArgumentError) do
79
- subject.call(TestClass, nil, :instance)
84
+ context "validation" do
85
+ should "raise ArgumentError unless receiver given" do
86
+ assert_raises(ArgumentError) do
87
+ subject.call(nil, :nil?, :instance)
88
+ end
80
89
  end
81
- end
82
90
 
83
- should "raise ArgumentError unless method owner given" do
84
- assert_raises(ArgumentError) do
85
- subject.call(TestClass, :class_factorial, nil)
91
+ should "raise ArgumentError unless method name given" do
92
+ assert_raises(ArgumentError) do
93
+ subject.call(method_owner, nil, :instance)
94
+ end
86
95
  end
87
- end
88
96
 
89
- should "raise TypeError for block methods" do
90
- assert_raises(TypeError) do
91
- subject.call(TestClass, :class_block_method, :class)
92
- end
93
- assert_raises(TypeError) do
94
- subject.call(TestClass, :instance_block_method, :instance)
97
+ should "raise ArgumentError unless method owner given" do
98
+ assert_raises(ArgumentError) do
99
+ subject.call(method_owner, :class_factorial, nil)
100
+ end
95
101
  end
96
- end
97
- end
98
102
 
99
- context "with class method" do
100
- should "raise NameError if no class method with given name defined" do
101
- assert_raises(NameError) do
102
- subject.call(TestClass, :marmalade, :class)
103
+ should "raise TypeError for block methods" do
104
+ assert_raises(TypeError) do
105
+ subject.call(method_owner, :singleton_block_method, :class)
106
+ end
107
+ assert_raises(TypeError) do
108
+ subject.call(method_owner, :instance_block_method, :instance)
109
+ end
103
110
  end
104
111
  end
105
112
 
106
- should "re-compile the given method with tail call optimization" do
107
- # Exceed maximum available stack depth by 100 for good measure
108
- factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
109
- assert_raises(SystemStackError) do
110
- TestClass.class_factorial(factorial_seed)
111
- end
113
+ context "#{method_owner_class} receiver" do
114
+ context "with module method" do
115
+ should "raise NameError if no #{method_owner_class} method with given name defined" do
116
+ assert_raises(NameError) do
117
+ subject.call(method_owner, :marmalade, method_owner_class)
118
+ end
119
+ end
112
120
 
113
- subject.call(TestClass, :class_factorial, :class)
114
- expected_result = iterative_factorial(factorial_seed)
115
- assert_equal expected_result, TestClass.class_factorial(factorial_seed)
116
- end
117
- end
121
+ should "re-compile the given method with tail call optimization" do
122
+ # Exceed maximum available stack depth by 100 for good measure
123
+ factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
124
+ assert_raises(SystemStackError) do
125
+ method_owner.module_factorial(factorial_seed)
126
+ end
118
127
 
119
- context "with instance method" do
120
- should "raise NameError if no instance method with given name defined" do
121
- assert_raises(NameError) do
122
- subject.call(TestClass, :marmalade, :instance)
128
+ subject.call(method_owner, :module_factorial, :module)
129
+ expected_result = iterative_factorial(factorial_seed)
130
+ assert_equal expected_result, method_owner.module_factorial(factorial_seed)
131
+ end
123
132
  end
124
- end
125
133
 
126
- should "re-compile the given method with tail call optimization" do
127
- # Exceed maximum available stack depth by 100 for good measure
128
- factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
129
- assert_raises(SystemStackError) do
130
- TestClass.new.instance_factorial(factorial_seed)
134
+ context "with class method" do
135
+ should "raise NameError if no class method with given name defined" do
136
+ assert_raises(NameError) do
137
+ subject.call(method_owner, :marmalade, :class)
138
+ end
139
+ end
140
+
141
+ should "re-compile the given method with tail call optimization" do
142
+ # Exceed maximum available stack depth by 100 for good measure
143
+ factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
144
+ assert_raises(SystemStackError) do
145
+ method_owner.class_factorial(factorial_seed)
146
+ end
147
+
148
+ subject.call(method_owner, :class_factorial, method_owner_class)
149
+ expected_result = iterative_factorial(factorial_seed)
150
+ assert_equal expected_result, method_owner.class_factorial(factorial_seed)
151
+ end
131
152
  end
132
153
 
133
- subject.call(TestClass, :instance_factorial, :instance)
134
- expected_result = iterative_factorial(factorial_seed)
135
- assert_equal expected_result, TestClass.new.instance_factorial(factorial_seed)
154
+ context "with instance method" do
155
+ should "raise NameError if no instance method with given name defined" do
156
+ assert_raises(NameError) do
157
+ subject.call(method_owner, :marmalade, :instance)
158
+ end
159
+ end
160
+
161
+ should "re-compile the given method with tail call optimization" do
162
+ # Exceed maximum available stack depth by 100 for good measure
163
+ factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
164
+ instance_class = instance_class_for_receiver(method_owner)
165
+ assert_raises(SystemStackError) do
166
+ instance_class.new.instance_factorial(factorial_seed)
167
+ end
168
+
169
+ subject.call(method_owner, :instance_factorial, :instance)
170
+ expected_result = iterative_factorial(factorial_seed)
171
+ assert_equal expected_result, instance_class.new.instance_factorial(factorial_seed)
172
+ end
173
+ end
136
174
  end
137
175
  end
138
176
  end
177
+
178
+ def instance_class_for_receiver(receiver)
179
+ return receiver if receiver.is_a?(Class)
180
+ Class.new { include receiver }
181
+ end
139
182
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tco_method
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Guinther
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-12 00:00:00.000000000 Z
11
+ date: 2015-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: method_source