pipe_operator 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
  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)