如何通过其符号名称设置变量

我正在学习水晶(只是为了好玩)并试图为结构实现一种 []= 方法 . 这是第一次尝试:

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 里面的 {%%} 语法无法解析 . 如何克服这个问题?也许还有其他惯用的方法来实现这样的方法?

回答(1)

3 years ago

您不需要实例变量具有所有可能类型的并集,您可以在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 . 或者你可能不知道是否有真正的用例 .