现有一个字符串列表新葡京32450网址:,方法来压缩堆

发布时间:2020-02-04  栏目:新葡京32450网址  评论:0 Comments

Ruby 2.7.0
稳定版在圣诞节当天发布了,此版本引入了许多新特性和性能改进,最值得注意的包括:

Ruby 2.7.0 preview 2 已经发布了,最终版本计划在 12
月发布。该版本引入了一些新特性和性能改进,主要是:

Ruby 2.7.0-rc2 发布了,最终版本计划于 12 月 25 日发布。

简便方法的用法

声明

本文系 sinatra 源码系列第 4 篇。系列的目的是通过 sinatra 学习 ruby
编程技巧。文章按程序运行的先后顺序挑重点分析,前一篇文章分析过的略去不说。水平很有限,所写尽量给出可靠官方/讨论链接,不坑路人。

  • 模式匹配(Pattern Matching)
  • REPL 改进
  • 紧凑 GC(Compaction GC)
  • 位置参数和关键字参数的分离
  • Compaction GC
  • Pattern Matching
  • REPL improvement
  • Separation of positional and keyword arguments

此版本引入了许多新特性和性能改进,最值得注意的包括:

现有一个字符串列表,需要对其中的每个字符串执行转换大写操作,我们可以用一个简便写法来完成。

重要提醒

一定要先安装 1.8 版本的 ruby ,因为 1.9+ 的 ruby ,String
的实例是不响应 each 方法的,这会直接导致 rack 报错。可以使用
rvm 安装 1.8.7 版本的 ruby
,如果使用 rvm ,请先升级到最新版本,否则安装 1.8.7 的 ruby 时也会报错。

使用命令 git log -1 --format=%ai 0.2.0 ,查看 0.2.0 版本 sinatra
的“出厂日期”,得到 2008-04-11 16:29:36 -0700 ;而 1.8.7 版本的 ruby 是
2008 年 5 月发布的,两者兼容性应该比较好。

列一下本人运行 sinatra 0.2.0 用到的 ruby 和关键 gem 的版本:

  • ruby-1.8.7-p374
  • rack 1.4.1
  • mongrel 1.1.5

模式匹配(实验性功能)

模式匹配是函数式编程语言中广泛使用的特性,如果匹配某一个模式,它可以遍历给定的对象并分配其值,目前尚处于实验阶段 [Feature
#14912]:

require "json"

json = <<END
{
  "name": "Alice",
  "age": 30,
  "children": [{ "name": "Bob", "age": 2 }]
}
END

case JSON.parse(json, symbolize_names: true)
in {name: "Alice", children: [{name: "Bob", age: age}]}
  p age #=> 2
end

有关该功能的具体细节请查看 Pattern matching – New feature in Ruby
2.7。

Compaction GC 

  • 模式匹配

复制代码 代码如下:

change log

  • 大重构,把功能模块都压缩在一个文件中
  • 增加大量测试用例

REPL 改进

绑定的交互式环境 irb 现在支持多行编辑,由 reline 提供支持,reline
是一种与 readline 兼容的纯 Ruby 实现。它还提供了 rdoc 集成。在 irb
中,可以显示给定类、模块或方法的引用。此外,binding.irb
中显示的源代码行和核心类对象的检查结果现在以颜色区分显示。

新葡京32450网址 1

这个版本引入了 Compaction
GC,以碎片化内存空间。GC.Compact 方法对堆进行压缩,这个函数压缩堆中的活动对象,以使用更少的页,并且堆会更友好。

模式匹配是函数式编程语言中广泛使用的特性,如果匹配某一个模式,它可以遍历给定的对象并分配其值:

name_list = [“chareice”, “angel”]
name_list.map(&:upcase)
# => [“CHAREICE”, “ANGEL”]

跑通所有测试用例

首先修改一处代码错误,在 sinatra.rb 文件的 1022 行,将
Rack::File::MIME_TYPES[ext.to_s] = type 改为
Rack::Mime::MIME_TYPES[ext.to_s] = type

然后安装一些缺少的 gem :

gem install builder -v '2.1.2'
gem install sass -v '3.1.0'
gem install haml -v '1.8.0'

跑测试用例,发现只有 sym_params_test.rb 文件中的一处跑不通过。

此处的测试是验证可以用 String 和 Symbol 访问参数。实现的关键方法是:

# sinatra.rb 663 行
h = Hash.new { |h, k| h[k.to_s] if Symbol === k }

调用 Hash.new 时传进一个 block ,可以设置当访问某个不存在于 Hash 的
Key 时的一些默认行为,比如上面的代码就是说,当 key 不存在且是 Symbol
时,把 key 转换为字符串再找找(再抢救一下…)

Hash.new 还可以用来初始化值为数组的键值对,在记录事件回调时很方便:

@events = Hash.new { |hash, key| hash[key] = [] }

# 出自这个版本的 sinatra.rb 的 738 行
# 再也不用先判断 key 是否存在,也不用手动初始化一个空数组了

回过头来修改代码以跑通测试用例,作者这里粗心写错了请求的方法,应该用
post_it ,而不是 get_it ,还要相应地修改路由:

specify "should be accessable as Strings or Symbols" do
  post '/' do
    params[:foo] + params['foo']
  end

  post_it '/', :foo => "X"
  assert_equal('XX', body)
end

要在这个版本的 sinatra 的 get 方法中传递参数,需要把参数写在 uri
中,下面的写法也能通过测试:

specify "should be accessable as Strings or Symbols" do
  get '/' do
    params[:foo] + params['foo']
  end

  get_it '/?foo=X'
  assert_equal('XX', body)
end

紧凑 GC(Compaction GC)

紧凑 GC 可以对碎片化的内存空间进行碎片整理。一些多线程 Ruby
程序可能会导致内存碎片,从而导致高内存使用率和速度下降。引入了
GC.compact
方法来压缩堆,此函数压缩堆中的活动对象,以便可以使用更少的页,并且堆可能对
CoW 更友好。

Pattern Matching(实验性)

require "json"

json = <<END
{
  "name": "Alice",
  "age": 30,
  "children": [{ "name": "Bob", "age": 2 }]
}
END

case JSON.parse(json, symbolize_names: true)
in {name: "Alice", children: [{name: "Bob", age: age}]}
  p age #=> 2
end

这个写法等同于

从 at_exit 说起

还是从 at_exit 开始读代码。

$! 记录异常信息,当调用 raise
的时候会设置这个变量,详见此处。

调用 load_options! 解释完启动参数后, sinatra 在所有环境设置遇到异常和
404 时的回调方法,在开发环境遇到异常和 404
的回调方法比其他环境暴露更多的信息。

位置参数和关键字参数的分离

关键词参数和位置参数的自动转换被标记为已废弃(deprecated),自动转换将会在
Ruby 3 中被移除。[功能
#14183]

  • 当方法传入一个 Hash
    作为最后一个参数,或者传入的参数没有关键词的时候,会抛出警告。如果需要继续将其视为关键词参数,则需要加入两个星号来避免警告并确保在
    Ruby 3 中行为正常。

  def foo(key: 42); end; foo({key: 42})   # warned
  def foo(**kw);    end; foo({key: 42})   # warned
  def foo(key: 42); end; foo(**{key: 42}) # OK
  def foo(**kw);    end; foo(**{key: 42}) # OK
  • 当方法传入一个 Hash
    到一个接受关键词参数的方法中,但是没有传递足够的位置参数,关键词参数会被视为最后一个位置参数,并抛出一个警告。请将参数包装为
    Hash 对象来避免警告并确保在 Ruby 3 中行为正常。

  def foo(h, **kw); end; foo(key: 42)      # warned
  def foo(h, key: 42); end; foo(key: 42)   # warned
  def foo(h, **kw); end; foo({key: 42})    # OK
  def foo(h, key: 42); end; foo({key: 42}) # OK
  • 当方法接受关键词参数传入,但不会进行关键词分割(splat),且传入同时含有
    Symbol 和非 Symbol 的 key,那么 Hash
    会被分割,但是会抛出警告。你需要在调用时传入两个分开的 Hash 来确保在
    Ruby 3 中行为正常。

  def foo(h={}, key: 42); end; foo("key" => 43, key: 42)   # warned
  def foo(h={}, key: 42); end; foo({"key" => 43, key: 42}) # warned
  def foo(h={}, key: 42); end; foo({"key" => 43}, key: 42) # OK
  • 当一个方法不接受关键词,但是调用时传入了关键词,关键词会被视为位置参数,不会有警告抛出。这一行为将会在
    Ruby 3 中继续工作。

  def foo(opt={});  end; foo( key: 42 )   # OK
  • 如果方法支持任意参数传入,那么非 Symbol
    也会被允许作为关键词参数传入。[功能
    #14183]

  def foo(**kw); p kw; end; foo("str" => 1) #=> {"str"=>1}
  • **nil 被允许使用在方法定义中,用来标记方法不接受关键词参数。以关键词参数调用这些方法会抛出
    ArgumentError [功能
    #14183]

  def foo(h, **nil); end; foo(key: 1)       # ArgumentError
  def foo(h, **nil); end; foo(**{key: 1})   # ArgumentError
  def foo(h, **nil); end; foo("str" => 1)   # ArgumentError
  def foo(h, **nil); end; foo({key: 1})     # OK
  def foo(h, **nil); end; foo({"str" => 1}) # OK
  • 将空的关键词分割(splat)传入一个不接受关键词的方法不会继续被当作空
    Hash
    处理,除非空哈希被作为一个必要参数,并且这种情况会抛出警告。请移除双星号来将
    Hash 作为位置参数传入。[功能
    #14183]

  h = {}; def foo(*a) a end; foo(**h) # []
  h = {}; def foo(a) a end; foo(**h)  # {} and warning
  h = {}; def foo(*a) a end; foo(h)   # [{}]
  h = {}; def foo(a) a end; foo(h)    # {}

如果要禁用“弃用提醒警告(deprecation
warnings)”,请使用命令行参数-W:no-deprecated或添加Warning[:deprecated] = false到代码中。

模式匹配是函数式程序设计语言中广泛使用的一种特性。通过模式匹配,可以遍历给定的对象并分配其值。

  • REPL 改进

复制代码 代码如下:

OpenStruct

值得细看的是在非开发环境遇到异常时的回调方法:

error do
  raise request.env['sinatra.error'] if Sinatra.options.raise_errors
  '<h1>Internal Server Error</h1>'
end

Sinatra.options 实际上是 OpenStruct 的实例。
OpenStruct
Hash 相似,但它通过元编程提供了不少快捷访问、设置值的方法。
OpenStruct 用法举例:

# 1
person = OpenStruct.new
person.name    = "John Smith"
p person.name    #=> "John Smith"

# 2
person = OpenStruct.new(:name => "John Smith")
p person.name    #=> "John Smith"

一个简单版本的 OpenStruct 实现:

class OpenStruct
  attr_accessor :h
  def initialize(hash = {})
    @h = hash

    h.each do |key, value|
      self.class.send(:define_method, key) do
        h[key]
      end
      self.class.send(:define_method, "#{key}=") do |value|
        h[key] = value
      end
    end
  end

  def method_missing(m, *args)
    if args.size == 1
      # m is  :name=
      # change m to :name
      h[m.to_s.chop.to_sym] = args[0]
    elsif args.size == 0
      h[m]
    end
  end

  def respond_to?(m)
    h.respond_to?(m) || super
  end

end

require 'test/unit'

class TestOS < Test::Unit::TestCase
  def setup
    @person_1 = OpenStruct.new
    @person_2 = OpenStruct.new(:name => 'zhu')
  end

  def test_case_1
    assert_equal true, @person_1.respond_to?(:name)
    assert_equal nil, @person_1.name
    @person_1.name = 'zhu'
    assert_equal 'zhu', @person_1.name
  end

  def test_case_2
    assert_equal true, @person_2.respond_to?(:name)
    assert_equal 'zhu', @person_2.name
    @person_2.name = 'jude'
    assert_equal 'jude', @person_2.name
  end
end

以上只是我心血来潮写的, OpenStruct
的实现远远不是上面写的那么简单,有兴趣可以看看源码。

Sinatra.options.raise_errors 的值只能在代码里设置,当其值不为 nil 或
false
时,默认在非开发环境下直接抛出异常。要想在命令行启动时设置值,只需要在
load_options! 方法中添加一行:

op.on('-r') { |env| default_options[:raise_errors] = true }

在订制开发环境下的异常和 404 页面时,使用到 %Q(...) 。 ruby
会特殊处理以百分号 ‘%’ 开头的字符串,帮你省去不少转义引号的麻烦:

The string expressions begin with % are the special form to avoid
putting too many backslashes into quoted strings.
出处

更多相似的用法见Ruby 里的 %Q, %q, %W, %w, %x, %r, %s,
%i。

在显示异常信息时,用 escap_html 来转义 &,<,>,/,',"
,把这些 ascii 字符编码成实体编码,防止 XSS 攻击,不过源码有注释说有 bug

On 1.8, there is a kcode = ‘u’ bug that allows for XSS otherwhise

源码中用正则表达式替换转义字符的实现值得参考。

更多关于 XSS
的知识,可以看看本人之前写的这篇。

其它值得关注的新特性

  • 方法引用运算符,.:,作为实验性功能加入了。功能
    #12125、功能
    #13581

  • 实验性地加入了把编号参数作为默认的块参数的特性。功能
    #4475

  • 无头范围实验性地加入了。它可能尽管没有无限范围那么有用,但它对开发
    DSL 是非常有用的。功能
    #14799

  ary[..3]  # identical to ary[0..3]
  rel.where(sales: ..100)
  • 新增了 Enumerable#tally,它会计算每个元素出现的次数。

  ["a", "b", "c", "b"].tally
  #=> {"a"=>1, "b"=>2, "c"=>1}
  • 允许在 self 上调用私有方法 [功能
    #11297] [功能
    #16123]

  def foo
  end
  private :foo
  self.foo
  • 新增 Enumerator::Lazy#eager。它会产生一个非懒惰的迭代器。[功能
    #15901]

  a = %w(foo bar baz)
  e = a.lazy.map {|x| x.upcase }.map {|x| x + "!" }.eager
  p e.class               #=> Enumerator
  p e.map {|x| x + "?" }  #=> ["FOO!?", "BAR!?", "BAZ!?"]
case JSON.parse('{...}', symbolize_names: true)
in {name: "Alice", children: [{name: "Bob", age: age}]}
  p age
  ...
end

绑定的交互式环境 irb 现在支持多行编辑,由 reline 提供支持,reline
是一种与 readline 兼容的纯 Ruby 实现。它还提供了 rdoc 集成。在 irb
中,可以显示给定类、模块或方法的引用。此外,binding.irb
中显示的源代码行和核心类对象的检查结果现在以颜色区分显示。

name_list.map do {|name| name.upcase}

lookup

接下来看 Application 的 call 方法。

首先由 lookup 方法实现根据请求找到正确的路由。

def lookup(request)
  method = request.request_method.downcase.to_sym
  events[method].eject(&[:invoke, request]) ||
    (events[:get].eject(&[:invoke, request]) if method == :head) ||
    errors[NotFound].invoke(request)
end

sinatra 在 Enumerable 上扩展了 eject 方法,因为 Array 加载了
Enumberable 模块,所以 Array 实例能用 eject 方法。

def eject(&block)
  find { |e| result = block[e] and break result }
end

eject 方法内部,使用 find 方法找到第一个产生非 false 结果的 block
,并返回这个结果。find 方法本来会返回第一个符合条件的元素,通过
break 可以订制自己的返回值。

这里 e 是 Event 的实例。 block 是由 Array 实例转化而来的 Proc 。

系列第一篇文章提到过,
如果跟在 & 后面对象的不是 Proc ,首先会调用这个对象的 to_proc
方法得到一个 Proc 实例,最后会调用这个 Proc 的 call 方法。

sinatra 扩展了 Array 的 to_proc 方法:

def to_proc
  Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
end

经过 to_proc 转换, Proc#call
把参数转换为一个数组,把这个数组第一个元素作为 receiver ,把调用
to_proc
方法的数组的第一个元素作为方法,把两个数组余下的元素作为方法的参数,拿前面的代码作例子:

# 在 lookup 方法里下面的这行代码

&[:invoke, request]

# 会得到这样一个 Proc

#=> Proc.new { |*args| args.shift.__send__(:invoke, *(args + [request])) }

# 在 eject 方法定义中

find { |e| result = block[e] and break result }

# block[e] 就是把 e 当作参数调用  Proc#call ,做的事情是: 以 `request` 作为参数,调用 `e` 的 `invoke` 方法。

block[e] 不能写成 block(e) ,否则 ruby 会把 block 当作是 main
的一个方法来调用。有三种方法可以调用
Proc#call

# 1
 a_proc.call()
# 2
 a_proc.()
# 3
 a_proc[]

相关文章

留下评论

网站地图xml地图