I am trying to create a DSL with operator methods. However, in a “with self yield” block, the operators don’t work.
Given following code:
class Adder
getter ns : Array(Int32) = [] of Int32
def add=(n : Int32)
ns.push n
end
def min(n : Int32)
ns.push (-1 * n)
end
def calc(&)
with self yield
end
end
adder = Adder.new
puts adder.ns
adder.add = 1
puts adder.ns
adder.calc do
add = 2
end
puts adder.ns
adder.min(3)
puts adder.ns
adder.calc do
min(4)
end
puts adder.ns
I get output
[1]
[1]
[1, -3]
[1, -3, -4]
But I expected
[1]
[1, 2]
[1, 2, -3]
[1, 2, -3, -4]
Is it possible to use operator methods in a “with self yield” block?
Think the problem here is add = 2 is ambiguous, so the compiler is assuming it’s a local variable assignment. If you want it to call the setter, you can do itself.add = 2. Tho really a more practical solution would be to use a more idiomatic method name, like def <<(n : Int32) and/or push to make it more clear and not involve assignment so compiler will know what to do.
The “add” and “minus” was just an example. In my DSL, I’ve got a lot of configuration options that can be set by assignment operator. The idea is to set options that can occur only once by assignment (=), and options that can occur multiple times by <<.
I’ll see if there’s another operator I can use instead of = for options I can set once.
The catch is that even tho there is an implicit receiver, the block is still executing at the top level, which there is no self in so the error is valid. #itself works because it returns self from the context of the object that was yielded. I.e. self in the block itself and self as returned by #itself are two different scopes, the former of which is invalid.
Interesting, so, in the Crystal, a block never has it’s own (direct) self, even not in the top level namespace, right? it’s self share the self in the object context, you must get it from Object#itself, right?
Following is a example:
class Adder
getter ns : Array(Int32) = [] of Int32
def add=(n : Int32)
ns.push n
end
def min(n : Int32)
ns.push(-1 * n)
end
def calc(&)
with self yield
end
end
class A
def initialize(@adder : Adder)
end
def foo
@adder.calc do
self.add = 2 # Error: undefined method 'add=' for A
end
pp! @adder.ns
end
end
a = A.new(Adder.new)
a.foo
Have to change from self.add = 2 into itself.add = 2 to work.
If i guess correct, this is a REALLY BIG different with Ruby, I’m surprised it never mentioned in the document.
As mentioned by @Blacksmoke16 its setter which is causing the ambiguity. so straight-forward approach would be define a add method for code to work.
class Adder
getter ns : Array(Int32) = [] of Int32
def add=(n : Int32)
ns.push n
end
def add(n)
ns.push n
end
def min(n : Int32)
ns.push (-1 * n)
end
def calc(&)
with self yield
end
end
adder = Adder.new
puts adder.ns
adder.add = 1
puts adder.ns
adder.calc do
add 2
end
puts adder.ns
adder.min(3)
puts adder.ns
adder.calc do
min(4)
end
puts adder.ns