Ruby 实现 block 里的变量

coolesting · 2013年07月08日 · 最后由 coolesting 回复于 2013年07月09日 · 3784 次阅读
F = {}

def data name = '', &block
    F[name] = [] unless F.include? name
    F[name] << block
end

def do_data name
    i = 2
    h = {}
    F[name].each do | b |
        # 这里出错, 无法实现 i 变量。
        h.merge!(b.call)
    end
    h
end

data :a do
    {
        :a1 => i,
        :a2 => 'a2222',
    }
end

p do_data(:a)

运行出错

undefined local variable or method `i' for main:Object (NameError)

How should i implement the variables in data block when it was called..

可能需要加参数传递吧

data :a do |i|
    {
        :a1 => i,
        :a2 => 'a2222',
    }
end

第一反应是看见这样的代码,就想揍人。

我改了下:

F = {}

def data name = '', &block
    F[name] = [] unless F.include? name
    F[name] << block
end

def do_data name
    i = 2
    h = {}
    F[name].each do | b |
        # 传递参数到 block
        h.merge!(b.call(i))
    end
    h
end

# call data function to generate data
data :a do |i|
    {
        :a1 => i,
        :a2 => 'a2222',
    }
end

p do_data(:a)

你有没有觉得你写的代码的本质是类似

def test
  i = 1
  def test2
    puts i
  end

  test2
end

test()

楼主能不能先说说这段代码是干什么用的... 我看得有点头大

你 block 执行的环境里面没有 i 这个变量啊,你的 i 是在一个方法里面定义的,方法的定义(以及类和模块的定义),会形成一个新的 scope,里面的变量你在外面是无法拿到的。而且你指出的拿不到 i 的地方也不对啊,你试下这个

F = {}

def data name = '', &block
    F[name] = [] unless F.include? name
    F[name] << block
end

i = 2  # 把 i 移到这里
def do_data name
    h = {}
    F[name].each do | b |
                h.merge!(b.call)
    end
    h
end

data :a do
    {
        :a1 => i,   # 你原来的代码是这里出错, 拿不到 i 
        :a2 => 'a2222',
    }
end

p do_data(:a)

@coolesting 我猜测你要实现的需求是实现变量的作用域动态配置,而不用作为一般的方法参数传入。

在 Ruby 里,局部变量 (local variable) 只能在 method 和 loop 内被访问,实例变量 (instance variable) 可以在一个类的实例的方法之间互访。

所以要么和 @guyanbiao 说的一样,把 i 这个变量拎到外面。

或者用 实例变量 也可以实现,如下:

1.9.3p392 :001 > class HH; def initialize &blk; @i = 5; instance_eval &blk; end; end
 => nil 
1.9.3p392 :002 > HH.new { puts (@i + 5) }
10
 => #<HH:0x000001010588e8 @i=5> 
1.9.3p392 :003 > @i
 => nil 
1.9.3p392 :004 > 

关于 Ruby 里的各种变量的用途在 http://www.techotopia.com/index.php/Ruby_Variable_Scope 这里解释的比较清楚了,包括类变量等。

#6 楼 @guyanbiao 我以为 block 在被 call 时,其块中的变量才被赋值,才把 i 放到方法里面去。

才把代码弄成这样,实际上,代码要复杂很多。


def ss
    'ss'
end

i = 2

data :a do
    {
        :a1 => ss,
        :a2 => 'a2222',
        :a3 => i,
    }
end

data :a do
    {
        :a4 => Time.now,
    }
end

#5 楼 @blacktulip 原本代码很复杂,一楼已被简化的了,但还是令大家生产异意。

不知道有人用过,sinatra 么,sinatra 不是有 before, helpers, configures 之类的块么?

现在我想增加一个 data 块,把 data 都存放在一个 D 常量中。按需索取。

10 楼 已删除
11 楼 已删除

block 的环境是 lexical 的不是 dynamic 的,你创建这个 block 的时候没有 i 变量


data :a do
    {
        :a1 => i,
        :a2 => 'a2222',
    }
end

这个 block 里的i在这段代码执行时就应该有初始值,然后它就被包裹到一个闭包里,这里的i跟下面这段代码里的i完全是两码事,它俩是完全独立的。

def do_data name
    i = 2
    h = {}
    F[name].each do | b |
        # 这里出错, 无法实现 i 变量。
        h.merge!(b.call)
    end
    h
end

#13 楼 @edgar_wang_cn 问题是,在 block 封装之前,无法知道对方会以什么形式来赋值, 我想在执行 block 时,赋值,类似用 eval 的方法,来实现,有解决方法 吗 ?

反正就是,有很多这样的 block


data :a do
    {
        :a1 => i,
        :a2 => 'a2222',
    }
end

data :a do
    {
        :a1 => Time.now,
        :a3 => 'a333',
    }
end

data :a do
    {
        :a4 => get_value_by_method(),
        :a5 => '555555',
        :a6 => '666666',
    }
end


我不知道用户会以什么方式赋值,我只想把数据装起来,要用的时候调用

the solution of question is end with this code h.merge!(instance_eval(&b))

需要 登录 后方可回复, 如果你还没有账号请 注册新账号