Last active
June 12, 2018 17:08
-
-
Save pcreux/2f87847e5e4aad37db02 to your computer and use it in GitHub Desktop.
*nix has pipes, Elixir has pipes, Ruby deserves pipes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Elixir has pipes `|>`. Let's try to implement those in Ruby. | |
# | |
# I want to write this: | |
# | |
# email.body | RemoveSignature | HighlightMentions | :html_safe | |
# | |
# instead of: | |
# | |
# HighlightMentions.call(RemoveSignature.call(email.body)).html_safe | |
# | |
# Ugly implementation starts here... | |
def pipe_it(input, filter) | |
# multiplexed input! | |
if input.is_a? Array | |
return input.map { |input_item| pipe_it(input_item, filter) } | |
end | |
case filter | |
when Symbol | |
input.send(filter) | |
when Hash | |
method = filter.keys.first | |
arguments = Array(filter.values.first) | |
input.send(method, *arguments) | |
when Array | |
# multiplex! | |
filter.map { |filter_item| pipe_it(input, filter_item) } | |
else | |
filter.call(input) | |
end | |
end | |
class Pipeline | |
def initialize(*filters) | |
@filters = filters | |
end | |
attr_accessor :filters | |
def call(input) | |
filters.inject(input) do |input, filter| | |
pipe_it(input, filter) | |
end | |
end | |
end | |
def pipable(input) | |
input.define_singleton_method(:|) do |filter| | |
pipable pipe_it(input, filter) | |
end | |
input | |
end | |
def pipe(input, *pipeline) | |
Pipeline.new(*pipeline).call(input) | |
end | |
# Let's define a few filters | |
Reverse = ->(string) { string.reverse } | |
Leet = ->(string) { string.gsub(/[aeiost]/,'a'=>'4','e'=>'3','i'=>'1','o'=>'0','s'=>'5','t'=>'7') } | |
Mooify = ->(string) { "Cow said: " + string } | |
Say = ->(string) { system %|say "#{string}"|; string } | |
TweetTo = Struct.new(:recipient) do | |
def call(input) | |
puts %|Tweeting "#{input}" to #{@recipient}!| | |
input | |
end | |
end | |
# Time to play with different approaches... | |
# 1 - We make the first element pipable and we can then just pipe through! | |
result = pipable("moo") | Reverse | Leet | Mooify | :downcase | TweetTo.new('@pcreux') | { delete: 'o' } | |
puts result | |
# => cw said: 00m | |
# 2 - Pipe without defining any `|` method | |
puts pipe("moo", Mooify, :upcase) | |
# => COW SAID: MOO | |
# 3 - Pipeline object | |
pipeline = Pipeline.new(Mooify, :downcase, { gsub: ["o", "a"] }) | |
pipeline.filters << ->(input) { input.gsub("moo", "maow") } | |
puts pipeline.call("moo") | |
# => caw said: maa | |
pipeline.filters.reverse! | |
puts pipeline.call("moo") | |
# => Cow said: maaw | |
# ZOMG! Multiplexing! | |
# "moo" => Mooify => :downcase => Reverse | |
# => :upcase => Reverse | |
p Pipeline.new(Mooify, [:downcase, :upcase], Reverse).call("moo") | |
# => ["oom :dias woc", "OOM :DIAS WOC"] | |
# Multi-Multiplexing... let me tell you... | |
p Pipeline.new(Mooify, [:downcase, :upcase], Reverse, [:reverse, Leet]).call("moo") | |
# => [["cow said: moo", "00m :d145 w0c"], ["COW SAID: MOO", "OOM :DIAS WOC"]] | |
Nice implementation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Honestly, I don't see the point in pipes. I mean, this is a nice demo of code, but my though is that the problem lies elsewhere, in modules used at wrapper.
In the firsts lines of this git, it's wrote (I shrink the quote)
So, there is a
RemoveSignature
module somewhere, with a singleton methodcall
. This is the problem you want to solve. And my guess is that the call you may want to make will beemail.body.remove_signature
I think this can be achieved by safely monkey patch the
String
class, using the Refinements of ruby 2+.Or, if you don't want to use that, you can extend the
body
variable with a module using definig instance methods, like this:Then, you will not have callings like
RemoveSignature.call(email.body)
, nor your 'pipe' need.