In Ruby, what do the >> and << operators return for Proc and Method objects, and which callable runs first in each?
answer
- f >> g: f first
- f << g: g first
- result is always a Proc
- any object with call
- TypeError: callable object is expected
basics
~20 sBoth return a new Proc. f >> g calls f with the arguments, then g with f's result; f << g calls g first, then f. The operand may be a Proc, a Method or any object responding to call.
solid answer
~40 s`>>` and `<<` are defined on `Proc` and `Method` and compose two callables into a new `Proc`, even when the receiver is a `Method`. `f >> g` reads left to right: the new proc calls `f` with all its arguments, then calls `g` with `f`'s single result. `f << g` reads right to left: `g` runs first and `f` receives its result. The operand may be a `Proc`, a `Method` or any object that responds to `call`, such as a class with a `self.call`; anything else raises `TypeError` (`callable object is expected`). Only the first callable to run receives the original arguments; the second always receives exactly one value. The result is a lambda when the first callable to run is a lambda, a `Method` or another non-proc callable.
code
ruby · 16 linesdouble = ->(x) { x * 2 }
inc = ->(x) { x + 1 }
(double >> inc).call(5) # => 11, double first
(double << inc).call(5) # => 12, inc first
class Upcaser
def self.call(text) = text.upcase
end
label = method(:String) >> Upcaser >> ->(s) { "[#{s}]" }
label.call(:total) # => "[TOTAL]"
label.class # => Proc
label.lambda? # => true
double >> 42 # TypeError: callable object is expectedgo deeper
Recall that >> runs the left callable first and << runs the right one first, and that both produce a new proc.
Explain that operands can be Procs, Methods or any object with call, that the second step gets one value, and that TypeError guards non-callables.
Build readable formatting pipelines from tested steps, keep one composition direction, and account for lambda-ness and the single-value handoff.
Decide whether composed callables or explicit objects make pipelines easier to debug, trace and extend for the team.
## Composition in Ruby **Function composition** builds one callable out of two by feeding the output of one into the other. Ruby provides it as operators on `Proc` and `Method`: | Expression | Runs first | Then | Reads | |---|---|---|---| | `f >> g` | `f` with the arguments | `g` with `f`'s result | left to right | | `f << g` | `g` with the arguments | `f` with `g`'s result | right to left | With `double = ->(x) { x * 2 }` and `inc = ->(x) { x + 1 }`: - `(double >> inc).call(5)` computes `inc.(double.(5))`, which is `11`. - `(double << inc).call(5)` computes `double.(inc.(5))`, which is `12`. ## What comes back 1. The result is always a new **`Proc`**. `method(:parse) >> method(:format_row)` returns a `Proc`, not a `Method`, because `Method#>>` converts the method with `to_proc` first. 2. The **first callable to run** receives every argument the composed proc is called with, including keywords and a block. 3. The **second callable** receives exactly one argument: the first one's return value. If you need to pass several values along, return an Array or a small object. 4. **Lambda-ness** follows the callable that runs first. For `f >> g` it is copied from `f`; for `f << g` it comes from `g` when `g` is a `Proc`, and a `Method` or other callable counts as a lambda. Composing two lambdas therefore gives a proc whose `lambda?` is `true`. ## What may be composed The operand can be: - another **`Proc`** or lambda, - a **`Method`** object such as `File.method(:read)`, - **any object that responds to `call`**, for example a class with `def self.call(text)`. The `Proc#>>` documentation shows a pipeline built exactly this way: `File.method(:read) >> Parser >> proc { |data| ... }`. Passing something with no `call` method, such as an Integer or a String, raises `TypeError` with `callable object is expected` when the composition is built, not when it is later called. ## A report pipeline For a report generator, composition turns small steps into one formatter: ```ruby normalize = ->(row) { row.transform_values { |v| v.to_s.strip } } render = PriceSheet.new("EUR").method(:format_row) terminate = ->(line) { line + "\n" } formatter = normalize >> render >> terminate ReportGenerator.new(formatter: formatter) ``` Each step can be tested alone, and the pipeline reads in the order the data flows. Choose one direction for a codebase; `>>` reads in execution order, which many teams find easier to follow. ## Composition versus a hand-written lambda | Aspect | `normalize >> render >> terminate` | `->(row) { terminate.(render.(normalize.(row))) }` | |---|---|---| | Reads in data-flow order | Yes | Inside out | | Adding a step | One more `>>` | Another level of nesting | | Passing extra values between steps | Not possible, one value per handoff | Possible | The hand-written lambda is the fallback when a later step needs more than the previous step's result, such as the row index. ## Pitfalls - Mixing up the direction of `<<` and `>>`, which silently applies steps in the wrong order when both steps accept the same type. - Expecting the second step to receive the original arguments as well; it receives only the first step's result. - Composing a step that returns `nil` on bad input: the next step receives `nil` and fails far from the cause. - Building the composition inside a loop; build it once and reuse it.
- In f >> g, what does g receive if f returns an Array?It receives that Array as its single argument. The composed proc passes exactly one value to the second callable. A lambda `g` declared with two parameters raises `ArgumentError`, while a plain proc with two parameters would auto-splat the Array.
- Can a class be used directly as a step in a composition?Yes, if it responds to `call`. A class that defines `def self.call(text)` can appear as `step >> Parser`, because composition accepts any object with a `call` method, not only `Proc` and `Method` instances.
saying these in an interview costs you the question
- f >> g runs g first and then f
- Method#>> returns a Method object
- Both composed callables receive the original arguments
- Only Proc objects can be composed
- A non-callable operand fails only when the composition is called