The Pipeline
Refinement module bundles a method duo that builds
a clean and clever pure-Ruby solution to rightward method piping.
A reminder that Refinement modules are activated with using
and last only
for that module/class block or file (if top-level). See: refinements.rdoc
(on docs.ruby-lang.org)
Here’s the example from Kernel#then
straight off the Ruby 3.2 docs:
require 'open-uri'
require 'json'
construct_url(arguments).
then {|url| URI(url).read }.
then {|response| JSON.parse(response) }
With the new Refinement active: (Oh gosh, this looks like a brand-new language!)
using Pipeline
arguments.then_pipe(
`construct_url`,
`URI`,
:read,
JSON.`:parse
)
We traditionally pipe methods leftwards as nesting arguments:
def parse(string) = …
do_stuff = ->(a){ … }
# This is even more incomprehensible if omitting round parentheses (poetry mode)
STDOUT.puts( # different-scope method
do_stuff.call( # Proc
parse( # same-scope method
input
)
)
)
The trending Rightward Operations improve readability –
especially for wordy expressions like the above –
by matching English’s left-to-right writing direction.
We currently accomplish this with Object#then
and light-weight Procs:
input
.then { parse _1 }
.then { do_stuff.call _1 }
.then { STDOUT.puts _1 }
We alternatively can deconstruct the corresponding #to_proc
objects with &
:
# heck, these `method` calls can't even go poetry mode
input
.then(&method(:parse))
.then(&do_stuff)
.then(&STDOUT.method(:puts))
The Refinement module Pipeline
introduces Object#then_pipe
to
eliminate repeating then(&…)
. It gives a beautiful vibe similar to
that of the Pipe Operator |>
in some other languages (namely Elixir).
using Pipeline
input.then_pipe(
method(:parse),
do_stuff,
STDOUT.method(:puts)
)
However, unlike Lisp-1 languages like Python or JavaScript,
Lisp-2s like Ruby face the additional challenge of namespace disambiguation.
Ruby solves it with the reflection methods method
& co., but, as seen above,
their verbosity makes them eyesores inside otherwise elegant syntax.
Therefore, Pipeline
further improves the grammar by replacing the `…`
syntax (powered by Kernel#`
) with an alias
of the bulky Object#method
:
using Pipeline
input.then_pipe(
`parse`,
do_stuff,
STDOUT.`:puts
)
- It is the backend of both
`…`
and%x{…}
syntaxes –self.`:meth
can instead be%x:meth:
or`meth`
. - A Ruby script is not (strictly) a Shell script. User system differences aside,
summoning subshells should be the last resort when there are no Ruby/Gem APIs.
Dedicating the
`
char to subshells is a waste; e.g., you typically see$(…)
in bash rather than`…`
.Pipeline
assigns#`
a new and recurrent purpose; it alsoalias
es the originalKernel#`
toObject#sys
in the event of its preference.
- The new
#`
ignores method visibilities (private
orprotected
).- This is a Ruby limitation – one can bypass visibilities with
Object#method
andMethod#call
regardless of this Refinement. A security engineer would love reflection APIs that respect visibilities. - The original design for the new
#`
was to matchObject#public_method
, but it couldn’t retrieve oneself’s ownprivate
andprotected
methods.
- This is a Ruby limitation – one can bypass visibilities with
#then_pipe
cannot pass additional arguments at each step.- The current
Object#then
-based solution is as good as it can get – There are no syntactical benefits to avoid a block while calling rightwards. Hey, unlike#then_pipe
, it welcomes (inner) blocks.# a lambda – a Proc with fixed arities, unlike regula-o’ procs (or blocks) do_stuff = ->(a, o, z){ … } # This essentially wraps `do_stuff` in a one-arg block (as in F#). # The `_1` resembles Hack’s special pipe variable `$$`. o.then { do_stuff.(a, _1, z) } # The previous, adapted for `then_pipe` o.then_pipe ->{ do_stuff.(a, _1, z) } # Pure-rightward solution [a, o, b].then { do_stuff.(*_1) }
- The current
https://gist.github.com/ParadoxV5/4f563e609decbdac07d40e5f2dead751
Copyright (c) 2023 ParadoxV5
Licensed under the Universal Permissive License v 1.0