In Ruby, what does & do with a non-Proc argument such as &method(:normalize_email) or a Hash, and when does it raise TypeError?
answer
- the to_proc protocol
- Method#to_proc gives a lambda
- Hash#to_proc maps key to value
- no to_proc: TypeError
- to_proc must return a Proc
basics
~20 s& calls to_proc on any argument that is not already a Proc or nil and uses the result as the block. Method#to_proc and Hash#to_proc return lambdas; an object without to_proc, or whose to_proc returns a non-Proc, raises TypeError.
solid answer
~40 sAt a call site, `&obj` means "make `obj` the block". `nil` gives no block, a `Proc` is used unchanged, and any other object is sent `to_proc`. `Method#to_proc` returns a lambda that calls the method, so `raw_emails.map(&method(:normalize_email))` passes each address to a named helper. `Hash#to_proc` returns a lambda mapping a key to its value, so `logins.map(&emails_by_login)` looks each login up, giving `nil` (or the Hash's default) for a missing key. Your own class can define `to_proc` and be used with `&`. If the object has no `to_proc`, Ruby raises `TypeError` (`no implicit conversion of String into Proc`); if `to_proc` returns something that is not a `Proc`, it raises `TypeError` saying the conversion gave the wrong class. The resulting lambdas check their argument count like any lambda.
code
ruby · 10 linesdef normalize_email(raw) = raw.strip.downcase
raw = [" [email protected]", "[email protected] "]
raw.map(&method(:normalize_email)) # => ["[email protected]", "[email protected]"]
method(:normalize_email).to_proc.lambda? # => true
emails_by_login = {"ada" => "[email protected]"}
%w[ada bob].map(&emails_by_login) # => ["[email protected]", nil]
%w[ada].map(&"upcase") # TypeError: no implicit conversion of String into Procgo deeper
Recall that & calls to_proc on non-Proc objects and that &method(:name) and a Hash both work because they define to_proc.
Explain the nil, Proc, to_proc order, the two TypeError cases, and that Method#to_proc and Hash#to_proc return lambdas that check arity.
Judge when &method(:helper), &hash or a custom to_proc improves code and when a silent nil or strict arity makes an explicit block safer.
Decide whether domain objects should expose to_proc as part of a team's API style, weighing concise call sites against discoverability and surprise.
## The to_proc protocol behind & In a Ruby method call, a **`&` before the last argument** supplies the call's block from an object. Ruby handles the object in this order: 1. **`nil`**: no block is passed. 2. **A `Proc`** (regular or lambda): used as the block unchanged. 3. **Anything else**: Ruby calls **`to_proc`** on it and uses the returned `Proc`. The third rule is a small protocol. Any class that defines `to_proc` returning a `Proc` works with `&`. Several core classes implement it: | Object after `&` | `to_proc` returns | Effect of `&obj` | |---|---|---| | `:email` (Symbol) | lambda calling `email` on its first argument | `map(&:email)` calls each element's `email` | | `method(:normalize_email)` (Method) | lambda calling that method | each value is passed to the method | | `emails_by_login` (Hash) | lambda doing `hash[key]` | each value is looked up as a key | ## Method objects: &method(:name) `Kernel#method` returns a `Method` object for a named method. With `&`, **`Method#to_proc`** turns it into a lambda, which lets a named helper stand in for a block: ```ruby def normalize_email(raw) = raw.strip.downcase [" [email protected]", "[email protected] "].map(&method(:normalize_email)) # => ["[email protected]", "[email protected]"] ``` Points to remember: - The resulting proc is a **lambda**, so it enforces the method's arity. `each_with_index(&method(:normalize_email))` yields two values to a one-argument method and raises `ArgumentError`. - `method(:name)` resolves the method **when you call `method`**, so it captures the receiver at that moment. - `Kernel#method` also finds **private** methods (unlike `public_method`), so a private helper of the current object, or a top-level `def` like the one above, works with `&method(:name)`. ## Hashes: & with a lookup table **`Hash#to_proc`** (since Ruby 2.3) returns a lambda that takes a key and returns the value: ```ruby emails_by_login = {"ada" => "[email protected]", "lin" => "[email protected]"} %w[ada lin bob].map(&emails_by_login) # => ["[email protected]", "[email protected]", nil] ``` A missing key yields `nil`, or the Hash's default value if one is set, exactly like `hash[key]`. That makes it a compact way to translate identifiers, but a silent `nil` can hide a typo; use `fetch` in a literal block when a missing key should fail. ## Your own to_proc Any object can opt in: ```ruby class EmailDomain def initialize(domain) @domain = domain end def to_proc = ->(user) { user.email.end_with?("@#{@domain}") } end users.select(&EmailDomain.new("example.com")) ``` This reads well when the object is a reusable rule. Keep `to_proc` cheap and side-effect free, because it runs every time the object is passed with `&`. ## When & raises TypeError The conversion is checked: - An object with **no `to_proc`** raises `TypeError`, for example `users.each(&"email")` gives `no implicit conversion of String into Proc`. Strings do not convert; only the Symbol form works. - A `to_proc` that returns **something other than a `Proc`** raises `TypeError` with `can't convert X to Proc (X#to_proc gives Y)`. - The error is raised **at the call site**, before the callee runs. ## Common mistakes 1. **Expecting a String to work like a Symbol.** `&"email"` raises `TypeError`; convert with `&:email` or `&"email".to_sym` only when the name is trusted. 2. **Forgetting lambda arity.** `&method(:helper)` and `&hash` both produce lambdas, so iterators that yield two values, such as `each_with_index`, raise `ArgumentError` for one-argument helpers. 3. **Hiding misses.** `&hash` turns unknown keys into `nil`, which may surface far away as `NoMethodError` on `nil`; prefer a block with `fetch` when every key must exist. 4. **Doing work in to_proc.** A `to_proc` that loads data or logs runs on every `&obj` call; keep it a pure constructor of a lambda. ## Choosing a form - `&:name` for one zero-argument method on each element. - `&method(:helper)` to reuse a named helper that takes each element as its argument. - `&hash` for a quick lookup where `nil` for unknown keys is acceptable. - A literal block whenever the logic needs arguments, conditions or error handling; clarity beats cleverness.
- What happens if a class defines to_proc but it returns a String?Passing that object with `&` raises `TypeError` with a message like `can't convert Rule to Proc (Rule#to_proc gives String)`. Ruby checks that the conversion produced a `Proc` before using it as the block, and raises at the call site.
- Why can users.each_with_index(&method(:send_welcome)) raise ArgumentError when send_welcome takes one argument?`Method#to_proc` returns a lambda, and lambdas check their argument count. `each_with_index` yields two values, the user and the index, so the one-argument method receives two and raises `ArgumentError`. A literal block with `|user, _index|` avoids it.
saying these in an interview costs you the question
- & works only with Symbols and Procs
- &"email" works the same way as &:email
- &method(:helper) produces a lenient regular proc
- Hash#to_proc raises KeyError for a missing key
- An object without to_proc passed with & is silently ignored