In Ruby, how do you define +, unary minus and [] on a Temperature class, and which operators can you not define?
answer
- a + b is a.+(b)
- left operand decides
- -@ and +@ for unary
- += comes free from +
- && and || are not methods
basics
~20 ssolid answer
~40 sIn Ruby `a + b` is a method call, `a.+(b)`, so the **left operand's class** decides what happens. You define binary operators with one parameter (`def +(other)`), unary minus and plus with an `@` suffix and no parameters (`def -@`), and element access with `def [](scale)` and `def []=(scale, value)`, which may take any number of arguments. You never define `+=`: `t += other` expands to `t = t + other`, so defining `+` is enough, and it should return a **new** object rather than mutate `self`. What you cannot define: `&&`, `||`, `and`, `or`, plain `=`, the compound assignments and the ternary; `not obj` does call your `!`. Precedence cannot be changed either.
code
ruby · 16 linesclass Temperature
attr_reader :celsius
def initialize(celsius) = @celsius = celsius
def +(other)
raise TypeError, "not a Temperature" unless other.is_a?(Temperature)
Temperature.new(celsius + other.celsius)
end
end
t = Temperature.new(20)
t += Temperature.new(5) # t = t + other; no += method exists
t.celsius # => 25
t.+(Temperature.new(1)).celsius # => 26, the same call spelled outgo deeper
Recall that a + b calls a.+(b), so defining def +(other) in a class gives that class the operator.
Explain the @ suffix for unary operators, [] and []= arities, why += comes free from +, and which operators are syntax rather than methods.
Argue for immutable results from arithmetic operators, explicit TypeError on unsupported operands, and restraint in overloading so domain code stays readable.
Decide where operator overloading helps a domain model, such as money or units, and where named methods communicate intent better across a large codebase.
## Operators are method calls Most Ruby operators are **syntactic sugar for method calls**. `a + b` sends the message `+` to `a` with `b` as its argument; you can even write `a.+(b)`. Consequences: - The **left operand's class** decides the behaviour. `temp + other` runs `Temperature#+`, while `5 + temp` runs `Integer#+`. - You overload an operator by defining a method with that name in your class. - Parsing does not change: **precedence and associativity are fixed** by the grammar, so a custom `*` still binds tighter than your `+`. ## Defining the common ones ```ruby class Temperature attr_reader :celsius def initialize(celsius) @celsius = celsius end def +(other) Temperature.new(celsius + other.celsius) end def -@ Temperature.new(-celsius) end def [](scale) scale == :f ? celsius * 9 / 5.0 + 32 : celsius end def to_s = "#{celsius} C" end a = Temperature.new(20) puts a + Temperature.new(5) # 25 C puts(-a) # -20 C a[:f] # => 68.0 ``` | Operator | Method to define | Parameters | |---|---|---| | `a + b`, `a - b`, `a * b`, `a / b`, `a % b`, `a ** b` | `+`, `-`, `*`, `/`, `%`, `**` | exactly one | | `a << b`, `a & b`, `a \| b`, `a ^ b` | `<<`, `&`, `\|`, `^` | exactly one | | `-a`, `+a` | `-@`, `+@` | none | | `!a`, `~a` | `!`, `~` (the `@` suffix is optional) | none | | `a[k]` | `[]` | zero or more | | `a[k] = v` | `[]=` | one or more, value last | The `@` suffix exists only to tell unary minus and plus apart from binary minus and plus. Defining `def -` with no parameters does not make `-temp` work; `-temp` looks for `-@`. ## What you get for free, and what you cannot define 1. **Compound assignment is derived.** `t += other` is rewritten to `t = t + other`, so `+=` works as soon as `+` exists; `def +=` is a syntax error. The same holds for `-=`, `*=`, `<<=` and the rest. 2. **Element assignment follows assignment rules.** `a[:c] = 21` evaluates to `21` whatever `[]=` returns. 3. **Logical operators are not methods.** `&&`, `||`, `and` and `or` short-circuit inside the parser and cannot be redefined. `not obj` is different: it calls `obj.!`, so a custom `!` affects it. 4. **Plain assignment, the ternary `?:` and `defined?` are syntax**, not methods. ## Element reference and core examples `[]` and `[]=` are the most flexible operator methods because their arity is yours to choose: - `def [](row, col)` makes `grid[2, 3]` work, and `def []` with no parameters makes `obj[]` legal. - `def []=(row, col, value)` receives the assigned value **last**, so `grid[2, 3] = :x` calls it with `(2, 3, :x)`. - The expression `grid[2, 3] = :x` evaluates to `:x`, whatever `[]=` returns, because it is assignment syntax. Core classes use the same mechanism. `String` defines unary `-@` and `+@`, and `Array#[]` accepts an index, a start and length, or a range, all through one method. Nothing about operators is reserved to built-in types. ## Design guidance - **Return a new object from arithmetic operators.** Callers expect `a + b` to leave `a` unchanged, and `a += b` depends on rebinding a variable, not on mutation. - **Keep the meaning unsurprising.** Ruby's documentation warns that giving `+` a meaning other than adding confuses readers. - **Check the argument's type** when mixing types, and raise `TypeError` for operands you do not support rather than failing deep inside. - **Reversed operands are a separate problem.** `5 + temp` never reaches `Temperature#+`; making that work goes through the numeric conversion protocol, not through defining another operator. - Comparison and equality operators (`==`, `<=>`, `<`) are also definable methods, with their own contracts around `Comparable` and hash keys.
- In Ruby, what happens when a Temperature class defines `def -` with no parameters and code writes `-temp`?It raises `NoMethodError` for `-@`, because unary minus dispatches to the method named `-@`, not `-`. A binary `temp - other` would then fail with `ArgumentError`, since that `-` takes no arguments. Define `-@` for negation and `-(other)` for subtraction.
- Why should `Temperature#+` return a new object instead of changing `self` in Ruby?Callers read `a + b` as a pure expression and keep using `a`. `a += b` also works by rebinding `a` to the result, so mutation adds nothing and surprises any other reference to the same object. Mutating operations belong in named methods, often with a `!` suffix.
saying these in an interview costs you the question
- To support t += other you must define a += method.
- Unary minus is defined as def -(other) with an ignored argument.
- You can overload && to combine two Temperature objects.
- Defining * in your class can make it bind looser than +.
- 5 + temp calls Temperature#+ because Ruby tries both operands.