From: "shugo (Shugo Maeda) via ruby-core" Date: 2026-10-01T07:53:49+00:00 Subject: [ruby-core:126903] [Ruby Feature#22392] Allow refinements to define && and || Issue #22392 has been updated by shugo (Shugo Maeda). Description updated alanwu (Alan Wu) wrote in #note-1: > Please no. Changing non-method call operations to be method calls has disastrous time and space cost for everyone involved, JIT or not. If accepted, this would be yet another case of refinement making people that do not use it pay in terms of compute. Fair point. I've implemented a PoC: https://github.com/ruby/ruby/pull/19133 I tried to reduce time and space cost for code that does not use the feature, and there are some numbers in the PR description, but the cost for non-users is not zero. To reduce the cost more, I have changed the proposal: conditions such as `if a && b` and statements that discard the value compile exactly as today and never reach a hook, because I could not find a use case for them. kddnewton (Kevin Newton) wrote in #note-2: > Please don't do this. This is going to start impacting &&= and ||=, which already have very complex semantics depending on what is on the left-hand side of the expression. This is going to be very difficult to reason about. I agree that supporting `&&=` and `||=` is too complex, so this proposal does not change their behavior. ---------------------------------------- Feature #22392: Allow refinements to define && and || https://bugs.ruby-lang.org/issues/22392#change-119287 * Author: shugo (Shugo Maeda) * Status: Open * Target version: 4.1 ---------------------------------------- ## Abstract I propose to allow refinements to define `&&` and `||`, so that DSLs can build expressions such as `name == "x" && age > 20`. The right-hand side is evaluated before the method is called, so short-circuiting is not customizable. ## Motivation Query builders and similar DSLs want to capture boolean expressions written in a block: ```ruby User.where { name == "x" && age > 20 } ``` Today such DSLs have to use `&` and `|` instead, which bind tighter than `==` and `>`, so every operand needs parentheses: `(name == "x") & (age > 20)`. ## Proposal `&&` and `||` can be defined as methods in refinements: ```ruby module QueryDSL refine Condition do def &&(other) = And.new(self, other) def ||(other) = Or.new(self, other) end end def User.where(&block) = build(block.refined(QueryDSL).call) User.where { name == "x" && age > 20 } # => And(Eq(name, "x"), Gt(age, 20)) ``` Semantics of `a && b` (`a || b` is symmetric): * If the class of `a` has `&&` defined by a refinement active in the current scope, `b` is evaluated and `a.&&(b)` is the result. * Otherwise, the behavior is unchanged. With a hook, the result is always `a.&&(b)`, which never short-circuits. Details: * `&&` and `||` can be defined only in refinements. Defining them elsewhere raises NameError. A hook changes when the right-hand side is evaluated, so its effect should be limited to code that opted in by `using` or `Proc#refined`, rather than reaching every method that receives such an object. There are no default methods such as `Object#&&`, so `super` is not available in a hook. * ~~The hooks also apply in conditions such as `if a && b`, so that `x = a && b; if x` and `if a && b` behave the same.~~ Conditions such as `if a && b`, and statements that discard the value, do not use the hooks and compile as today; only the value of `a && b` can reach a hook. A hook cannot change how a condition is tested, so hooking conditions would only add cost. * `and` and `or` behave the same as `&&` and `||`. * `&&=` and `||=` do not use the hooks, to keep the memoization idiom. * `def &&`, `def ||`, `:&&` and `:||` become valid syntax. ## Prior art Scala and Swift treat `&&` and `||` as ordinary methods, so DSLs such as Slick define them on their own types, and the compiler resolves them statically. C# lets a type define `&`, `|`, `true` and `false`, and expands `x && y` to `T.false(x) ? x : x & y`. C++ allows overloading `&&` globally, which is discouraged because short-circuiting is lost for every user of the type. Python, Kotlin, Rust and Julia don't allow it; their DSLs use `&` and `|` with the same precedence problem as in Ruby, and PEP 335, which proposed overloadable `and`/`or`, was rejected for the cost it added to every `and`/`or` and for its complexity. This proposal follows Scala and Swift in dispatching on the receiver, at run time since Ruby is dynamically typed, and limits the definitions to refinements so that, unlike in C++, code outside the DSL keeps short-circuiting. Code that doesn't use the hooks pays nothing, unlike in PEP 335. The right-hand side is evaluated eagerly rather than passed lazily: a hook that builds an expression needs its value in any case, so laziness would add complexity without a use. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/