tco_method 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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