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.
- I believe the Crystal compiler cannot be certain that
@@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). - A compile error occurs because the value of
@@auto_cast
is unknown at compile time. - Due to the contradictory nature of the bodies:
.
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:
- Set at compile time.
- Used globally within macros at compile time.
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)?