Create a gist now

Instantly share code, notes, and snippets.

Proc Composition (>> and <<, with specs)
# See https://bugs.ruby-lang.org/issues/6284
# (It's probably going to happen, but they haven't figured out what they want
# to call it. I've chosen << and >> for now.)
module Patches
module Proc
module Composition
# (f << g)[x] == f[g[x]]
# (f << g).call(x) == f.call(g.call(x))
# "self" is "f" in the above.
def <<(g)
proc { |*args|
self.call(*g.to_proc.call(*args))
}
end
# The above, except "forwards".
# (f >> g)[x] == g[f[x]]
# (f >> g).call(x) == g.call(f.call(x))
# "self" is "f" in the above.
def >>(g)
proc { |*args|
g.to_proc.call(*self.call(*args))
}
end
end
end
end
# Examples of usage:
# to_s = proc {|x| x.to_s }
# to_upper = proc {|x| x.upcase }
# to_a = proc {|x| x.split('') }
#
# # Pipeline
# p (to_a << to_upper << to_s).call("Abc123") # => ['A', 'B', 'C', '1', '2', '3']
# p (to_s >> to_upper >> to_a).call("Abc123") # => ['A', 'B', 'C', '1', '2', '3']
RSpec.describe Patches::Proc::Composition do
let(:reverse) { proc {|x| x.reverse } }
let(:uppercase) { proc {|x| x.upcase } }
let(:ascii_nums) { proc {|x| x.split('').map(&:ord) } }
describe "<<" do
it "allows procs to be stuck together in a pipeline" do
expect((ascii_nums << uppercase << reverse).call("Hello World")).to eq [68, 76, 82, 79, 87, 32, 79, 76, 76, 69, 72]
end
it "doesn't matter which bits of the pipeline you stick together first" do # (is associative)
expect(
((ascii_nums << uppercase) << reverse).call("Hello World")
).to eq(
(ascii_nums << (uppercase << reverse)).call("Hello World")
)
end
it "matters what order the bits of the pipeline are stuck together" do # (is not commutative)
expect((ascii_nums << uppercase << reverse).call("Hello World")).to eq [68, 76, 82, 79, 87, 32, 79, 76, 76, 69, 72]
expect{(uppercase << ascii_nums << reverse).call("Hello World")}.to raise_error
expect{(uppercase << reverse << ascii_nums).call("Hello World")}.to raise_error
end
it "duck-types on #to_proc when composing" do
# More of an accident than anything. Symbol has to_proc(), which converts ":to_s" to "proc {|x| x.to_s }".
expect((reverse << :to_s).call(123)).to eq "321"
end
end
describe ">>" do
it "allows procs to be stuck together in a pipeline" do
expect((uppercase >> reverse >> ascii_nums).call("Hello World")).to eq [68, 76, 82, 79, 87, 32, 79, 76, 76, 69, 72]
end
it "doesn't matter which bits of the pipeline you stick together first" do # (is associative)
expect(
((uppercase >> reverse) >> ascii_nums).call("Hello World")
).to eq(
(uppercase >> (reverse >> ascii_nums)).call("Hello World")
)
end
it "matters what order the bits of the pipeline are stuck together" do # (is not commutative)
expect((uppercase >> reverse >> ascii_nums).call("Hello World")).to eq [68, 76, 82, 79, 87, 32, 79, 76, 76, 69, 72]
expect{(uppercase >> ascii_nums >> reverse).call("Hello World")}.to raise_error
expect{(ascii_nums >> uppercase >> reverse).call("Hello World")}.to raise_error
end
it "duck-types on #to_proc when composing" do
# More of an accident than anything. Symbol has to_proc(), which converts ":to_i" to "proc {|x| x.to_i }".
expect((reverse >> :to_i).call("123")).to eq 321
end
end
end
Patches::Proc::Composition
<<
duck-types on #to_proc when composing
allows procs to be stuck together in a pipeline
doesn't matter which bits of the pipeline you stick together first
matters what order the bits of the pipeline are stuck together
>>
duck-types on #to_proc when composing
allows procs to be stuck together in a pipeline
doesn't matter which bits of the pipeline you stick together first
matters what order the bits of the pipeline are stuck together
Finished in 0.3115 seconds
8 examples, 0 failures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment