我学习水晶(只是为了好玩)并尝试为结构实现一种[]=
方法。这是第一次尝试:
struct Foo
@str : String | Int32 # Have to share all the types, NOT OK
@int : Int32 | String # Have to share all the types, NOT OK
def initialize(@str = "foo", @int = 111)
end
def []=(variable, value)
{% for ivar in @type.instance_vars %}
@{{ivar}} = value if {{ivar.id.symbolize}} == variable
{% end %}
end
end
foo = Foo.new
foo[:str] = "bar" # OK
foo[:int] = 222 # OK
它有效,但缺点是所有实例变量都应该共享相同的类型,否则编译器会抱怨。我的第二次尝试是重新实现这样的方法:
def []=(variable, value)
{% for ivar in @type.instance_vars %}
{% if ivar.id.symbolize == variable %} # ERROR: undefined macro variable 'variable'
@{{ivar}} = value
{% end %}
{% end %}
end
但这不起作用,因为variable
语法中的{%%}
无法解决。如何克服这个问题?也许还有其他习惯用法来实现这种方法吗?
答案 0 :(得分:1)
您不需要实例变量具有所有可能类型的并集,您可以在setter方法中限制为匹配类型:
struct Foo
@str : String
@int : Int32
def initialize(@str = "foo", @int = 111)
end
def []=(variable, value)
{% for ivar in @type.instance_vars %}
if {{ivar.id.symbolize}} == variable
if value.is_a?({{ ivar.type.id }})
@{{ivar}} = value
else
raise "Invalid type #{value.class} for {{ivar.id.symbolize}} (expected {{ ivar.type.id }})"
end
end
{% end %}
end
end
foo = Foo.new
foo[:str] = "bar"
foo[:int] = 222
foo[:int] = "string" # => Invalid type String for :int (expected Int32)
虽然这当然是可能的,但它的缺点是这些类型不匹配只能在运行时检测到。如果直接访问属性,您将获得更好的类型安全性(foo.int = "string"
甚至不会编译)。
应该可以将[]=
实现为一个宏,它可以在编译时抱怨类型不匹配,但我不确定这是你在寻找什么。
更好的解决方案可能是使用NamedTuple
。或者您可能根本不需要使用符号访问字段。我不知道是否有真正的用例。