1

I'm writing a compiler in Ruby, and I have many classes where instance methods will modify instance variables. For example, my lexer (the part that finds tokens in the code), works like this:

class Lexer
  attr_accessor :tokens

  def initialize(input)
    @input = input
    @tokens = nil
  end

  def lex!
    # lex through the input...
    #   @tokens << { lexeme: 'if', kind: :if_statement }

    @tokens
  end
end

lexer = Lexer.new('if this then that')
lexer.lex! # => [ { lexeme: 'if', kind: :if_statement }, ... ]
lexer.tokens # => [ { lexeme: 'if', kind: :if_statement }, ... ]

Is this a valid practice? Or, should I use an approach where the methods (like #lex) take in the input and return the results, without modifying the instance variables?

class Lexer
  def initialize
  end

  def lex(input)
    # lex through the input...
    #   tokens << { lexeme: 'if', kind: :if_statement }

    tokens
  end
end

lexer = Lexer.new
lexer.lex('if this then that') # => [ { lexeme: 'if', kind: :if_statement }, ... ]

2 Answers 2

1

Either is valid depending on your design goals and how the lexer will be used.

Do you actually need an instance variable containing the tokens? E.g., does the lexer need to use them for anything else?

If not, I'd lean towards not using an instance variable, and you have less to reason about ("will this instance variable be changed by other system interactions", for example).

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

2 Comments

No, it doesn't do anything else with @tokens. I think that pretty much answers my question, thanks!
@EthanTurkeltaub It's kind of the functional vs. non-functional approach, really.
1

It is by all means valid to mutate an instance variable in one of your instance's methods (that's why they exist in the first place). Whether some piece of data should be kept in an instance variable or not, however, depends on how your instance is to be used.

You can think of instance variables as the state of your instance, and of your instance as a state manager for your instance variables. If you have a Counter class, for example, with increment and decrement methods, the current value of your counter would clearly be a piece of the state, and these methods would mutate it.

A good rule of thumb is : are going to pass data back and forth ? If you are, then it probably should be an instance variable. If you are not, then it's not really part of your instance's state, and should be kept outside of it. In your case :

lexer = Lexer.new
tokens = lexer.lex('my statement')
lexer.do_something_else tokens # if you do that, then lexer should be aware of the tokens and keep it in an instance variable, if not: why bother ?

As a conclusion, it all depends whether Lexer is a functional utility class or if its instances need to be stateful.

Comments

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.