Applying refinements to a block

177 Views Asked by At

Let's say I'm designing a domain-specific language. For this simplified example, we have variables, numbers, and the ability to add things together (either variables or numbers)

class Variable
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

class Addition
  attr_reader :lhs, :rhs

  def initialize(lhs, rhs)
    @lhs = lhs
    @rhs = rhs
  end
end

Now, if I want to represent the expression x + 1, I write Addition.new(Variable.new('x'), 1).

We can make this more convenient by providing a + method on Variable.

class Variable
  def +(other)
    Addition.new(self, other)
  end
end

Then we can write Variable.new('x') + 1.

But now, suppose I want the opposite: 1 + x. Obviously, I don't want to monkey-patch Integer#+, as that disables ordinary Ruby integer addition permanently. I thought this would be a good use case for refinements.

Specifically, I want to define a method expr which takes a block and evaluates that block in a context where + is redefined to construct instances of my DSL. That is, I want something like

module Context
  refine Integer do
    def +(other)
      Addition.new(self, other)
    end
  end
end

def expr(&block)
  Context.module_eval(&block)
end

So that, ideally, expr { 1 + Variable.new('x') } would result in the DSL expression Addition.new(1, Variable.new('x')).

However, it seems that Ruby refinements are quite fickle, and module_eval'ing into a scope with an active refinements does not activate that refinement inside the block, as I was hoping it would. Is there a way to use module_eval, instance_eval, etc. to activate a refinement inside a particular Ruby block?

I realize that I could wrap integers in a IntegerExpr class and provide + on that. However, this is Ruby, and the sky is the limit with metaprogramming, so I'm curious if it can be done with ordinary Ruby Integer instances. I want to define a method expr such that, in

expr { 1 + Variable.new('x') }

the + inside the block is a refinement-defined Integer#+, even if that refinement is not active at the call site of expr. Is this possible?

0

There are 0 best solutions below