So I'm building a data type where, I would like, optional auto-casting. The last question I asked is related to this also.
The code I currently have can be found below:
class Test(T)
@@auto_cast = false
def initialize(var : T)
@var = var
end
def self.auto_cast
@@auto_cast
end
def self.auto_cast=(val)
@@auto_cast = val
end
def self.auto_cast(forced_value=true,&block)
#Force value, but store initial value:
ac = @@auto_cast
@@auto_cast = forced_value
block.call
@@auto_cast = ac
end
def +(val)
var = @var
if @@auto_cast
if var.is_a? String
casted_arg = val.to_s
return var + casted_arg
else
casted_arg = typeof(var).new(val)
return var + casted_arg
end
else
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
return var + val
end
end
end
end
When I try to test the data type however:
Test.auto_cast do
puts Test.auto_cast
puts Test.new(1) + "1"
puts Test.new("1") + 1
end
It throws an error at return var + val
:
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
ERROR! --> return var + val
end
At first I was confused why, but now it makes sense.
@@auto_cast
will be true whenever I intend to auto_cast (and to be fair, when auto-casting is disabled, I want the syntax error).@@auto_cast
is unknown at compile time..
if var.is_a? String
casted_arg = val.to_s
return var + casted_arg
else
casted_arg = typeof(var).new(val)
return var + casted_arg
end
and
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
return var + val
end
Each definition should only be used when the user explicitly declares it. Thus this is more suited to a macro.
Given these reasons I started trying to build the functionality into a macro instead:
def +(val)
var = @var
{%if @@auto_cast%}
if var.is_a? String
casted_arg = val.to_s
return var + casted_arg
else
casted_arg = typeof(var).new(val)
return var + casted_arg
end
{%else%}
if typeof(var) != typeof(val)
{{raise "Error: Type of <<var>> is not equal to type of <<val>> while auto_cast is false."}}
else
return var + val
end
{%end%}
end
I thought this would work because, this way code is only ever generated if @@auto_cast
is set. However what I forgot was premise #2. I.E. the value of @@auto_cast
is unknown at compile time. Ultimately, in order to make this work I would need a variable which can be:
Ultimately I figured I could do something along the lines of this:
SET_AUTOCAST_VARIABLE true
puts Test.auto_cast
puts Test.new(1) + "1"
puts Test.new("1") + 1
SET_AUTOCAST_VARIABLE false
and then in the +()
definition:
{%if autocast_variable %}
...
{%else%}
...
{%end%}
The problem is, I do not think such a macro global variable exists... I was thinking about ways to get around this issue and so far the only solution I can come up with is using some external file at compile time:
{{File.write("/tmp/cct","1")}}
puts Test.auto_cast
puts Test.new(1) + "1"
puts Test.new("1") + 1
{{File.write("/tmp/cct","")}}
and in the method definition:
{%if File.read("/tmp/cct")=="1" %}
...
{%else%}
...
{%end%}
This feels really hacky though... I was wondering whether there were any other alternatives, (or, even, if this just won't work at all)?
This can't work. Methods are only instantiated once, it is not possible to have two implementations of the same method with the same argument types. In the following example both +
methods will inevitably have the same implementation.
Test.auto_cast do
Test.new(1) + "1"
end
Test.new(1) + "1"
You can't have different implementations of the same method depending on lexical scope. The method is exactly instantiated once and so is the macro inside it.
I don't understand your overall use case, but maybe there are other ways to achieve what you need.
For completeness: You can utilize a constant as a macro global variable. Constants can't be redefined, but altered through macro expressions. That can be used to store state between macros. For example:
FOO = [true]
{{ FOO[0] }} # => true
{% FOO.clear; FOO << false %}
{{ FOO[0] }} # => false
That's pretty hacky, though ;)