pipe_operator 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
  SHA256:
3
- metadata.gz: fc08d5b15937d1418ca864e631eb9f0a6a3fafd04e2c1f7496fbdd925531eec7
4
- data.tar.gz: 340f185be7fe2f803a0d61603f1b1c89c641f6dfbf21ba691e1c3e48c3c5bdce
3
+ metadata.gz: ddf66c989601033a8afbc02f4ae7689052eecaa61d361d477f2cf92bec30ce97
4
+ data.tar.gz: fa3b1c43271d8ee56032fe7b485a81aec78152602e39ece819929c2accf246f6
5
5
  SHA512:
6
- metadata.gz: 4b2e3096c092d839dde50eadbc196cef82745371c99cb313f845587b0c4908ab6225a261309984d2cdef927605f44308026adf57a7c02970721d84c0e240d958
7
- data.tar.gz: a873ca59f03c6e50630c9d8b91f5494a5b7bfb06b558b21c574c716065f63fb381d155b66c106c4d12de237fbb8b1506bb40fd2e459f130a278ceea26ee0daa5
6
+ metadata.gz: 32a09b3839ee5f41e35623300a0501c8fdc3365c38996d6433ad89d659e055d514afa170946c10c58fb5646236c077fe5463c047c2e96c2ddd48e13d141ba196
7
+ data.tar.gz: b3e31722a99368a65a8daf790693d0712008862c558daea345321d8bdbaaccf5eaf09037c516e1210fdf0fbc967130459cbbb545e91ad28e4381c40ff6608227
data/README.md CHANGED
@@ -3,22 +3,47 @@
3
3
  > Elixir/Unix style pipe operations in Ruby - **PROOF OF CONCEPT**
4
4
 
5
5
  ```ruby
6
- -9.pipe { abs | Math.sqrt | to_i } #=> 3
7
- ```
8
-
9
- ```ruby
10
- [9, 64].map(&Math.|.sqrt.to_i) #=> [3, 8]
11
- ```
12
-
13
- ```ruby
14
- "https://api.github.com/repos/ruby/ruby".| do
6
+ "https://api.github.com/repos/ruby/ruby".pipe do
15
7
  URI.parse
16
8
  Net::HTTP.get
17
9
  JSON.parse.fetch("stargazers_count")
18
10
  yield_self { |n| "Ruby has #{n} stars" }
19
11
  Kernel.puts
20
12
  end
21
- #=> Ruby has 15115 stars
13
+ #=> Ruby has 15120 stars
14
+ ```
15
+
16
+ ```ruby
17
+ -9.pipe { abs; Math.sqrt; to_i } #=> 3
18
+
19
+ # Method chaining is supported:
20
+ -9.pipe { abs; Math.sqrt.to_i } #=> 3
21
+
22
+ # Pipe | for syntactic sugar:
23
+ -9.pipe { abs | Math.sqrt.to_i } #=> 3
24
+
25
+ # Pipe blocks are instance_exec within the context
26
+ # of a PipeOperator::Pipe object which defines its
27
+ # own implementation for how `|` should behave.
28
+ #
29
+ # If we actually need to pipe the method `|` on
30
+ # some other object then we can just use `send`:
31
+ -2.pipe { abs | send(:|, 4) } #=> 6
32
+ ```
33
+
34
+ ```ruby
35
+ sqrt = Math.pipe.sqrt #=> #<PipeOperator::Closure:0x00007fc1172ed558@pipe_operator/closure.rb:18>
36
+ sqrt.call(9) #=> 3.0
37
+ sqrt.call(64) #=> 8.0
38
+
39
+ [9, 64].map(&Math.pipe.sqrt) #=> [3.0, 8.0]
40
+ [9, 64].map(&Math.pipe.sqrt.to_i.to_s) #=> ["3", "8"]
41
+
42
+ # Still not concise enough for you?
43
+ Module.alias_method(:|, :__pipe__)
44
+
45
+ [9, 64].map(&Math.|.sqrt) #=> [3.0, 8.0]
46
+ [9, 64].map(&Math.|.sqrt.to_i.to_s) #=> [3.0, 8.0]
22
47
  ```
23
48
 
24
49
  ## Why?
@@ -31,6 +56,16 @@ There's been some recent activity related to `Method` and `Proc` composition in
31
56
 
32
57
  This gem was created to **propose an alternative syntax** for this kind of behavior.
33
58
 
59
+ ## Matz on Ruby
60
+
61
+ Source: [ruby-lang.org/en/about](https://www.ruby-lang.org/en/about)
62
+
63
+ Ruby is a language of careful **balance of both functional and imperative programming**.
64
+
65
+ Matz has often said that he is **trying to make Ruby natural, not simple**, in a way that mirrors life.
66
+
67
+ Building on this, he adds: Ruby is **simple in appearance, but is very complex inside**, just like our human body.
68
+
34
69
  ## Concept
35
70
 
36
71
  The general idea is to **pass the result of one expression as an argument to another expression** - similar to [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)):
@@ -46,7 +81,7 @@ The [Elixir pipe operator documentation](https://elixirschool.com/en/lessons/bas
46
81
  JSON.parse(Net::HTTP.get(URI.parse(url)))
47
82
  ```
48
83
 
49
- To be **inversed** and rewritten as **left to right** or **top to bottom** which is more **natural to read** in English:
84
+ To be **inverted** and rewritten as **left to right** or **top to bottom** which is more **natural to read** in English:
50
85
 
51
86
  ```ruby
52
87
  # left to right
@@ -83,7 +118,7 @@ end
83
118
  While the ability to perform a job correctly and efficiently is certainly important - the **true beauty of a program lies in its clarity and conciseness**:
84
119
 
85
120
  ```ruby
86
- "https://api.github.com/repos/ruby/ruby".| do
121
+ "https://api.github.com/repos/ruby/ruby".pipe do
87
122
  URI.parse
88
123
  Net::HTTP.get
89
124
  JSON.parse.fetch("stargazers_count")
@@ -96,22 +131,12 @@ end
96
131
  There's nothing really special here - it's just a **block of expressions like any other Ruby DSL** and the pipe `|` operator has been [around for decades](https://en.wikipedia.org/wiki/Pipeline_(Unix))!
97
132
 
98
133
  ```ruby
99
- # we can already do this
100
- objects.map(&Marshal.method(:dump))
101
-
102
- # but this is more concise
103
- objects.map(&Marshal.|.dump)
104
- ```
105
-
106
- The **simplicity and elegance of Ruby** is one of the many reasons that people fall in love with the language!
107
-
108
- ```ruby
109
- Ruby.is.so(:simple, &elegant).that(you can) do
134
+ Ruby.is.so(elegant, &:expressive).that(you can) do
110
135
  pretty_much ANYTHING if it.compiles!
111
136
  end
112
137
  ```
113
138
 
114
- This concept of **pipes could be a great fit** like it has been for many other languages:
139
+ This concept of **pipe operations could be a great fit** like it has been for many other languages:
115
140
 
116
141
  * [Caml composition operators](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#1_Compositionoperators)
117
142
  * [Closure threading macros](https://clojure.org/guides/threading_macros)
@@ -128,22 +153,11 @@ This concept of **pipes could be a great fit** like it has been for many other l
128
153
 
129
154
  **WARNING - EXPERIMENTAL PROOF OF CONCEPT**
130
155
 
131
- This has only been **tested in isolation with RSpec**!
156
+ This has only been **tested in isolation with RSpec and Ruby 2.5.3**!
132
157
 
133
158
  ```ruby
134
159
  # First `gem install pipe_operator`
135
160
  require "pipe_operator"
136
-
137
- # Then use PipeOperator as a refinement
138
- using ::PipeOperator
139
-
140
- # Or include PipeOperator in classes/modules
141
- class AnyClass
142
- include ::PipeOperator
143
- end
144
-
145
- # Or monkey patch PipeOperator into ALL objects
146
- require "pipe_operator/autoload"
147
161
  ```
148
162
 
149
163
  ## Implementation
@@ -151,37 +165,40 @@ require "pipe_operator/autoload"
151
165
  The [PipeOperator](https://github.com/lendinghome/pipe_operator/blob/master/lib/pipe_operator.rb) module has a method named `__pipe__` which is aliased as `pipe` for convenience and `|` for syntactic sugar:
152
166
 
153
167
  ```ruby
154
- def __pipe__(*args, &block)
155
- Pipe.new(self, *args, &block)
168
+ module PipeOperator
169
+ def __pipe__(*args, &block)
170
+ Pipe.new(self, *args, &block)
171
+ end
156
172
  end
157
173
 
158
- alias | __pipe__
159
- alias pipe __pipe__
174
+ BasicObject.send(:include, PipeOperator)
175
+ Kernel.alias_method(:pipe, :__pipe__)
176
+ Module.alias_method(:|, :__pipe__)
160
177
  ```
161
178
 
162
179
  When no arguments are passed to `__pipe__` then a [PipeOperator::Pipe](https://github.com/lendinghome/pipe_operator/blob/master/lib/pipe_operator/pipe.rb) object is returned:
163
180
 
164
181
  ```ruby
165
- Math.| #=> #<PipeOperator::Pipe:Math>
182
+ Math.pipe #=> #<PipeOperator::Pipe:Math>
166
183
  ```
167
184
 
168
185
  Any methods invoked on this object returns a [PipeOperator::Closure](https://github.com/lendinghome/pipe_operator/blob/master/lib/pipe_operator/closure.rb) which **calls the method on the object later**:
169
186
 
170
187
  ```ruby
171
- sqrt = Math.|.sqrt #=> #<PipeOperator::Closure:0x00007fc1172ed558@pipe_operator/closure.rb:18>
172
- sqrt.call(16) #=> 4.0
188
+ sqrt = Math.pipe.sqrt #=> #<PipeOperator::Closure:0x00007fc1172ed558@pipe_operator/closure.rb:18>
189
+ sqrt.call(16) #=> 4.0
173
190
 
174
- missing = Math.|.missing #=> #<PipeOperator::Closure:0x00007fc11726f0e0@pipe_operator/closure.rb:18>
175
- missing.call #=> NoMethodError: undefined method 'missing' for Math:Module
191
+ missing = Math.pipe.missing #=> #<PipeOperator::Closure:0x00007fc11726f0e0@pipe_operator/closure.rb:18>
192
+ missing.call #=> NoMethodError: undefined method 'missing' for Math:Module
176
193
 
177
- Math.method(:missing) #=> NameError: undefined method 'missing' for class '#<Class:Math>'
194
+ Math.method(:missing) #=> NameError: undefined method 'missing' for class '#<Class:Math>'
178
195
  ```
179
196
 
180
197
  When `__pipe__` is called **with arguments but without a block** then it behaves similar to `__send__`:
181
198
 
182
199
  ```ruby
183
- sqrt = Math | :sqrt #=> #<PipeOperator::Closure:0x00007fe52e0cdf80@pipe_operator/closure.rb:18>
184
- sqrt.call(16) #=> 4.0
200
+ sqrt = Math.pipe(:sqrt) #=> #<PipeOperator::Closure:0x00007fe52e0cdf80@pipe_operator/closure.rb:18>
201
+ sqrt.call(16) #=> 4.0
185
202
 
186
203
  sqrt = Math.pipe(:sqrt, 16) #=> #<PipeOperator::Closure:0x00007fe52fa18fd0@pipe_operator/closure.rb:18>
187
204
  sqrt.call #=> 4.0
@@ -191,13 +208,13 @@ sqrt.call(16) #=> ArgumentError: wrong number of arguments (given
191
208
  These [PipeOperator::Closure](https://github.com/lendinghome/pipe_operator/blob/master/lib/pipe_operator/closure.rb) objects can be [bound as block arguments](https://github.com/lendinghome/pipe_operator/blob/master/lib/pipe_operator/proxy.rb#L10-L13) just like any other [Proc](https://ruby-doc.org/core-2.5.3/Proc.html):
192
209
 
193
210
  ```ruby
194
- [16, 256].map(&Math.|.sqrt) #=> [4.0, 16.0]
211
+ [16, 256].map(&Math.pipe.sqrt) #=> [4.0, 16.0]
195
212
  ```
196
213
 
197
214
  Simple **closure composition is supported** via [method chaining](https://github.com/lendinghome/pipe_operator/blob/master/lib/pipe_operator/closure.rb#L56):
198
215
 
199
216
  ```ruby
200
- [16, 256].map(&Math.|.sqrt.to_i.to_s) #=> ["4", "16"]
217
+ [16, 256].map(&Math.pipe.sqrt.to_i.to_s) #=> ["4", "16"]
201
218
  ```
202
219
 
203
220
  The **block** form of `__pipe__` behaves **similar to instance_exec** but can also [call methods on other objects](https://github.com/lendinghome/pipe_operator/blob/master/lib/pipe_operator/pipe.rb#L81):
@@ -292,7 +309,35 @@ define_method(method) do |*args, &block|
292
309
  end
293
310
  ```
294
311
 
295
- These proxy modules are prepended everywhere! It's certainly something that **could be way more efficient as a core part of Ruby**.
312
+ These **proxy modules are prepended everywhere**!
313
+
314
+ It's certainly something that **could be way more efficient as a core part of Ruby**.
315
+
316
+ Maybe somewhere **lower level where methods are dispatched**? Possibly somewhere in this [vm_eval.c switch](https://github.com/ruby/ruby/blob/trunk/vm_eval.c#L111)?
317
+
318
+ ```c
319
+ again:
320
+ switch (cc->me->def->type) {
321
+ case VM_METHOD_TYPE_ISEQ
322
+ case VM_METHOD_TYPE_NOTIMPLEMENTED
323
+ case VM_METHOD_TYPE_CFUNC
324
+ case VM_METHOD_TYPE_ATTRSET
325
+ case VM_METHOD_TYPE_IVAR
326
+ case VM_METHOD_TYPE_BMETHOD
327
+ case VM_METHOD_TYPE_ZSUPER
328
+ case VM_METHOD_TYPE_REFINED
329
+ case VM_METHOD_TYPE_ALIAS
330
+ case VM_METHOD_TYPE_MISSING
331
+ case VM_METHOD_TYPE_OPTIMIZED
332
+ case OPTIMIZED_METHOD_TYPE_SEND
333
+ case OPTIMIZED_METHOD_TYPE_CALL
334
+ case VM_METHOD_TYPE_UNDEF
335
+ }
336
+ ```
337
+
338
+ Then we'd **only need Ruby C API ports** for [PipeOperator::Pipe](https://github.com/LendingHome/pipe_operator/blob/master/lib/pipe_operator/pipe.rb) and [PipeOperator::Closure](https://github.com/LendingHome/pipe_operator/blob/master/lib/pipe_operator/closure.rb)!
339
+
340
+ All other objects in this proof of concept are related to **method interception** and would no longer be necessary.
296
341
 
297
342
  ## Bugs
298
343
 
@@ -366,6 +411,7 @@ bundle exec rspec
366
411
  * https://github.com/tiagopog/piped_ruby
367
412
  * https://github.com/jicksta/methodphitamine
368
413
  * https://github.com/jicksta/superators
414
+ * https://github.com/baweaver/xf
369
415
 
370
416
  ## Contributing
371
417
 
@@ -13,10 +13,6 @@ module PipeOperator
13
13
  def __pipe__(*args, &block)
14
14
  Pipe.new(self, *args, &block)
15
15
  end
16
- alias | __pipe__
17
- alias pipe __pipe__
18
-
19
- refine(::BasicObject) { include PipeOperator }
20
16
 
21
17
  class << self
22
18
  def gem
@@ -47,3 +43,7 @@ module PipeOperator
47
43
  end
48
44
  end
49
45
  end
46
+
47
+ BasicObject.send(:include, PipeOperator)
48
+ Kernel.alias_method(:pipe, :__pipe__)
49
+ Module.alias_method(:|, :__pipe__)
@@ -39,6 +39,10 @@ module PipeOperator
39
39
  super
40
40
  end
41
41
 
42
+ def |(*args, &block)
43
+ Pipe.new(self, *args, &block)
44
+ end
45
+
42
46
  def __chain__(*args)
43
47
  return @__chain__ if args.empty?
44
48
 
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.required_ruby_version = ">= 2.0.0"
10
10
  s.summary = "Elixir/Unix style pipe operations in Ruby"
11
11
  s.test_files = `git ls-files -- spec/* 2>/dev/null`.split("\n")
12
- s.version = "0.0.1"
12
+ s.version = "0.0.2"
13
13
 
14
14
  s.rdoc_options = %w[
15
15
  --all
@@ -1,6 +1,4 @@
1
1
  RSpec.describe PipeOperator do
2
- using PipeOperator
3
-
4
2
  describe ".gem" do
5
3
  it "returns a Gem::Specification" do
6
4
  gem = PipeOperator.gem
@@ -61,7 +59,7 @@ RSpec.describe PipeOperator do
61
59
  end
62
60
 
63
61
  it "curries arguments and blocks" do
64
- actual = "testing".|.sub("test").("TEST")
62
+ actual = "testing".pipe.sub("test").("TEST")
65
63
  expect(actual).to eq("TESTing")
66
64
 
67
65
  actual = ["testing"].pipe.map(&:upcase).call
@@ -106,22 +104,22 @@ RSpec.describe PipeOperator do
106
104
  end
107
105
 
108
106
  it "supports pipe and stream expressions" do
109
- actual = "-9".|{to_i}
107
+ actual = "-9".pipe{to_i}
110
108
  expect(actual).to eq(-9)
111
109
 
112
- actual = "-9".|{to_i; abs}
110
+ actual = "-9".pipe{to_i; abs}
113
111
  expect(actual).to eq(9)
114
112
 
115
- actual = "-9".|{to_i; abs; Math.sqrt}
113
+ actual = "-9".pipe{to_i; abs; Math.sqrt}
116
114
  expect(actual).to eq(3)
117
115
 
118
116
  actual = "-9".pipe { to_i | abs | Math.sqrt }
119
117
  expect(actual).to eq(3)
120
118
 
121
- actual = "-9".|{to_i; abs; Math.sqrt; to_i; send(:*, 2)}
119
+ actual = "-9".pipe{to_i; abs; Math.sqrt; to_i; send(:*, 2)}
122
120
  expect(actual).to eq(6)
123
121
 
124
- actual = "-16".|{
122
+ actual = "-16".pipe{
125
123
  to_i
126
124
  abs
127
125
  Math.sqrt
@@ -174,7 +172,7 @@ RSpec.describe PipeOperator do
174
172
  end
175
173
  end
176
174
 
177
- actual = Markdown.new.|.format.call("test")
175
+ actual = Markdown.new.pipe.format.call("test")
178
176
  expect(actual).to eq("TEST")
179
177
 
180
178
  actual = "test".pipe(Markdown.new, &:format)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pipe_operator
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
  - LendingHome
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-09 00:00:00.000000000 Z
11
+ date: 2018-12-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: engineering@lendinghome.com
@@ -26,7 +26,6 @@ files:
26
26
  - README.md
27
27
  - Rakefile
28
28
  - lib/pipe_operator.rb
29
- - lib/pipe_operator/autoload.rb
30
29
  - lib/pipe_operator/closure.rb
31
30
  - lib/pipe_operator/observer.rb
32
31
  - lib/pipe_operator/pipe.rb
@@ -1,3 +0,0 @@
1
- require_relative "../pipe_operator"
2
-
3
- BasicObject.send(:include, PipeOperator)