Reputation: 70257
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?
Upvotes: 5
Views: 195