pied_piper 0.1.1 → 0.1.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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +138 -36
- data/lib/pied_piper/kernel.rb +2 -2
- data/lib/pied_piper/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1caeb762311438ceb233ae23503f78187a8c832d334f3d82ce9937401c9e476
|
|
4
|
+
data.tar.gz: 4b59343cd11ed7aa09799fb7b60064a6a4ee017f4d604d647fcde098e73bbec1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd9a93c163394ff8843b3026772c597db9a8f088806e38d7d9f550126cfcf648ec3ece2fd4d4cb30705155b9004f3be667c6244ee448b8a8567c128ea511ce72
|
|
7
|
+
data.tar.gz: ffa15ea30fc2fbb48c36e5921c4217fd4978a58848cf963ce6ab46d5ae9716b27cc4b477ae0996c12178fda757c418dbc1c238ad300d817f378ca1599e82f4bf
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# PiedPiper
|
|
1
|
+
# PiedPiper
|
|
2
2
|
|
|
3
3
|
This gem provides "Unix-like" pipe functionality in Ruby.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The inspiration for this gem were `|>` pipes and functional programming in [Elixir](https://elixir-lang.org).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
After trying to introduce the same `|>` pipe operator in Ruby, which I found out isn't possible due to syntactic reasons ( without hacking the underlying C code ), I settled for another well-known pipe operator, the `|` Unix pipe operator.
|
|
8
|
+
|
|
9
|
+
If you want to read about the inspiration for the name `PiedPiper`, it's an old german fairy tale, the [Pied Piper of Hamelin](https://en.wikipedia.org/wiki/Pied_Piper_of_Hamelin), of a guy who played pipe and hypnotized and lured all children out of town with his music and they were never be seen again.
|
|
8
10
|
|
|
9
11
|
Despite the word "pipe" there's also another common thing between the fairy tale and pipes in this gem:
|
|
10
12
|
|
|
@@ -12,7 +14,7 @@ There's a "piper" object who lures "children" (other objects) away ( through pip
|
|
|
12
14
|
|
|
13
15
|
If you never worked with pipes, this little analogy may help to understand what's happening.
|
|
14
16
|
|
|
15
|
-
Have fun with PiedPiper and don't let him lure you away :-)
|
|
17
|
+
Have fun with PiedPiper and don't let him lure you away... :-)
|
|
16
18
|
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
@@ -37,56 +39,80 @@ First you have to initialize a piper object with another object of your choice:
|
|
|
37
39
|
```ruby
|
|
38
40
|
require "pied_piper"
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
p = PiedPiper.new("rats and kids")
|
|
41
43
|
```
|
|
42
44
|
|
|
43
45
|
A shortcut to get a piper object anywhere you want, is shown in the following example. Only use this if your name is "Chuck Norris of Hamelin", since it "roundhouse pipes" (monkey-patches) Ruby's `Kernel` module :-D
|
|
44
46
|
|
|
47
|
+
Note: It only adds the `piper` method to `Kernel` which isn't defined anywhere else and doesn't change existing Ruby behaviour, so using it doesn't actually make you this badass ;-)
|
|
48
|
+
|
|
45
49
|
```ruby
|
|
46
50
|
require 'pied_piper/kernel'
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
p = piper("rats and kids")
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
Once you have a piper object you can pipe it "Unix-style".
|
|
52
56
|
|
|
53
|
-
Our initial object (e.g: `"rats and
|
|
57
|
+
Our initial object (e.g: `"rats and kids"`), is passed around through each pipe from left to right and transformed by one of the following objects:
|
|
54
58
|
|
|
55
59
|
### Symbol/String Pipes
|
|
56
60
|
|
|
57
61
|
1. A Symbol or String, this calls a method on the piped object with the same name:
|
|
58
62
|
|
|
59
63
|
```ruby
|
|
60
|
-
p =
|
|
64
|
+
p = piper("rats and kids")
|
|
65
|
+
|
|
61
66
|
p | :upcase | p.end
|
|
62
|
-
# => "RATS AND
|
|
67
|
+
# => "RATS AND KIDS"
|
|
63
68
|
```
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
Pipes can be chained:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
p = piper("rats and kids")
|
|
74
|
+
|
|
75
|
+
p | :upcase | :reverse | p.end
|
|
76
|
+
# => "SDIK DNA STAR"
|
|
77
|
+
```
|
|
66
78
|
|
|
67
|
-
|
|
79
|
+
### Ending Pipes
|
|
68
80
|
|
|
69
|
-
|
|
81
|
+
Note: Since "piping" is not a native Ruby syntax feature, rather than a method call in disguise (e.g: `p.|(:upcase)`), the underlying `PiedPiper` class, which wraps the initial object (e.g: `"foo"`) and on which the pipe functionality is called, is just a wrapper for the initial object which handles piping logic.
|
|
82
|
+
|
|
83
|
+
Everytime you finished a pipe transformation you create a new object of class `PiedPiper`, which wraps the mutated inital object again (e.g: from `"foo"` to `"FOO"`).
|
|
84
|
+
|
|
85
|
+
We can build pipe-chains this way, but at the end of each pipe-chain, the piped object has to be "unwrapped" again by ending the pipe-chain with the `.end` method on the pipe object or by just writing `PiedPiper::EndOfPipe` or if you have required `pied_piper/kernel` you can just write `p_end`.
|
|
70
86
|
|
|
71
87
|
```ruby
|
|
72
|
-
p =
|
|
88
|
+
p = piper("rats and kids")
|
|
73
89
|
|
|
74
90
|
p | :upcase | p.end
|
|
75
|
-
# => "RATS AND
|
|
91
|
+
# => "RATS AND KIDS"
|
|
76
92
|
|
|
77
93
|
p | :upcase | PiedPiper::EndOfPipe
|
|
78
|
-
# => "RATS AND
|
|
94
|
+
# => "RATS AND KIDS"
|
|
79
95
|
|
|
80
|
-
p | :upcase |
|
|
81
|
-
# => "RATS AND
|
|
96
|
+
p | :upcase | p_end # when PiedPiper::Kernel was required
|
|
97
|
+
# => "RATS AND KIDS"
|
|
82
98
|
```
|
|
83
99
|
|
|
100
|
+
It would be possible to avoid writing `p.end` at the end of the chain, by implementing the gem in another way, but that would have included to monkey-patch existing Ruby classes, since the `|` method is already implemented by some of them.
|
|
101
|
+
|
|
102
|
+
I decided against that, since I only wanted to add pipe functionality and not alter existing behaviour in any way.
|
|
103
|
+
|
|
104
|
+
Thus the `PiedPiper` class was born.
|
|
105
|
+
|
|
106
|
+
If you want to add pipe functionality everywhere, we already talked about how to implement it above under "Usage".
|
|
107
|
+
|
|
108
|
+
This will make you use pipe functionality everywhere with the least amount of side-effects, since it only adds and doesn't alter existing behaviour.
|
|
109
|
+
|
|
84
110
|
### Array Pipes
|
|
85
111
|
|
|
86
112
|
An Array, whose first element (Symbol/String) again acts as a method call on the piped object and additonal elements which act as parameters to the method call.
|
|
87
113
|
|
|
88
114
|
```ruby
|
|
89
|
-
p =
|
|
115
|
+
p = piper("Pied Piper")
|
|
90
116
|
|
|
91
117
|
concat = [:concat, " of", " Hamelin"]
|
|
92
118
|
|
|
@@ -97,39 +123,39 @@ p | concat | p.end
|
|
|
97
123
|
### Proc Object Pipes
|
|
98
124
|
|
|
99
125
|
An Object of `Proc` class which takes exactly one parameter:
|
|
100
|
-
- `Proc.new { |
|
|
101
|
-
- `proc { |
|
|
102
|
-
- `lambda { |
|
|
103
|
-
- `->(
|
|
126
|
+
- `Proc.new { |kid| ... }`
|
|
127
|
+
- `proc { |kid| ... }`
|
|
128
|
+
- `lambda { |kid| ... }`
|
|
129
|
+
- `->(kid) { ... }`
|
|
104
130
|
|
|
105
131
|
```ruby
|
|
106
|
-
p =
|
|
132
|
+
p = piper("Hypnotized kid")
|
|
107
133
|
|
|
108
|
-
no_happy_end = ->(
|
|
134
|
+
no_happy_end = ->(kid) { kid + " was never seen again..." }
|
|
109
135
|
|
|
110
136
|
p | no_happy_end | p.end
|
|
111
|
-
# => "Hypnotized
|
|
137
|
+
# => "Hypnotized kid was never seen again..."
|
|
112
138
|
```
|
|
113
139
|
|
|
114
|
-
### Method Object
|
|
140
|
+
### Method Object Pipes
|
|
115
141
|
|
|
116
142
|
An object of the `Method` class, where the piped object will be used as the first parameter. You can pass an Array if you need additional parameters:
|
|
117
143
|
|
|
118
144
|
```ruby
|
|
119
145
|
class PiedPiperOfHamelin
|
|
120
|
-
def self.plays_song_on_pipe(audience = "
|
|
121
|
-
puts "#{audience} feel
|
|
146
|
+
def self.plays_song_on_pipe(audience = "Kids", effect = "slightly")
|
|
147
|
+
puts "#{audience} already feel #{effect + " "}hypnotized!"
|
|
122
148
|
end
|
|
123
149
|
end
|
|
124
150
|
|
|
125
|
-
p =
|
|
151
|
+
p = piper("You")
|
|
126
152
|
hypnotize = PiedPiperOfHamelin.method(:plays_song_on_pipe)
|
|
127
153
|
|
|
128
154
|
p | hypnotize | p.end
|
|
129
|
-
# => "You feel
|
|
155
|
+
# => "You already feel slightly hypnotized!"
|
|
130
156
|
|
|
131
157
|
p | [hypnotize, "VERY"] | p.end
|
|
132
|
-
# => "You feel
|
|
158
|
+
# => "You already feel VERY hypnotized!"
|
|
133
159
|
```
|
|
134
160
|
|
|
135
161
|
### Combining Pipes
|
|
@@ -137,26 +163,48 @@ p | [hypnotize, "VERY"] | p.end
|
|
|
137
163
|
Once you know the basic building blocks, you can combine them and build your own pipes of arbitrary length:
|
|
138
164
|
|
|
139
165
|
```ruby
|
|
140
|
-
|
|
166
|
+
p = piper('You')
|
|
141
167
|
lures = [:+, " feel"]
|
|
142
168
|
you = ->(str) { str + " hypnotized!" }
|
|
143
169
|
away = :upcase
|
|
144
170
|
|
|
145
|
-
|
|
171
|
+
p | lures | you | away | piper.end
|
|
146
172
|
# => "YOU FEEL HYPNOTIZED!"
|
|
147
173
|
```
|
|
148
174
|
|
|
149
175
|
### Multiline Pipes
|
|
150
176
|
|
|
151
|
-
Sadly Ruby's syntax doesn't allow for totally nice multiline pipes ( at least not in a way I already found out ). Thus multiline pipes have to be written
|
|
177
|
+
Sadly Ruby's syntax doesn't allow for totally nice multiline pipes ( at least not in a way I already found out ). Thus multiline pipes have to be written with a twist ( since they're actually just inline syntactic sugar for methods like `obj.|(arg)` under the hood:
|
|
178
|
+
|
|
179
|
+
#### With Backslashes:
|
|
180
|
+
|
|
181
|
+
In order to tell Ruby that our (inline) expression hasn't ended yet, when writing it over multiple lines, we can put a backslash at the end of a line to avoid syntax errors:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
p = piper('You')
|
|
185
|
+
lures = [:+, " feel"]
|
|
186
|
+
you = ->(str) { str + " hypnotized!" }
|
|
187
|
+
away = :upcase
|
|
188
|
+
|
|
189
|
+
p \
|
|
190
|
+
| lures \
|
|
191
|
+
| you \
|
|
192
|
+
| away \
|
|
193
|
+
| h.end
|
|
194
|
+
# => "YOU FEEL HYPNOTIZED!"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### As explicit method calls:
|
|
198
|
+
|
|
199
|
+
As we already noticed that pipes `|` are simply method calls in disguise, we can explicitly call them on subsequent lines, which is valid Ruby syntax:
|
|
152
200
|
|
|
153
201
|
```ruby
|
|
154
|
-
|
|
202
|
+
p = piper('You')
|
|
155
203
|
lures = [:+, " feel"]
|
|
156
204
|
you = ->(str) { str + " hypnotized!" }
|
|
157
205
|
away = :upcase
|
|
158
206
|
|
|
159
|
-
|
|
207
|
+
p
|
|
160
208
|
.|(lures)
|
|
161
209
|
.|(you)
|
|
162
210
|
.|(away).
|
|
@@ -164,6 +212,60 @@ piper
|
|
|
164
212
|
# => "YOU FEEL HYPNOTIZED!"
|
|
165
213
|
```
|
|
166
214
|
|
|
215
|
+
### What kind of advantages can pipes offer?
|
|
216
|
+
|
|
217
|
+
Actually we can do nothing else with pipes then we can also do with regular Ruby syntax ( since it only builds on already existing Ruby functionality and is not a totally new language feature ).
|
|
218
|
+
|
|
219
|
+
But piping objects can make some operations more clear/readable because we go from left to right in a linear fashion, instead from inside to outside like in regular function calls.
|
|
220
|
+
|
|
221
|
+
This offers advantages when for example working in "functional style" instead of using methods:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
class Foo
|
|
225
|
+
def self.one
|
|
226
|
+
-> {|x| ... }
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
class Bar
|
|
231
|
+
def self.two
|
|
232
|
+
-> {|x| ... }
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class Baz
|
|
237
|
+
def self.three
|
|
238
|
+
-> {|x| ... }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# This
|
|
243
|
+
|
|
244
|
+
Baz.three.call(Bar.two.call(Foo.one.call(x)))
|
|
245
|
+
|
|
246
|
+
# becomes this
|
|
247
|
+
|
|
248
|
+
piper(x) | Foo.one | Bar.two | Baz.three | p_end
|
|
249
|
+
|
|
250
|
+
# or
|
|
251
|
+
|
|
252
|
+
piper(x) \
|
|
253
|
+
| Foo.one \
|
|
254
|
+
| Bar.two \
|
|
255
|
+
| Baz.three \
|
|
256
|
+
| p_end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
I guess you won't use these kind of functional programming in Ruby too often, since Ruby follows another philosophy.
|
|
260
|
+
|
|
261
|
+
PiedPiper is more a kind of experiment how Ruby can be modified to resemble concepts used in other programming languages like for example [Elixir](https://elixir-lang.org).
|
|
262
|
+
|
|
263
|
+
As far as I can judge Ruby does quite a good job, the flexible language constructs that Ruby has to offer, makes it one of the nicest programming languages to work with. :-)
|
|
264
|
+
|
|
265
|
+
What other kind of good use cases for pipes can you come up with?
|
|
266
|
+
|
|
267
|
+
If you know some ( or missing features ), feel free to open up a pull request, so we can augment code/documentation :-)
|
|
268
|
+
|
|
167
269
|
## Development
|
|
168
270
|
|
|
169
271
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/pied_piper/kernel.rb
CHANGED
data/lib/pied_piper/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pied_piper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Christoph Weegen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-04-
|
|
11
|
+
date: 2019-04-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|