Project

General

Profile

Actions

Feature #20202

closed

Memoized endless method definitions

Added by matheusrich (Matheus Richard) about 1 year ago. Updated about 1 month ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:116369]

Description

I propose introducing a shorthand for memoized endless method definitions:

class Foo
  def bar ||= :memoized_value

  # It should behave like
  def bar = (@bar ||= :memoized_value)
end

Not only is this shorter and (IMO) a natural follow-up for endless method definitions, but it's also a common pattern on Ruby codebases. It's very useful to decompose responsibilities into several objects:

class User
  def notifications_enabled? = settings.notifications?
  def enable_notifications = (settings.notifications = true)
  def disable_notifications = (settings.notifications = false)

  private

  def settings = @settings ||= Settings.new(self)
end

class User::Settings
  attr_writer :notifications

  def initialize(user)
    @user = user
    @notifications = false
  end

  def notifications? = @notifications
end

u = User.new
u.notifications_enabled? # => false
u.enable_notifications
u.notifications_enabled? # => true

In the example, the settings method could be rewritten as

  def settings ||= Settings.new(self)

Updated by rubyFeedback (robert heiler) about 1 year ago

matheusrich wrote:

it's also a common pattern on Ruby codebases

I am not sure it is that common. It is perhaps more common in
rails-specific code. For other regular ruby gems out there,
I don't see that many use endless methods nor memoization
(though admittedly I see more memoization than endless method
definitions; this could perhaps be explained by endless method
definitions being still fairly young, and many gem authors
prefer to not be "too quickly" on the most recent ruby version.
Rubular, for instance, still uses Ruby 2.5.9, oddly enough).

Personally I also find endless method definitions harder to
read; I kind of have to look more to the right side, whereas
in regular ruby code with "def foo; end", the latter being on
the same indent level as "def", I kind of look more towards the
left side, and the bottom area below "def". I am rather used
to seeing "def foo; end", although years ago I actually wanted
to see how ruby code would "feel" when we can omit end, like
in python (but without the mandatory ":").

I did not have endless method in mind when I wanted this, though,
and I kind of adjusted to having to type "end".

For instance:

def settings = @settings ||= Settings.new(self)
def notifications? = @notifications

I find this condensed variant harder to read.

Personally I am also interested in sawa's opinion about
the proposal here, as he recently suggested a shortcut
definition for endless method definitions (e. g. by
omitting "def").

In the example, the settings method could be rewritten as

def settings ||= Settings.new(self)

I kind of understand your point of view there and it would
omit some typing to do, right? So this may just be a different
style of writing ruby code. Personally, though, I am not a
big fan of these condensed one-liner variants.

Updated by matheusrich (Matheus Richard) about 1 year ago

I understand why some people might not like endless methods, but I don't think they're purely one-line methods (read @zverok (Victor Shepelev) 's take on this). Also they're already merged, so I guess it's out of topic dicussing that.

I am not sure it is that common. It is perhaps more common in
rails-specific code

I'd argue that using a helper object like that is pretty common even outside Rails code. Sometimes it is done via a macro to delegate, instead of an explicit method like I did.

Personally, though, I am not a
big fan of these condensed one-liner variants.

Then I guess my proposal helps with that? It would be shorter than ever to memoize a method.
def settings ||= Settings.new(self)

Updated by matheusrich (Matheus Richard) about 1 year ago

@palkan made it possible to play with what this feature would look like via Ruby Next.

Updated by matz (Yukihiro Matsumoto) about 1 year ago

  • Status changed from Open to Rejected

I don't accept this proposal for several reasons:

  • I don't see significant use-case
  • method set for a class should be stable for consistency/understandability
  • fragile method set could be bad for object shape and method caching

Matz.

Updated by systho (Philippe Van Eerdenbrugghe) about 1 month ago

(I'm not a frequent user of this board, I hope it is okay to comment on a closed ticket)

I believe there is a significant use case for this feature idea because it reveals something about the intention in a more terser way, and therefore less noisy.

Memoization is a frequent enough intention and the most frequent pattern for implementing it is @current_method_name ||= compute_value

This pattern is frequently not usable because of 2 reasons :

  • A) The computation does not hold in a one-liner
  • B) The result is a boolean (or anything that can be evaluated to false by || operator)

Case A does not really interest us here since endless methods are mostly about one-liners.
Case B is usually solved by using a different pattern :

def my_method
  return @my_method if defined?(@my_method)
  @my_method = compute_boolean_value
end

which turns case B into a multiliner and therefore prevents it being written as an endless method.
This is even worse with most style guidelines forcing a blank line after an early return statement, which is also quite frequent.

An additional issue happens when working in teams that will force the usage of defined?(@ivar) as the only valid way to do memoizing in order to maximize consistency within the team.

Because of these reasons, the memoization intention gets lost in noisy boilerplate and I believe this feature idea (or anything that would allow marking the memoization intention at the signature level) has a valid use case.

The two other bullet points about the method set are beyond my understanding so I cannot propose an argument.

There is an issue which has not been mentioned though : memoization is rather easy for method without arguments called on immutable objects.
But it becomes much more complex when those criteria are not met.

I do not know how realistic it would be to implement this feature in a way that would gracefully fail when some conditions are not met.

Actions

Also available in: Atom PDF

Like1
Like0Like0Like0Like0Like0