In Ruby 4.0, what does `def fahrenheit = celsius * 9 / 5.0 + 32` define, and what does that def expression itself return?
answer
- one expression after =
- parentheses required for parameters
- no setters, SyntaxError
- def evaluates to a Symbol
- private def works on one line
basics
~20 sIt defines an instance method fahrenheit whose body is that one expression; endless defs arrived in Ruby 3.0. The def expression itself evaluates to the Symbol :fahrenheit, which is why private def fahrenheit = ... works.
solid answer
~40 sIt is an **endless method definition**, added in Ruby 3.0: `def name(params) = expression` defines a method whose body is that single expression, equivalent to a `def ... end` with one line. Parameters need parentheses, setters (`name=`) cannot be written this way, and since 3.1 the body may be a command call such as `def greet = puts "hi"`. Separately, every `def` is itself an expression that evaluates to the method's name as a **Symbol**, so this line returns `:fahrenheit`. That is what lets methods taking a name wrap a definition: `private def fahrenheit = ...` defines the method and passes `:fahrenheit` to `private` in one line. One trap: a trailing `if` modifier guards the whole definition, not the body.
code
ruby · 14 linesclass Temperature
attr_reader :celsius
def initialize(celsius) = @celsius = celsius
def fahrenheit = celsius * 9 / 5.0 + 32
def self.parse(text) = new(Float(text)) rescue nil
private def kelvin = celsius + 273.15
end
Temperature.new(30).fahrenheit # => 86.0
Temperature.parse("abc") # => nil
Temperature.new(30).respond_to?(:kelvin) # => falsego deeper
Recall the shape def name(args) = expression and that it returns that expression, like a one-line def ... end.
Explain the restrictions (parentheses, no setters), the 3.1 command-call body, and why def returning :name lets private def work on one line.
Spot the trailing-if trap that silently skips a definition, and set Style/EndlessMethod and Style/AmbiguousEndlessMethodDefinition so the team uses the form only where it reads clearly.
Decide whether endless defs belong in the codebase's style at all, weighing terseness for small derived values against churn when bodies grow and mixed styles in review.
## The endless method definition Ruby 3.0 added a shorthand for methods whose body is **exactly one expression**: ```ruby class Temperature attr_reader :celsius def initialize(celsius) = @celsius = celsius def fahrenheit = celsius * 9 / 5.0 + 32 def freezing? = celsius <= 0 def scaled(factor) = celsius * factor end Temperature.new(30).fahrenheit # => 86.0 ``` The form is `def name = expression` or `def name(params) = expression`. There is no `end`, and the method returns the value of that expression exactly as a one-line `def ... end` would. Ruby's syntax documentation calls it the shorthand syntax, and its 3.0 release notes introduced it as an experimental feature. ## Rules and restrictions | Rule | Example | Result | |---|---|---| | Parameters need parentheses | `def add_one value = value + 1` | `SyntaxError` | | Setters are not allowed | `def celsius=(v) = @celsius = v` | `SyntaxError` | | Command call as the body (3.1+) | `def greet = puts "hi"` | valid | | `rescue` modifier | `def parse(s) = Float(s) rescue nil` | rescue belongs to the body | | `if` modifier | `def cold? = true if winter` | guards the **definition** | A few points behind the table: - The setter restriction has its own error message: a setter method cannot be defined in an endless method definition. - Ruby 3.1 allowed command calls without parentheses in the body. Its release notes said `private def foo = puts "Hello"` did not parse; Ruby 4.0's own test suite asserts that it does. - The two modifiers bind differently. The `rescue` modifier becomes part of the method body. A trailing `if`, `unless`, `while` or `until` applies to the whole `def`, so when the condition is false the method is never defined. RuboCop's pending cop `Style/AmbiguousEndlessMethodDefinition` flags this; write `def cold? = (true if winter)` if the condition belongs in the body. - RuboCop's pending `Style/EndlessMethod` cop defaults to `allow_single_line`, which rejects endless definitions spread across several lines. ## def is an expression that returns a Symbol Since Ruby 2.1, a `def` is not just a statement: it **evaluates to the method's name as a Symbol**. This is true for regular and endless forms and for singleton definitions such as `def self.from_fahrenheit(f)`. ```ruby (def fahrenheit = 86.0) # => :fahrenheit (def self.parse(s); end) # => :parse ``` That return value exists so that methods which accept a method name can wrap a definition directly: 1. **Visibility on one line.** `private def fahrenheit = ...` defines the method, then calls `private(:fahrenheit)`. What `private` does to the method is a visibility question; the part that belongs here is that `def` supplies the Symbol it needs. 2. **Other name-taking methods.** `module_function def helper ... end` works the same way, as does any class-level macro of your own that accepts a Symbol, for example one that wraps a method with caching. ## Common traps in interviews and reviews 1. **Believing `def` returns `nil`.** That was true before Ruby 2.1; since then it returns the Symbol, which is what `private def` relies on. 2. **Expecting a trailing `if` to guard the body.** It guards the definition; parenthesise the body to keep the condition inside. 3. **Writing a writer in endless form.** The parser rejects it, so writers stay as `def celsius=(value) ... end` or come from `attr_writer`. 4. **Treating endless methods as a different kind of method.** They are ordinary methods: the same visibility rules, the same parameters and arity, the same Symbol from the `def` expression. Only the source syntax differs. ## When to use the endless form - **Good fits:** small derived values (`fahrenheit`), predicates (`freezing?`), simple delegations and conversions such as `def to_s = "#{celsius} C"`. - **Poor fits:** anything with branching, side effects in sequence, or a body you would want to extend later; converting back to `def ... end` is churn in the diff. - **Never:** writers, which the parser rejects, and conditional definitions written with a trailing `if` by accident. ## Key points - `def name(params) = expr` is a one-expression method, available since Ruby 3.0. - Parentheses are mandatory around parameters; setters are a `SyntaxError`. - A trailing `rescue` modifier is part of the body; a trailing `if` wraps the definition. - Every `def` evaluates to a Symbol, which lets `private def ...` and similar wrappers work on one line.
- In Ruby 4.0, what does `def cold? = true if ENV["WINTER"]` do when WINTER is not set?It defines nothing. The trailing `if` modifier applies to the whole `def` expression, so the definition runs only when the condition is truthy and `cold?` is then missing. To keep the condition in the body, write `def cold? = (true if ENV["WINTER"])`.
- Why does `private def fahrenheit = celsius * 9 / 5.0 + 32` work as a single line in Ruby?The `def` runs first and evaluates to the Symbol `:fahrenheit`, which becomes the argument to `private`. So one line both defines the method and names it for `private`. Any method that takes a method name, such as `module_function`, can wrap a `def` the same way.
saying these in an interview costs you the question
- A def expression returns nil, so it cannot be passed to another method.
- An endless def can define a setter such as celsius=.
- Parameters on an endless def work without parentheses.
- def cold? = true if winter defines cold? returning nil when winter is false.
- Endless defs return a Method object instead of a Symbol.