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

时间:2018-02-08 09:24:38

标签: crystal-lang

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

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 个答案:

答案 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。或者您可能根本不需要使用符号访问字段。我不知道是否有真正的用例。