1

I often use the ||= operator to cut down on redundant calls. I want to take something like this

@my_variable ||= my_calculation_method

and turn it into

@my_variable.assign { code_block }

using this idea

class Object
    def assign
      if self.instance_of? NilClass
          self = yield
      end
    end
end

As you may have already guessed, assigning self does not make sense and does not work.

How do you go about accessing the @my_variable pointer within the assign method to modify the value?

1
  • You could replace if self.instance_of? NilClass (self. not needed, btw) with if nil?. (see Object#nil?.) For many subclass of Object--but not all--you could write replace yield if nil?. Commented Aug 30, 2016 at 20:03

3 Answers 3

5

If your initialization logic is non-trivial and spans several lines (which, I assume, was the premise of the question), here's what you can do, in idiomatic ruby:

@my_variable ||= begin
  # code block
end

What I normally do is splitting the logic in two parts. Memoization is separated from calculation. You have this in your first line.

def stats
  @stats ||= compute_stats
end

def compute_stats
  ...
end

I find this pattern of XXX + compute_XXX quite easy to recognize/follow.

Sign up to request clarification or add additional context in comments.

Comments

3

self = x is completely impossible. You can't tell an object what it is, that never changes. If it's created with a particular class, it dies with that very same class. All you can do is make modifications to that object or its associated class, but the object's class is immutable.

The only reason @x ||= y works is because @x is simply a variable, it's a pointer to an object, which is something you can reassign. Saying @x = z does not change the object, it only changes the pointer. self is a special case, it refers to the context you're operating within, and that cannot be reassigned.

Keep in mind if by some fluke your self = yield call succeeded you'd redefine what nil is program-wide.

This whole effort is misguided, the ||= pattern is pretty much idiomatic Ruby at this point. If you want to improve on that pattern then what you want to do is make some alternative to memoize.

2 Comments

Is "completely impossible" less likely than "impossible"?
There's a lot of "impossible" things in Ruby that are simply crazy to think about doing. For example: Redefining what + and - mean on numbers, or making true show up as false when inspect-ed.
2

As tadman says, you can't write self =. But you could write a method which takes the name of the instance variable and sets it.

You have comments saying this is 'inelegant' or 'misguided'. Although I don't share this judgement, it is good practice to write unpretentious code that uses standard conventions as much as possible. This helps others comprehend your code easily. I.e. it's probably not productive to write alternate syntaxes for such fundamental ruby methods like ||= ; other people will have to do lots of digging through your code to understand how things work.

That being said, in the interest of teaching how to make Ruby do what you want, I can suggest this Object method for this purpose:

class Object
    def assign(key)
      # make sure the key is a valid instance variable
      unless key =~ /^[A-Za-z0-9\_]+$/ 
        raise ArgumentError, "#{key} is not a valid ivar name"
      end

      if instance_variable_get("@#{key}").nil?
        instance_variable_set("@#{key}", yield)
      end
    end
end

# example
class Foo
  def set_bsd
    assign("bsd", yield)
  end
end
foo = Foo.new
foo.assign("my_ivar") { "val" }
foo
# => #<Foo:0x007fc2e4ccaca0 @asd=1>
foo.set_bsd { 2 }
foo
# => #<Foo:0x007fc2e4ccaca0 @asd=1 @bsd = 2>

although I would recommend against patching Object in this way - it's too generic a class and could have unintended conflicts with internal functionality. Rather, add this functionality to a custom class/module and either include the module or inherit the class.

3 Comments

That's a big gun you're giving :)
Although I think this sort of heavy-handed patching is a bad habit to get into, giving you +1 for sheer bravado. Do try and use \A and \z in your regular expressions as the ^ and $ counterparts are newline sensitive. The expression should be: \A\w+\z since \w is shorthand for [a-zA-Z0-9_].
FYI your regular expression is overly strict, for example @🐱 is a legal instance variable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.