1

我一直在浏览 Jekyll 源代码,偶然发现了这种方法:

  # Public: Generate a Jekyll configuration Hash by merging the default
  # options with anything in _config.yml, and adding the given options on top.
  #
  # override - A Hash of config directives that override any options in both
  #            the defaults and the config file. See Jekyll::DEFAULTS for a
  #            list of option names and their defaults.
  #
  # Returns the final configuration Hash.
  def self.configuration(override)
    # Convert any symbol keys to strings and remove the old key/values
    override = override.reduce({}) { |hsh,(k,v)| hsh.merge(k.to_s => v) }

    # _config.yml may override default source location, but until
    # then, we need to know where to look for _config.yml
    source = override['source'] || Jekyll::DEFAULTS['source']

    # Get configuration from <source>/_config.yml or <source>/<config_file>
    config_file = override.delete('config')
    config_file = File.join(source, "_config.yml") if config_file.to_s.empty?

    begin
      config = YAML.safe_load_file(config_file)
      raise "Configuration file: (INVALID) #{config_file}" if !config.is_a?(Hash)
      $stdout.puts "Configuration file: #{config_file}"
    rescue SystemCallError
      # Errno:ENOENT = file not found
      $stderr.puts "Configuration file: none"
      config = {}
    rescue => err
      $stderr.puts "           " +
                   "WARNING: Error reading configuration. " +
                   "Using defaults (and options)."
      $stderr.puts "#{err}"
      config = {}
    end

    # Merge DEFAULTS < _config.yml < override
    Jekyll::DEFAULTS.deep_merge(config).deep_merge(override)
  end
end

尽管有评论,但我无法弄清楚它的作用。reduce({})特别困扰我 - 它有什么作用?

此外,之前调用的方法configuration是:

options = normalize_options(options.__hash__) 

做什么__hash__

4

2 回答 2

2

让我们看看有问题的代码:

override.reduce({}) { |hsh,(k,v)| hsh.merge(k.to_s => v) }

现在让我们看一下文档Enumerable#reduce

通过应用二元运算组合枚举的所有元素,该二元运算由命名方法或运算符的块或符号指定。

如果您指定一个块,那么对于枚举中的每个元素,该块都会传递一个累加器值(备忘录)和元素。如果你指定了一个符号,那么集合中的每个元素都将被传递给 memo 的命名方法。无论哪种情况,结果都会成为 memo 的新值。在迭代结束时,memo 的最终值就是方法的返回值。

因此,覆盖将是您典型的 Ruby 选项哈希,例如:

{
  debug: 'true',
  awesomeness: 'maximum'
}

那么当你reduce在覆盖上使用它时会发生什么?

它将使用二进制函数组合枚举的所有元素(覆盖哈希的键 => 值对)merge。Merge 接受一个哈希并将其合并到接收器中。那么这里发生了什么?

  1. hsh开始,{}第一个键/值对被合并:{}.merge(:debug.to_s => "true").
  2. hsh现在是{"debug" => "true"}
  3. 下一个键/值对被合并到:{"debug" => "true"}.merge(:awesomeness.to_s => "maximum").
  4. hsh就是现在{"debug" => "true", "awesomeness" => "maximum"}
  5. 没有更多的元素,所以hsh返回这个值。

这与代码注释相匹配,该注释说“将任何符号键转换为字符串并删除旧键/值”,尽管从技术上讲旧值不会被删除。相反,构造一个新的散列,并通过用新值替换变量来丢弃具有旧值的旧散列,最终由垃圾收集器收集 - 以及由 reduce 中的合并创建的中间对象。顺便说一句,这意味着这merge!将比merge在这种情况下更有效,因为它不会创建那些中间对象。


__foo__是一个 ruby​​ 习惯用法,用于您要确保不会重新定义的准私有和/或“核心”方法,例如,__send__因为Socket想要使用send. 在 Ruby 中,hash是对象的哈希值(使用哈希函数计算,当对象用作哈希键时使用),因此__hash__可能指向options将其数据存储为哈希的对象的实例变量。这是一个来自 gem 的类,它就是这样做的。不过,您必须查看文档以了解任何类型的对象options。(您必须查看代码才能确定。;)

于 2013-04-01T15:41:22.153 回答
0

reduce通常用于构建数组或哈希,其方式类似于使用mapor collect,通过迭代地将每个元素添加到该容器,通常在对元素进行一些操作之后。

我改用each_with_object它,因为这种操作更直观:

[:foo, :bar].each_with_object({}) do |e, h|
  h[e.to_s] = e
end

请注意,each_with_object不需要从块中返回“记住”值reduceinject想要。reduce并且inject对于其他类型的求和魔法each_with_object非常有用,所以也将它们留在你的工具箱中。

于 2013-04-01T15:41:56.707 回答