<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>cichol (cichol)</title>
    <link>https://ruby-china.org/cichol</link>
    <description>我也想找工作</description>
    <language>en-us</language>
    <item>
      <title>Agda 是怎么证明定理的</title>
      <description>&lt;p&gt;定理证明器是一种特殊的编程语言。我们平常的编程语言在编译的时候会做类型检查，来确保没有类型错误，而定理证明器则是设计出来确保数学证明没有错误的。在使用它们的时候，我们会把数学定理的证明写成程序，然后让软件来做类型检查。&lt;/p&gt;

&lt;p&gt;这篇文章会介绍定理证明器的一些基本的工作原理，以及程序员一般不太熟悉的看待类型的视角。使用 Agda 是因为它是定理证明器里比较接近程序员的，也拥有非常优美的语法特性，是学起来很值的语言。&lt;/p&gt;
&lt;h3 id="以集合来理解类型"&gt;以集合来理解类型&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data Apple : Set where
  red-apple : Apple
  green-apple : Apple
  yellow-apple : Apple
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的代码定义了一个苹果的集合，这个集合包含三个元素，分别是红苹果、绿苹果和黄苹果。&lt;/p&gt;

&lt;p&gt;在 Agda 里集合（Set）和类型是同义词，定义一个集合就等同于定义一个类型，实际上我们也可以将类型看作集合。在传统的编程语言如 C 里，int 是一个类型，如果把 int 看作一个集合，也就是整数集合，那么 1、2、3 等都是其中的元素。&lt;/p&gt;

&lt;p&gt;理论上在 Agda 中我们可以这样表述一个自然数的集合，或者类型：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data Nat : Set where
  0 : Nat
  1 : Nat
  2 : Nat
  ......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们通过把每一个元素都罗列出来定义这个集合，但是实际上这是做不到的，因为自然数有无穷多个。稍后我们会看到更实用的定义自然数的方式。&lt;/p&gt;

&lt;p&gt;在 C 里，如果一个函数接受 int，我们可以把 1、2、3 传给它。同样的，Apple 也是一个类型，如果一个函数需要 Apple，我们可以把三种苹果之一传给它。在传统语言里，类型一般只被理解为一种标记，但是在定理证明器里，类型往往描述的是一种性质，例如“有某某性质的集合”。因此，以集合的视角来看待类型会更接近它们在定理证明器里的形态。&lt;/p&gt;
&lt;h3 id="证明即可被构造"&gt;证明即可被构造&lt;/h3&gt;
&lt;p&gt;考虑这么一个问题，我们如何证明一个苹果存在？最简单的方法就是找到一个苹果的实例。那么这如何在 Agda 里表述呢？&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-apple-exist : Apple
proof-that-apple-exist = ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上代码中的 proof-that-apple-exist 是一个未完成的证明，问号的地方有待填入内容，但是这个证明的目标是明确的，它需要找到一个苹果的实例。我们也会发现这个 proof 本身的类型就是 Apple，这和我们的目标是对应的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-apple-exist : Apple
proof-that-apple-exist = red-apple
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在问号的地方我们可以填入一个 red-apple，于是这个 red-apple 就证明了苹果的存在，就像我们给要求我们证明的人扔了一个红苹果。这段代码是可以通过类型检查的，这也意味着我们形式化地证明了苹果的存在。&lt;/p&gt;

&lt;p&gt;这看起来是非常平凡的结论，但是它有很深远的意义，例如我们可以设想这样一段代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-1-plus-1-equals-2 : (1 + 1 == 2)
proof-that-1-plus-1-equals-2 = ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码里 proof-that-1-plus-1-equals-2 的类型是 (1 + 1 == 2)，如果我们把这个类型视为一个需要证明存在的整体，只要我们提供一个 (1 + 1 == 2) 类型的实例，我们就证明了 1 + 1 = 2。&lt;/p&gt;

&lt;p&gt;(1 + 1 == 2) 看起来完全不像一个类型，但是这在有依赖类型的语言里（包括 Agda）是完全合法的。于是，类型和定理证明联系了起来。如果我们可以把一个数学定理写成一个类型，那么只要提供一个这个类型的实例，我们就证明了这个定理。&lt;/p&gt;

&lt;p&gt;那么如何提供一个这样的实例呢？我们需要了解 (1 + 1 == 2) 这个类型是怎么定义的，这个类型的核心是==，一种常见的定义如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data _==_ {A : Set} : A -&amp;gt; A -&amp;gt; Set where
  refl : (a : A) -&amp;gt; (a == a)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个定义里的==是一个二元操作符，接受两个参数，返回一个类型。具体的语法不重要，重要的是这个类型拥有一个名为 refl 的元素。不过和苹果的例子不同的是，这个元素不是一个简单的实例，而是一个函数，它的意思是给它任意一个 a，它可以返回一个 (a == a) 的实例。&lt;/p&gt;

&lt;p&gt;refl 称作==类型的一个构造器，我们之前的 Apple 类型里的三种苹果也是构造器，只不过那些构造器不需要参数就可以直接构造出实例，而 refl 需要参数。另外，由于 refl 是==类型唯一的构造器，也就意味着我们仅有此一种方式去构造一个==的实例。而在苹果的例子中，我们有三种不同的方式去构造一个苹果。&lt;/p&gt;

&lt;p&gt;因此，(1 + 1 == 2) 这个类型是可以被构造的，而构造它势必会用到 refl。实际上，我们可以直接在问号处填入 refl 来通过类型检查，这是因为 1 + 1 在 Agda 的计算过程中会规约为 2，于是待证明的类型变成了 (2 == 2)，而 refl 函数正好可以构造这么一个类型。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-1-plus-1-equals-2 : (1 + 1 == 2)
proof-that-1-plus-1-equals-2 = refl 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的代码要实际运行还依赖自然数和加法的定义，这里暂时略过，但是证明的原理就是构造一个类型的实例。这种类型和证明之间的联系称为柯里 - 霍华德同构（Curry-Howard correspondence）。这个理论指出，一个逻辑命题和一个类型是等价的，而命题的证明和类型的实例是等价的。上面的例子中，(1 + 1 == 2) 是一个数学命题也是一个类型，而 refl 是这个命题的证明也是这个类型的实例。&lt;/p&gt;

&lt;p&gt;实际上更常见的命题以函数的形式出现的，例如：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-addition-commutes : (a b : Nat) -&amp;gt; (a + b == b + a)
proof-that-addition-commutes a b = ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个加法交换律的证明，它本身是一个函数。它的类型签名的意思是，输入 a 和 b 两个自然数，可以输出一个 a + b == b + a 的证明，也就是对于任意自然数 a 和 b，加法交换律成立。这个函数接受了两个参数，而问号的地方需要填入的就是它的函数体。这也是柯里 - 霍华德同构的另一种常见表述，命题和函数类型等价，而函数的实现和证明等价。因此，每一个计算机程序实际上都是一个命题，而计算机程序本身就是这个命题的证明。&lt;/p&gt;
&lt;h3 id="递归构造器"&gt;递归构造器&lt;/h3&gt;
&lt;p&gt;在文章开头我们提到过一种很蹩脚的定义自然数的方式，由于自然数有无穷多个，我们实际上是没办法把它们全部枚举的。那么我们该如何表述一个无限的集合呢？我们可以用到递归：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data Nat : Set where
  zero : Nat
  succ : Nat -&amp;gt; Nat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个自然数的类型有两个构造器。zero 代表 0，也就是声明了 0 本身是一个自然数。succ 是一个函数，输入一个自然数，输出一个自然数，我们可以用 succ 构造出其他自然数。&lt;/p&gt;

&lt;p&gt;zero 是自然数，那么 (succ zero) 根据签名也会是一个自然数，(succ (succ zero)) 也会是一个自然数。succ 是后继者（successor）的意思，我们可以把 (succ zero) 视作 1，因为它是 0 的后继。接着 (succ (succ zero)) 便是 2，因为它是 1 的后继。不断叠加 succ，我们便可以获得无限多的自然数。&lt;/p&gt;

&lt;p&gt;这便是皮亚诺公理（Peano's axioms）里定义的自然数的表示法，仅仅凭借这个定义，Agda 并不能知道 1 就是 (succ zero)，因为 1 只是我们日常用来简写的符号，但是任何一个自然数都可以仅通过 succ 和 zero 两个构造器构造出来，因此我们可以确定它们处在 Nat 的集合中。&lt;/p&gt;
&lt;h3 id="分类讨论"&gt;分类讨论&lt;/h3&gt;
&lt;p&gt;让我们来证明一些简单的关于自然数的定理，首先我们需要定义加法。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_+_ : (a b : Nat) -&amp;gt; Nat
_+_ a b = ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们定义 + 为一个二元操作符，输入 a 和 b 两个自然数，输出一个自然数。问号的地方应该填入加法的函数体，但是应该填入什么呢？我们对 a 和 b 一无所知，但是我们知道它们既然是自然数，那要么是通过 zero 构造的，要么是通过 succ 构造的。我们可以使用 Agda 的交互证明功能，对 a 进行一个分类讨论。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_+_ : (a b : Nat) -&amp;gt; Nat
zero + b = ?
(succ x) + b = ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agda 的编译器可以帮我们把 a 按构造器来分类讨论，于是我们的函数有两种情况，第一种情况是 a 为 zero，第二种情况是 a 是某个数的后继，也就是 (succ x)，其中 x 为任意的自然数。这两种情况是可以覆盖所有可能的，因为自然数仅仅有这两种构造器。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_+_ : (a b : Nat) -&amp;gt; Nat
zero + b = b
(succ x) + b = succ (x + b)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于第一种情况，显然 0 + b 是等于 b 的，这样才符合我们常用的加法。对于第二种情况，x 的后继加 b 实际上等于 (x + b) 的后继。这是一个递归定义，我们可以简单考虑一下 2 + 2 是如何递归的。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;a&lt;/th&gt;
&lt;th&gt;b&lt;/th&gt;
&lt;th&gt;参数的模式匹配&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;th&gt;下一步需要计算&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;(succ 1) + 2&lt;/td&gt;
&lt;td&gt;succ (1 + 2)&lt;/td&gt;
&lt;td&gt;1 + 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;(succ zero) + 2&lt;/td&gt;
&lt;td&gt;succ (zero + 2)&lt;/td&gt;
&lt;td&gt;zero + 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;zero + 2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;可以看到前两次调用都是分支二，最后一次调用到了分支一，所以这个递归是会终止的。把递归结果代入回前两次调用，就会是 (succ (succ 2))，也就是 4，结果是正确的。&lt;/p&gt;

&lt;p&gt;因此，一个类型有哪些构造器也决定了我们在使用它们的时候需要考虑的可能，只有把每一个构造器的可能都覆盖到了，我们的函数定义才是完备的。&lt;/p&gt;
&lt;h3 id="数学归纳法"&gt;数学归纳法&lt;/h3&gt;
&lt;p&gt;我们来尝试证明一个简单的定理：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-zero-is-identity : (a : Nat) -&amp;gt; (a + zero) == a
proof-that-zero-is-identity a = ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个定理代表了对任意自然数 a，a 加上 zero 都是它自身。我们首先对 a 进行分类讨论：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-zero-is-identity : (a : Nat) -&amp;gt; (a + zero) == a
proof-that-zero-is-identity zero = ?
proof-that-zero-is-identity (succ x) = ?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一种情况里需要证明的是 (zero + zero) == zero，这个可以通过 refl 直接解决。第二种情况需要证明的是 ((succ x) + zero) == succ x。我们可以猜想到第二种情况的证明一定是需要递归的，当我们调用 (proof-that-zero-is-identity x) 的时候，我们会得到一个 ((x + zero) == x) 的证明，然后只要对两边同时应用 succ，我们就可以得到我们的目标了。实际的证明代码如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-zero-is-identity : (a : Nat) -&amp;gt; (a + zero) == a
proof-that-zero-is-identity zero = refl zero
proof-that-zero-is-identity (succ x) = cong succ (proof-that-zero-is-identity x)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 cong succ 是在两边同时加上 succ 的意思，例如我们有 a == b 的时候，根据函数的定义，也必定会有 (succ a == succ b)。&lt;/p&gt;

&lt;p&gt;我们在对 (proof-that-zero-is-identity (succ x)) 的证明中，用到了 (proof-that-zero-is-identity x)，有数学训练的读者应该很容易发现，这实际上是使用了数学归纳法，(proof-that-zero-is-identity x) 就是归纳假设。我们要证明一个定理对自然数成立的时候，只要证明它对 0 成立，同时当 k 成立的时候对 k+1 也成立，那么它就对所有自然数都成立。&lt;/p&gt;

&lt;p&gt;在定理证明器里使用数学归纳法是非常常见的，而实际上这也是由递归构造器的定义所决定的。&lt;/p&gt;
&lt;h3 id="停机检查"&gt;停机检查&lt;/h3&gt;
&lt;p&gt;程序员大概都会听过“停机问题是不可判定的”，也就是给定一个程序，我们并不能确定它在有限时间内一定会停下来。在普通的应用里这不是很大的问题，但是如果我们的程序是用来证明一个数学定理的，而我们不能确保它一定会停机，也就不能得到一个确定的证明，这是不可接受的。&lt;/p&gt;

&lt;p&gt;Agda 既然要证明定理，就需要确保所有证明都是可以停机的，这也意味着证明里不能包含死循环。为了解决这个问题，Agda 内置了一个停机检查器（termination checker），并且默认开启。我们用一个例子来解释这个停机检查是如何工作的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proof-that-zero-is-identity : (a : Nat) -&amp;gt; (a + zero) == a
proof-that-zero-is-identity zero = refl zero
proof-that-zero-is-identity (succ x) = proof-that-zero-is-identity (succ x)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初学 Agda 的人可能会写出这样的证明，这个证明的类型是正确的，但是 Agda 会提示这个证明没法通过停机检查，因为证明的第二个分支实际上是一个死循环，它在不断地用相同的参数调用自身。&lt;/p&gt;

&lt;p&gt;为了通过停机检查，我们需要确保每次递归调用自身的时候，参数都比上一次变小了。例如在我们正确的证明中，对 (proof-that-zero-is-identity (succ x)) 的情况，我们调用了 (proof-that-zero-is-identity x)，新的参数是比原来的参数更小的，于是 Agda 知道我们的递归是一定会终止的。&lt;/p&gt;
&lt;h3 id="总结"&gt;总结&lt;/h3&gt;
&lt;p&gt;通过此文，读者应该能想象到人们是怎么在 Agda 里描述和证明定理的，只要定义证明需要的类型和构造器，Agda 就能保证我们的推理符合规则和不遗漏状况。而 Agda 的核心可以非常小，我们所需要用到的东西都可以通过类型这一基本概念定义出来。&lt;/p&gt;

&lt;p&gt;Agda 的交互式证明实际上是非常酷的，但是仅仅通过文章没有办法展示，有兴趣的读者可以看千里冰封的演示视频&lt;a href="https://www.bilibili.com/video/BV1xx411L7Jr" rel="nofollow" target="_blank" title=""&gt;https://www.bilibili.com/video/BV1xx411L7Jr&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;关于类型系统，还有一个演讲也十分有趣&lt;a href="https://www.bilibili.com/video/BV1Pt4y1k7tf" rel="nofollow" target="_blank" title=""&gt;https://www.bilibili.com/video/BV1Pt4y1k7tf&lt;/a&gt;。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Sun, 16 May 2021 16:12:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/41265</link>
      <guid>https://ruby-china.org/topics/41265</guid>
    </item>
    <item>
      <title>想法：一个更彻底的去中心化社交网络</title>
      <description>&lt;h2 id="Federation还不够好"&gt;Federation 还不够好&lt;/h2&gt;
&lt;p&gt;目前的分散式（federated）社交网络的弊端在难以迁移，例如你没办法更改你在 mastodon 上的 id，或者简单迁移到另一个域名。即使你可以导出数据再在另一个服务器上导入，由于别人服务器上存着的是你旧的地址，这部分的引用会坏掉。即使你可以用重定向指向新的地址，这也需要旧的域名保持可用，问题是没有人会愿意为一个没用到的域名付费。&lt;/p&gt;

&lt;p&gt;理想情况下，用户除了可以拥有他们的数据，还应该可以拥有他们的 id。最坏的情况是，你的 mastodon 实例主打算窃取你的账户，并伪装成你，你得有一种方法保护你的账户。同时，如果你和实例主产生了过节，他打算不让你取回你的数据，你也会处在一个很滑稽的处境。然而，在现有的 federated 实现里，包括 mastodon 和 solid，实例主还是能那么干，除非所有人都购买自己的域名和运行自己的实例。&lt;/p&gt;
&lt;h2 id="更彻底的中心化"&gt;更彻底的中心化&lt;/h2&gt;
&lt;p&gt;让我们先大致设想一下更进一步的去中心化。&lt;/p&gt;

&lt;p&gt;一个推特用户，应该拥有他们全部推的数据，而不需要依赖任何在线服务。例如如果用户想发一个推，他应该先在自己本地的文件夹里创建这个推，再推送到远端的服务器上，让别人可以访问。&lt;/p&gt;

&lt;p&gt;另外，用户也应该可以拥有所有他们见到过的数据。例如他们可以保存看过的推，以及别人发给他的评论，这样的话所有交互产生的数据也可以不依赖在线服务被保存。&lt;/p&gt;

&lt;p&gt;在线服务变成数据的中转站（简称 hub），而不是数据的存储站。他们的任务是促进数据在用户间流通，而不长久拥有数据。他们只能缓存和展示用户决定要展示的数据。&lt;/p&gt;

&lt;p&gt;一个 hub 同时也是一个消息信箱。如果其他人给一个用户发送评论，当这个用户离线的时候，这些评论会被暂存在 hub 里，等待被用户接收。而如果这个用户是在线的，他们就可以充当自己的 hub 直接与他人交互。我们最好用 udp 的方式交互，来尽量减少 hub 服务商经手的数据。&lt;/p&gt;
&lt;h2 id="维护id"&gt;维护 id&lt;/h2&gt;
&lt;p&gt;现在问题变成，我们如何为每个用户维护一个通用的 id？用户应该可以在不同 hub 间转移，而这个 id 会保持有效。一个可能的选择是用 pgp 公私钥对，然后让其他人保存公钥用作引用。但是公私钥对用起来很麻烦，尤其是当私钥丢失的时候。&lt;/p&gt;

&lt;p&gt;我认为最好的 id 就是邮箱地址。几乎每个人都有邮箱地址，而邮箱地址的所有权是非常好验证的，只要发一封邮件问问就可以了。另外，邮箱本身就是一个消息信箱，我们也许不需要 hub 来暂存消息。如果你想在互联网上找一个人，一个邮箱地址是最不可能失效的。&lt;/p&gt;

&lt;p&gt;不过，hub 的存在还是能提高可访问性，我们还是会需要它们。因此我们需要一个类似 ns 服务器的东西，去把邮箱地址和 hub 的地址关联起来。这个服务需要做得尽量简洁和公开，或者我们也可以尝试类似 namecoin 的区块链方案。&lt;/p&gt;

&lt;p&gt;另一个问题是，我们如何保证一个推文或者消息是来自于一个邮箱的所有人？如果我们通过 hub 来阅读他人的信息，那么 hub 将有可能伪造这些信息。因此我们仍然需要公私钥对，我们的 ns 除了要维护一个人关联的 hub，还要维护他们所用的公钥，这些公钥的设立要先经过邮箱验证。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;在深入更多细节之前，我想要了解其他人对这样模型的想法。你觉得这样的社交网络可以吸引你吗？&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Tue, 05 May 2020 22:55:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/39832</link>
      <guid>https://ruby-china.org/topics/39832</guid>
    </item>
    <item>
      <title>关于数据自有的讨论</title>
      <description>&lt;p&gt;之前读到 solid project 关于数据自有的理念时，一直感觉这个应该是互联网的未来。简单地说，就是假如我想要发布一个微博，我会先在本地或者自己的服务器上发布，然后再授权 weibo，twitter，facebook 等来我的服务器上读取，这样我们的数据不会因为互联网公司而消失。&lt;/p&gt;

&lt;p&gt;现实中已经有的类似理念的去中心化的产品是 mastodon，以及 matrix 等，但是它们在迁移账户上面会有些问题，因此我想和大家讨论一下这些问题和看看能不能找到解决方案。&lt;/p&gt;

&lt;p&gt;mastodon 将 id@domain 的形式作为唯一 id，这个 id 是不可以更改的，其他服务器在交互中引用的也是这个 id。在现有的方案里，人们可以考虑在新建 id 之后，将原先的 id 做一个重定向。这样的话会存在一些问题，例如如果切换了服务器，旧数据的正常工作需要依赖旧服务器正常工作，但是谁能保证旧的维护者会一直运行下去呢？我认为这样其实并没有实现数据自有的优势。而且最理想的情况是，每个人搭一个服务器，但这样的话，为了维持重定向，我们也一样必须保证域名的可访问，相当于一直要为服务器和域名续费。&lt;/p&gt;

&lt;p&gt;我认为为了解决迁移的问题，应该有一套独立的 id，称作 uid 吧，然后维护一个 uid-&amp;gt;mastodon id 的映射。然而这个 uid 的维护，似乎必须要是中心化的，相当于现在的 dns 系统。然后这个 uid 需要保持有效，在不同服务器里作为引用。&lt;/p&gt;

&lt;p&gt;假如我们要设计这么一套 uid，我认为最简单的方式就是用邮箱地址，因为邮箱地址本身对个人用户是几乎不需要成本就能一直持有的。假如我们要做这个中心化服务器，我希望的设计是，服务器不存储任何密码，每当用户要更改他的映射，只需要在邮箱里确认邮件，这样看起来是最容易被接受的，因为不会涉及到很多用户隐私。&lt;/p&gt;

&lt;p&gt;大家怎么看？&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Sat, 07 Mar 2020 13:39:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/39571</link>
      <guid>https://ruby-china.org/topics/39571</guid>
    </item>
    <item>
      <title>使用 associationist 玩转 Rails 虚拟关联</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/CicholGricenchos/associationist" rel="nofollow" target="_blank" title=""&gt;Github Repo&lt;/a&gt;
&lt;a href="https://github.com/CicholGricenchos/associationist/blob/master/README_zh.md" rel="nofollow" target="_blank" title=""&gt;中文文档&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一般来说 Rails 的关联是要在数据表里通过外键实现的，但是有时候会有一些形式上是关联的数据，却没法通过数据表实现。&lt;/p&gt;

&lt;p&gt;例如我们想在一个保存为 text 字段的帖子中，查询所有提到的人，并且使用 includes 一并加载出来，也就是我们想：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mentioned_people&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 所有被提到的Person&lt;/span&gt;
&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:mentioned_people&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;往常我们是做不到的，因为 mentioned_people 和 post 之间并没有真正存在的关联。这时候我们可以用 associationist 虚拟出一个自定义的关联，具体代码是这样的：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Associationist&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;name: :mentioned_people&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;type: :collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_mentioned_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_mentioned_ids&lt;/span&gt; &lt;span class="c1"&gt;# 返回一个id数组&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/提取id的模式/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:first&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在直接在 post 对象上调用已经可以正确取出关联了，并且可以像往常一样在之后叠加 limit, count 等方法。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mentioned_people&lt;/span&gt;
&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mentioned_people&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mentioned_people&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过只定义了 scope 的情况，在 preload 的时候还是会有 n+1 问题，因为 active record 的 preloader 还不知道怎么批量加载这些关联，需要我们自己定义：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Associationist&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;name: :mentioned_people&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;type: :collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_mentioned_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;# preloader需要返回一个key为对象，value为关联数据的hash&lt;/span&gt;
    &lt;span class="ss"&gt;preloader: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;people_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:extract_mentioned_ids&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;people_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;people_ids&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;]}.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;
      &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_mentioned_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;people_hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 preloader 做的事就是将一组 post 关联的 people id 先取出来，然后使用一次 sql 查询查出，再安装回 post 上。&lt;/p&gt;

&lt;p&gt;现在 includes 方法也可以正常使用了，并且可以像往常一样添加多级的 includes：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:mentioned_people&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;mentioned_people: :address&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事实上可以定义成虚拟关联的不只 scope，可以是任何对象：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Associationist&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;name: :stock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;preloader: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stock&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样我们可以把一些复杂 sql 甚至不是 sql 的东西抽象成关联。事实上我设计 associationist 的初衷，就是想让一个很复杂的库存查询的取用变简单。&lt;/p&gt;

&lt;p&gt;目前 associationist 只提供了最基础的虚拟关联，其实还可以更进一步去提供一些操作 collection proxy 的方法等等，这么做可以让 active record 真正变成多数据源的，不仅仅能用 sql。（不过没需求就算了）&lt;/p&gt;

&lt;p&gt;顺便一提，我之前做的用来实现 shopify 的智能类目的 gem：&lt;a href="https://ruby-china.org/topics/34865" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/34865&lt;/a&gt;，已经改为基于 associationist 实现。
smart_collection 里面的 scope 是不定的，对于每个条目都可能生成不同的 scope，所以 preloader 没有这个 Post 的这么好写，要额外用到一个 cache store 或者 cache table，有需要的可以参考参考。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Sun, 26 May 2019 17:02:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/38571</link>
      <guid>https://ruby-china.org/topics/38571</guid>
    </item>
    <item>
      <title>在多域名系统中同步 session - 坑与解决方案</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;我们网站是一个多域名的电商系统，有 3 个子站，但是共用一套购物车，添加购物车是在子域名，而下单是在主域名进行的，所以涉及到在 3 个域名下同步购物车的问题。这个场景可能在现实中不太常见，不过解决问题的过程还是挺有趣的，于是把几年来的一些经历做个记录。&lt;/p&gt;
&lt;h2 id="使用ajax访问主域名下的cookie"&gt;使用 ajax 访问主域名下的 cookie&lt;/h2&gt;
&lt;p&gt;这是最初使用的方案，所有用户信息的 cookie 都存在主域名，在子域名每次被打开的时候，用 ajax 请求来获取主域名的 cookie 信息，然后用这些数据来更新页面上的购物车商品数等等。用户在子域名下添加产品到购物车的请求，也是 ajax 发到主域名的。由于支付和账户页面都在主域名，子域名只需要很少的 session 信息，所以这种做法也没有什么问题。&lt;/p&gt;

&lt;p&gt;到 16 年左右，为了用户隐私，各大浏览器开始禁用第三方 cookie。在 A 站向 S 站发送一个 ajax 请求，对于 A 站来说 S 站是第三方，这个请求对 S 站 cookie 的访问就会被阻断。所以 ajax 这个方案不能再用了，加购物车对主域名的读写都是无效的，用户跳转到主域名之后就会发现购物车根本没商品。&lt;/p&gt;
&lt;h2 id="使用307跳转同步session_token"&gt;使用 307 跳转同步 session_token&lt;/h2&gt;
&lt;p&gt;既然第三方 cookie 被禁用了，那只要把访问变成第一方访问就好了。我们采用的方式是，每当访问子域名时，如果当前 cookie 中没有一个 session_token，就用 307 跳转到主域名获取一个，写入子域名里。这样各站都能获得到一个统一的 session_token，再用这个 session_token 到 redis 里取用一个 session。可以看出这和 oauth 的流程挺像的，不过我们的系统需要在匿名的情况（允许不登录加购物车）下同步这个 session_token，不像 oauth 有个账户 id 之类的凭据，偶尔会出问题。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/57d6a191-a282-4ed0-bfee-6112bb0f675a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这里使用 307 可以让 POST 请求维持原样，302 则会变成 GET 请求。&lt;/p&gt;

&lt;p&gt;一开始生成在子域名的 client_key 的作用是防止循环跳转，假如客户浏览器没有启用 cookie，即使 consume_auth_code 的请求回来了，下一次跳转还是会发现缺少 session_token，又会重新 generate_auth_code，陷入循环。而 consume_auth_code 请求中一旦发现 client_key 和子站域名里的不一致，就可以终止这个流程，并提示用户启用 cookie。&lt;/p&gt;
&lt;h2 id="通过时间戳解决session不一致"&gt;通过时间戳解决 session 不一致&lt;/h2&gt;
&lt;p&gt;然而启用了这套系统，还是有零星的购物车产品丢失的情况出现。调查发现是两个 generate_auth_code 请求同时到达主域名，由于主域名 cookie 里还没 session_token，所以两个请求都生成了 session_token，写入 cookie 然后返回。其中有一个会失效，子域名也会因此得到不同步的 session_token。&lt;/p&gt;

&lt;p&gt;当时我一直觉得这个情况是无解的，因为需要加锁，但是对两个匿名请求没办法加锁。有一天在网上聊到这个问题，有位网友才提醒我这种情况可以用乐观锁的，若当时没办法区分保留哪一个，就两个都保留，事后再统一选择一个。&lt;/p&gt;

&lt;p&gt;于是我给 cookie 里的 session_token 键加上一个时间戳，如 session_token.1511254333304564304，这样一个域名下可能出现多个 session_token，但是后端总是使用时间戳最大的，然后把其他 token 都在 redis 里重定向到这个选定的 token，这样就解决了不一致的问题。&lt;/p&gt;
&lt;h2 id="使用meta跳转应对webkit对307取用cookie的封杀"&gt;使用 meta 跳转应对 webkit 对 307 取用 cookie 的封杀&lt;/h2&gt;
&lt;p&gt;苹果在 safari 11 里引入了一套新的机制，&lt;a href="https://webkit.org/blog/7675/intelligent-tracking-prevention/" rel="nofollow" target="_blank" title=""&gt;Intelligent Tracking Prevention&lt;/a&gt;，对跨站 cookie 的访问阻断更严格了。ITP 会通过机器学习来阻断 cookie 访问，另外 307 跳转也被归到了第三方 cookie 的范畴，还有一些玄学的判定条件，例如用户如果没在 24 小时内访问 S 站，那么 A 站到 S 站的 307 跳转是访问不到 S 站的 cookie 的。&lt;/p&gt;

&lt;p&gt;这个机制也一度让我们一筹莫展，甚至开始考虑仿照 google analytics 的 auto linker，在 url 里传递 session_token，然后应用&lt;a href="https://github.com/Valve/fingerprintjs2" rel="nofollow" target="_blank" title=""&gt;浏览器指纹&lt;/a&gt;和 ip 来对某个客户端同步 session。当然这是很危险的，因为用户的 token 要暴露到 url 中，可能会被利用，而且手机的浏览器指纹重合率很高，最终我们也没这么干。&lt;/p&gt;

&lt;p&gt;最后我们死马当活马医，既然 307 被拦截，那 200 总行了吧？我们尝试了用&lt;a href="https://en.wikipedia.org/wiki/Meta_refresh" rel="nofollow" target="_blank" title=""&gt;meta refresh&lt;/a&gt;进行跳转，并且意外的发现是可行的，cookie 访问没有被 ITP 拦截。于是 safari 11 这个问题暂时这么解决了，用 js 来跳转应该也是可行的。&lt;/p&gt;
&lt;h2 id="向单域名迈进"&gt;向单域名迈进&lt;/h2&gt;
&lt;p&gt;即使这样，还是有零星的 session 不同步，原因五花八门，有的用户干脆禁用了我们主域名的 cookie，有的单独清了主域名的 cookie（主域名的 token10 年过期，子域名都是 session 级别的）。最奇葩的是 firefox，部分 firefox 浏览器会无端延长 cookie 的生命，一个 session 级 cookie 甚至能活一个月之久，理论上关浏览器就应该消失的。而且还会出现单个域名下，有多套 cookie 轮换，例如用户需要跳转到 paypal 支付，一分钟后跳转回来的时候却是另一套 cookie 了。&lt;/p&gt;

&lt;p&gt;于是，我们也不得不将方案逐渐改为单域名。目前，我们让所有域名都可以进行下单流程，不依赖主域名，至少这样能让用户下单当前站的商品。同时在 url 里带一部分 session_token，可以及时发现 session 不一致，并且提示用户重开浏览器或清空 cookie 试试。&lt;/p&gt;

&lt;p&gt;总的来说，这种多域名的方案已经不再推荐，如果有新的站点想这么干，还是及早悬崖勒马为好:-)。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Tue, 27 Mar 2018 17:00:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/35339</link>
      <guid>https://ruby-china.org/topics/35339</guid>
    </item>
    <item>
      <title>分享一个新做的 gem：smart_collection</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/CicholGricenchos/smart_collection" rel="nofollow" target="_blank" title=""&gt;Github Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在电商环境中，我们常常需要根据一些条件去选择一组商品，例如从类目 A 和类目 B 里选出价格少于 10 元的产品。而且我们希望当某个产品涨价大于 10 元的时候，会自动退出这个 collection。&lt;/p&gt;

&lt;p&gt;这是 shopify 的一个卖点：&lt;a href="https://help.shopify.com/api/reference/smartcollection" rel="nofollow" target="_blank" title=""&gt;智能类目&lt;/a&gt;，我把它做成了一个 AR 的插件，让其他 Rails 项目也能实现类似的功能。&lt;/p&gt;

&lt;p&gt;我们可以这样去定义一个 Collection Model：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateCollections&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:collections&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:rule&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt; &lt;span class="ss"&gt;:cache_expires_at&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;serialize&lt;/span&gt; &lt;span class="ss"&gt;:rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;SmartCollection&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;items: :products&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建一个 collection：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;rule: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;and: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;or: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;association: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Catalog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="vi"&gt;@pen_catalog.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;source: &lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;association: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s1"&gt;'Catalog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="vi"&gt;@pencil_catalog.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="ss"&gt;source: &lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;condition: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;joins: &lt;/span&gt;&lt;span class="s1"&gt;'properties'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;where: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;properties: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'Red'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;选出商品：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;products&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 选出水笔和铅笔中有“红色”属性的产品&lt;/span&gt;
&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;in_stock: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 关联返回的仍旧是scope，可以继续进行where order等操作&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现原理是自定义一个 association，用条件拼出来的 scope 重载掉 association_scope，因为 rails5 里 scope 可以 or，用 merge 和 or 拼接很方便。condition 子句原样支持 AR 的 joins 和 where 参数，我发现这样的表达力是最好的，就不自己造其他 dsl 了。&lt;/p&gt;

&lt;p&gt;这时直接去用可能会比较慢，因为每个集合的 scope 都需要一条查询，如果批量会有 n+1 问题，我们需要使用缓存。&lt;/p&gt;

&lt;p&gt;smart_collection 提供了 table 缓存和 cache_store 缓存，启用之后就可以使用 preload 了，如果缓存过期，单个读取和 preload 都会更新缓存。&lt;/p&gt;

&lt;p&gt;启用缓存：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateSmartCollectionCachedItems&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:smart_collection_cached_items&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:collection_id&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:item_id&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;serialize&lt;/span&gt; &lt;span class="ss"&gt;:rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;SmartCollection&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;items: :products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;cached_by: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;table: :default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hour&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;#cached_by: {&lt;/span&gt;
    &lt;span class="c1"&gt;#  cache_store: Rails.cache,&lt;/span&gt;
    &lt;span class="c1"&gt;#  expires_in: 1.hour&lt;/span&gt;
    &lt;span class="c1"&gt;#}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;products: :properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 不会有n+1问题&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;隐藏特性：对于 table 缓存，eager_load 也是可行的，但是 eager_load 的时候没法判断 collection 的缓存是否过期，所以只适合定期更新全部缓存的场景。&lt;/p&gt;

&lt;p&gt;缺陷：在 rule 里存储的数据，如果源数据被删掉了，就会出错，未来考虑把 collection 依赖的数据都保存起来，实现 dependent: :destroy 之类的特性。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Wed, 10 Jan 2018 13:55:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/34865</link>
      <guid>https://ruby-china.org/topics/34865</guid>
    </item>
    <item>
      <title>做了个使用前缀树的 router，可供 mruby 使用</title>
      <description>&lt;p&gt;repo: &lt;a href="https://github.com/CicholGricenchos/Mrouter" rel="nofollow" target="_blank"&gt;https://github.com/CicholGricenchos/Mrouter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近遇到用 ngx_mruby 匹配路由的需求，我们网站曾经有两三百条为了 SEO 做的 url，大概是这个样子：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get 'office-supplies(.:format)' =&amp;gt; 'catalog#category', :id =&amp;gt; 2, :lang=&amp;gt;'en', :version =&amp;gt; 'v1'
get 'computers-and-parts(.:format)' =&amp;gt; 'catalog#category', :id =&amp;gt; 3, :lang=&amp;gt;'en', :version =&amp;gt; 'v1'
get 'accessories(.:format)' =&amp;gt; 'catalog#category', :id =&amp;gt; 4, :lang=&amp;gt;'en', :version =&amp;gt; 'v1'
get 'office-supplies/:name(.:format)' =&amp;gt; 'catalog#category', :id =&amp;gt; 2, :lang=&amp;gt;'en', :version =&amp;gt; 'v1'
get 'computers-and-parts/:name(.:format)' =&amp;gt; 'catalog#category', :id =&amp;gt; 3, :lang=&amp;gt;'en', :version =&amp;gt; 'v1'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在要做前后端分离的手机版，老板不想放弃这些链接，把这些都打包进 js 又会增加体积，干脆就在 nginx 上匹配了。&lt;/p&gt;

&lt;p&gt;mruby 目前还没有现成的 router，看了一下 mustermann，用了新语法不好移植。而且 mustermann 是用正则一行一行匹配，在我们这种用例可能动不动就要匹配三百多次，性能也不好。我觉得用前缀树做是比较合适的，以后可能还可以搞成 dfa，所以就自己造了个轮子。&lt;/p&gt;

&lt;p&gt;性能测试下来，匹配单行路由耗时是 mustermann 的 9 倍左右，但是当总路由数到几十以上，优势就比较明显了。有空的话改用 c 写应该更好一些。&lt;/p&gt;

&lt;p&gt;虽然这个 gem 是为 mruby 搞的，但是 ruby 也是能用的，当然现在功能还很简单，有需要再加吧。。。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Wed, 11 Oct 2017 20:49:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/34358</link>
      <guid>https://ruby-china.org/topics/34358</guid>
    </item>
    <item>
      <title>Fiber 的状态有没有可能保存？(FRP 游戏的流程状态如何保存？)</title>
      <description>&lt;p&gt;我想用 Fiber 描述一些游戏剧情之类的脚本，因为游戏中会有很多自由状态，所以一段剧情大概是这样的：&lt;/p&gt;

&lt;p&gt;第一段 =&amp;gt; 角色自由移动 =&amp;gt; 触发第二段 =&amp;gt; 角色自由移动 =&amp;gt; 触发第三段&lt;/p&gt;

&lt;p&gt;如果有 Fiber 就可以取代状态机，把三段剧情用同步的方式写出来，每一段结束的时候 yield，下一段触发的时候 resume。&lt;/p&gt;

&lt;p&gt;但是遇到一个问题就是 Fiber 的状态没办法保存，如果角色在自由移动的时候进行存档，没办法把 Fiber 导出成状态量。&lt;/p&gt;

&lt;p&gt;刚才去看了一下 elm-lang，感觉上也会存在类似的问题，FRP 的操作流程是一个整体，而能序列化的只是 Model 的状态，感觉只能做一些&lt;a href="http://elm-lang.org/examples/mario" rel="nofollow" target="_blank" title=""&gt;这种&lt;/a&gt;demo，一旦涉及到不能在一次会话里解决的流程，似乎无法实现。。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Tue, 20 Oct 2015 21:25:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/27758</link>
      <guid>https://ruby-china.org/topics/27758</guid>
    </item>
    <item>
      <title>Ruby marshal dump 的东西自己也不认得</title>
      <description>&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#encoding: utf-8&lt;/span&gt;

&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"keywords"&lt;/span&gt;
&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wb'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="no"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="no"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:read&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby 1.9 运行即出错，问题出在 keywords 这个特定的字符组合
&lt;img src="https://l.ruby-china.com/photo/2015/c44384b8a472737c50d3ad4e8c09118d.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;感觉只可以做些自定义的转码来解决。。有没有不那么麻烦的方法啊。。。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Sun, 11 Oct 2015 03:44:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/27626</link>
      <guid>https://ruby-china.org/topics/27626</guid>
    </item>
    <item>
      <title>用 WebSocket 把前端的 View Model 双向绑定到后端，将 Web App 做成持久会话如何？</title>
      <description>&lt;p&gt;前端用 MVVM 框架监听和呈现变化，VM 的变化通过 websocket 同步到 Ruby 后端的 VM，反之也将后端的变化同步到前端。&lt;/p&gt;

&lt;p&gt;Ruby 维护数据的逻辑，Js 维护界面的逻辑，前后的接口就由框架去维护，保证兼容就可以了。&lt;/p&gt;

&lt;p&gt;其实这本身只是 API 的包装，不过重点是可以抛弃掉 HTTP 的 请求/响应 机制，因为这实在是太费事了。。信息传递必须通过一次一次的请求，而每次会话都需要初始化。&lt;/p&gt;

&lt;p&gt;当然这种东西不会适合普通的网站，但是对于一些动态性高的管理界面的制作应该很有用，其实传统 GUI 程序本来就是这样的嘛。&lt;/p&gt;

&lt;p&gt;实现的话我想只要给每个会话分配一片 eval 的空间就可以了，程序在其中执行，过时的会话进行序列化储存或者销毁掉，这样来提供多用户的支持。&lt;/p&gt;

&lt;p&gt;双向绑定的话我已经实践过了，还是很不错的诶。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Wed, 09 Sep 2015 21:02:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/27260</link>
      <guid>https://ruby-china.org/topics/27260</guid>
    </item>
    <item>
      <title>用 Ruby 的 Enumerator 实现的流</title>
      <description>&lt;p&gt;啃 SICP 到第三章，想试试将无穷流应用在 Ruby 里，顺便研究一下 Enumerator 的用法。&lt;/p&gt;

&lt;p&gt;Enumerator 可以直接用下面的代码创建：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enumerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;yielder&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;yielder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;yielder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;yielder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 2&lt;/span&gt;
&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 3&lt;/span&gt;
&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码挺好懂，但也有些诡异。可以看到每次执行 e.next 会要求 enumerator 返回一个值，这个值就是 proc 里的代码 yield 出来的值。因为 proc 里有个死循环，所以这就是一个无穷流，会 yield 出 1,2,3,3,3,3,3……的序列。&lt;/p&gt;

&lt;p&gt;每次 yield 完毕之后 proc 的代码执行会暂停，等待下次调用 e.next 之后才会继续执行，这里不涉及返回什么的，就是单纯的控制跳转。&lt;/p&gt;

&lt;p&gt;不过这和方法对块参数的 yield 很不一样，一般用 yield 是把控制权从方法移交到块里，块里 yield 又可以移交到哪里呢？&lt;/p&gt;

&lt;p&gt;查了一些资料，这种 yield 和 Fiber 的 yield 很相似，但是 Enumerator 本质上是不是用 Fiber 构建的还不清楚。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fiber 是一种可控的 Thread，控制权可以通过 yield 和 resume 随意转移，yield 之后 Fiber 就会被冻结，等待 resume 的唤醒。&lt;/p&gt;

&lt;p&gt;Enumerator 大概搞明白之后，就开始 Stream 类的构建。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stream&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
    &lt;span class="vi"&gt;@enumerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enumerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
    &lt;span class="vi"&gt;@evaluated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="vi"&gt;@values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="vi"&gt;@evaluated&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@evaluated..n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="vi"&gt;@evaluated&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="vi"&gt;@values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@enumerator.next&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="vi"&gt;@values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;first&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# y &amp;lt;&amp;lt; 等价于 y.yield&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rest&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
    &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;accumulate&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
    &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]}.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enumerator 只是&lt;strong&gt;一段不断向前执行的代码&lt;/strong&gt;，所以要把每次执行的结果保存起来，并提供索引访问的方法，才是一个真正的序列。而且缓存可以避免每次访问，Eumerator 都要从头开始计算。&lt;/p&gt;

&lt;p&gt;我把.next 求出来的值存到一个数组里，并保存现在计算到第几项，如果访问已经计算过的项就从 values 数组中返回，否则就进行计算。这就实现了 lazy evaluation（或者说 Enumerator 本身就是 lazy 的）。&lt;/p&gt;

&lt;p&gt;之后还要提供一些处理流的方法，比如 map，这些方法也应该返回 lazy 的流，所以方法体都是流的创建。&lt;/p&gt;

&lt;p&gt;实际上这里有很多重复的代码，还有频繁地赋值，但是我还没想到该怎么去抽象。&lt;/p&gt;

&lt;p&gt;试试按书上的做法去计算圆周率：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 根据π/4 = 1 - 1/3 + 1/5 - 1/7 + ...&lt;/span&gt;

&lt;span class="c1"&gt;# seq 是 1, -1/3, 1/5, -1/7, ... 的流&lt;/span&gt;
&lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#sum是seq n项和的流&lt;/span&gt;
&lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accumulate&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;#把sum每项扩大4倍就是pi的流&lt;/span&gt;
&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [4.0, 2.666666666666667, 3.466666666666667, 2.8952380952380956, 3.3396825396825403, 2.9760461760461765, 3.2837384837384844, 3.017071817071818, 3.2523659347188767, 3.0418396189294032]&lt;/span&gt;

&lt;span class="c1"&gt;#但是这个流收敛得很慢，用书上给的欧拉加速收敛的方法变形一遍&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eular_transform&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
  &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;s0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eular_transform&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [3.166666666666667, 3.1333333333333337, 3.1452380952380956, 3.13968253968254, 3.1427128427128435, 3.1408813408813416, 3.142071817071818, 3.1412548236077655, 3.1418396189294033, 3.141406718496503]&lt;/span&gt;

&lt;span class="c1"&gt;#快了不少，对变形后的流还可以不断地应用变形，实现更快的加速&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;accelerate&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
  &lt;span class="no"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eular_transform&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
      &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accelerate&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [3.166666666666667, 3.142105263157895, 3.141599357319005, 3.1415927140337785, 3.1415926539752927, 3.1415926535911765, 3.141592653589778, 3.1415926535897953, 3.141592653589795, NaN]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby 实现计算出的结果和 Scheme 是完全一致的，但是 Ruby 的实现总是有点怪，因为 Scheme 里的流本质上是链表，而我做出来的是数组。这样就没办法递归地去创建流，比如下面这样：&lt;/p&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;ones是生成1, 1, 1, ...的流，把首项设为1，把下一项指向自身，这样访问下一项就是访问自身的首项&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;define&lt;/span&gt; &lt;span class="nv"&gt;ones&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cons-stream&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过流的本质就是迭代，scheme 是用一个 proc 去迭代，ruby 是用 loop 去迭代，ruby 把 Enumerator 写成一段迭代的代码，还是很酷的。&lt;/p&gt;

&lt;p&gt;实际上单纯用 lambda 也是可以实现 Enumerator 的构造的，只是没有 Fiber 那样 magic：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Enum&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt;
    &lt;span class="vi"&gt;@enumerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;
    &lt;span class="vi"&gt;@enumerator.call&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;联动：
&lt;a href="https://ruby-china.org/topics/23606" title=""&gt;[译] Ruby 2.0 Works Hard So You Can Be Lazy&lt;/a&gt;
&lt;a href="http://stackoverflow.com/questions/9052621/why-do-we-need-fibers" rel="nofollow" target="_blank" title=""&gt;Why do we need fibers&lt;/a&gt;&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Thu, 04 Jun 2015 21:41:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/25885</link>
      <guid>https://ruby-china.org/topics/25885</guid>
    </item>
    <item>
      <title>使用 Fiddle 调用 C 函数</title>
      <description>&lt;p&gt;中英文关于 Fiddle 的资料好像都挺少，之前为了调用 win32api 去研究了一下，现在把一些经验记在这里。&lt;/p&gt;

&lt;p&gt;先贴上两段代码：&lt;/p&gt;

&lt;p&gt;C 代码：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;hw_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;integer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// compiled by&lt;/span&gt;
&lt;span class="c1"&gt;// gcc hw.c --share -o hw.so&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby 代码：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'fiddle'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'fiddle/import'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Lib&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Fiddle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Importer&lt;/span&gt;
  &lt;span class="n"&gt;dlload&lt;/span&gt; &lt;span class="s1"&gt;'./hw.so'&lt;/span&gt;
  &lt;span class="n"&gt;extern&lt;/span&gt; &lt;span class="s1"&gt;'int hw(char*, int)'&lt;/span&gt;
  &lt;span class="n"&gt;extern&lt;/span&gt; &lt;span class="s1"&gt;'void hw_s(*)'&lt;/span&gt;

  &lt;span class="no"&gt;Struct_Test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt; &lt;span class="s1"&gt;'char* string, int integer'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello World."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;#=&amp;gt; 打印 Hello World.&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;   &lt;span class="c1"&gt;#=&amp;gt;9&lt;/span&gt;

&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Lib&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Struct_Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;malloc&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
&lt;span class="no"&gt;Lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hw_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;   &lt;span class="c1"&gt;#=&amp;gt;1000&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;   &lt;span class="c1"&gt;#=&amp;gt;rest&lt;/span&gt;
&lt;span class="no"&gt;Fiddle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上是两个简单的调用例子，写了两个 C 函数，hw 打印输入的字符串，然后把输入的 int 原样返回，hw_s 接受一个结构体指针，然后对所指的结构做一些修改。&lt;/p&gt;

&lt;p&gt;Ruby 方面，建立了一个 Lib 模块，然后扩展了&lt;code&gt;Fiddle::Importer&lt;/code&gt;，Importer 封装了 Fiddle 的部件，可以用 DSL 的语法引入 C 库。&lt;/p&gt;

&lt;p&gt;我们首先用 dlload 导入编译好了的动态链接库，然后用 extern 声明了两个函数原型。&lt;/p&gt;

&lt;p&gt;extern 语句会对参数字符串进行解析，取出函数名、返回值类型和参数类型，用这些参数创建一个&lt;code&gt;Fiddle::Function&lt;/code&gt;对象，也就是用于调用的 C 函数。并且给 Lib 模块注册了方法，使其可以通过类似&lt;code&gt;Lib.hw()&lt;/code&gt;的方式调用这个函数。&lt;/p&gt;

&lt;p&gt;这里 hw_s 函数接受的是一个结构体，而我在参数里只写了一个*符号，实际上去看看解析用的代码：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="sr"&gt;/\*/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/\[\s*\]/&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;TYPE_VOIDP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以发现所有&lt;code&gt;[] *&lt;/code&gt;都会被转化成空指针型，所以指针的类型是无所谓的，hw 参数里的 char 实际上也可以省略。&lt;/p&gt;

&lt;p&gt;先不管结构体的创建，看看 hw 函数的调用。&lt;/p&gt;

&lt;p&gt;给 hw 传一个字符串，运行一下发现已经被打印出来了，第二个参数传入了 9，也可以发现这个 9 被返回到了 a 里。代码很直观，这样去调用 C 函数就不用像 require 那样特意去做一层封装。&lt;/p&gt;

&lt;p&gt;不过常见的情况参数可能不会如此简单，像 win32api 很多要传一个结构体，那就涉及到结构体的构建了。&lt;/p&gt;

&lt;p&gt;再次回到 Lib 模块，最后那句代码就用 struct 方法声明了一个结构体。struct 接受描述成员信息的字符串数组，也可以接受一个字符串，字符串的话一开始会被&lt;code&gt;split(/\s*,\s*/)&lt;/code&gt;，也就变成一个数组了。&lt;/p&gt;

&lt;p&gt;接着解析器会把成员的类型和名字抽取出来，用&lt;code&gt;Fiddle::CStructBuilder.create&lt;/code&gt;创建一个结构体类，然后赋值给 Struct_Test，这个类包含 malloc 方法，可以用来申请内存，然后结构体的成员也会被注册为实例方法，就可以对结构体的成员进行操作了。&lt;/p&gt;

&lt;p&gt;底下的代码用 malloc 分配了一个结构体 s，然后把 s.string 设成了字符串 test，接着调用 hw_s。调用结束后检查结构体，发现 s.integer 已经被赋值为 1000，而 s.string 的第一个字符也按照 C 代码所写的被修改了。&lt;/p&gt;

&lt;p&gt;接下来就是比较重要的一步了，用 malloc 分配的内存必须要进行回收，不然会造成内存泄露。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="no"&gt;Lib&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Struct_Test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;malloc&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试运行这段代码，打开任务管理器，可以看到占用内存一直飙升。&lt;/p&gt;

&lt;p&gt;这里我直接用 Fiddle.free 方法回收内存，这个方法接受一个地址，而 s.to_i 会返回 s 的地址。似乎 Fiddle 类里的 to_i 都是返回地址，这应该是个惯例。&lt;/p&gt;

&lt;p&gt;但是这样做的话，每次都要手动回收，不就和写 C 没什么差别了，有没有办法让 Ruby 的 GC 进行回收呢？&lt;/p&gt;

&lt;p&gt;当然是可以的，去翻翻 Fiddle 的源码，结构体之类的创建都是对&lt;code&gt;Fiddle::Point.new(address, size, freefunc)&lt;/code&gt;的封装，这个函数可以带上一个 freefunc 参数，freefunc 会在 GC 回收 Pointer 时调用，所以释放内存可以在 freefunc 里做，这样申请的内存就可以自动回收了。&lt;/p&gt;

&lt;p&gt;实际上这个函数 Ruby 已经提供了一份，&lt;code&gt;Fiddle::RUBY_FREE&lt;/code&gt;记录了 free() 函数的地址，我们可以直接创建一个函数对象：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;free_func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiddle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Fiddle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RUBY_FREE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;TYPE_VOIDP&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;TYPE_VOID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过麻烦的是，&lt;code&gt;Fiddle::CStructBuilder&lt;/code&gt;并没有提供传入 freefunc 的方法，而是在内部直接把 freefunc=nil 处理了，对于这一点我很不理解，不过没关系，我们可以用一个猴子补丁来解决。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Fiddle&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;CStructBuilder&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;new_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:initialize&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="c1"&gt;#添加free_func&lt;/span&gt;
          &lt;span class="n"&gt;free_func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiddle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Fiddle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RUBY_FREE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;TYPE_VOIDP&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;TYPE_VOID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="c1"&gt;#@entity = klass.entity_class.new(addr, types)&lt;/span&gt;
          &lt;span class="vi"&gt;@entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entity_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;free_func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="vi"&gt;@entity.assign_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:to_ptr&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="vi"&gt;@entity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:to_i&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="vi"&gt;@entity.to_i&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="vi"&gt;@entity&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"="&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="vi"&gt;@entity&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entity_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;new_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__LINE__&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
        def new_class.size()
          &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
        end
        def new_class.malloc()
          addr = Fiddle.malloc(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;)
          new(addr)
        end
&lt;/span&gt;&lt;span class="no"&gt;      EOS&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_class&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="kp"&gt;module_function&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在再执行那个 while 循环，内存占用维持稳定，问题解决。&lt;/p&gt;

&lt;p&gt;顺便附上一个调用 win32api 的实例：
&lt;a href="https://github.com/CicholGricenchos/ShenmeGUI/blob/master/lib/shenmegui/file_dialog.rb" rel="nofollow" target="_blank"&gt;https://github.com/CicholGricenchos/ShenmeGUI/blob/master/lib/shenmegui/file_dialog.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文的代码在：
&lt;a href="https://github.com/CicholGricenchos/tricks/tree/master/fiddle" rel="nofollow" target="_blank"&gt;https://github.com/CicholGricenchos/tricks/tree/master/fiddle&lt;/a&gt;
我的.so 是在 windows 下编译的，可能不太正宗=  =&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;在了解到这个科技之后，当然是要尝试把一些工作转到 C 去做，于是想试试用 C 来进行排序。但是很快遇到了问题，Ruby 的 Array 对象没办法直接传给 C 函数，尽管都是 Fixnum，Fiddle 类也没有相关的转换方法。&lt;/p&gt;

&lt;p&gt;于是我只能用 pack 来把数组打包成字符串，虽然测试是成功了，但 pack 和 unpack 的过程耗费了大量的时间。不知道有没有什么更好的方法。&lt;/p&gt;

&lt;p&gt;代码也贴上：&lt;/p&gt;

&lt;p&gt;C：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;merge_sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;sz&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;sz&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'fiddle'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'fiddle/import'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Lib&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Fiddle&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Importer&lt;/span&gt;
  &lt;span class="n"&gt;dlload&lt;/span&gt; &lt;span class="s1"&gt;'sort.so'&lt;/span&gt;
  &lt;span class="n"&gt;extern&lt;/span&gt; &lt;span class="s1"&gt;'void merge_sort(int[], int)'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort_by_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;pack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge_sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'l'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort_by_ruby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10000000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shuffle&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sort_by_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 5.959973 其中pack及unpack可用去 2.364577&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sort_by_ruby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; 24.802533&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然如此，性能提升还是挺明显的 : -)&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Tue, 24 Mar 2015 20:08:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/24821</link>
      <guid>https://ruby-china.org/topics/24821</guid>
    </item>
    <item>
      <title>一套使用 HTML 为 Ruby 构建 GUI 的工具</title>
      <description>&lt;h2 id="简介"&gt;简介&lt;/h2&gt;
&lt;p&gt;ShenmeGUI（先不要吐槽这个名字）是一套受&lt;a href="http://shoesrb.com/" rel="nofollow" target="_blank" title=""&gt;Shoes&lt;/a&gt;启发而诞生的 GUI 工具，拥有相似的 DSL 语法，可以便捷地实现一些轻量的 GUI 应用。&lt;/p&gt;

&lt;p&gt;程序将 DSL 语句转化为 HTML 结构，并使用 Javascript 绑定上一些事件，使 HTML 的前端和 Ruby 的后端可以进行双向的数据同步。&lt;/p&gt;

&lt;p&gt;前端视图的变化会立即同步到后端（通过对 input 等事件的监听），若触发事件，则会额外传递一个事件信号。后端接收到事件信号则执行相应的回调函数，对控件属性的修改也会即时反映到 HTML 视图。&lt;/p&gt;

&lt;p&gt;前后端的数据通信是通过 WebSocket 实现的。&lt;/p&gt;

&lt;p&gt;项目托管在 &lt;a href="https://github.com/CicholGricenchos/ShenmeGUI" rel="nofollow" target="_blank"&gt;https://github.com/CicholGricenchos/ShenmeGUI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这是我第一次做开源项目，希望得到各位前辈的指点:-)&lt;/p&gt;
&lt;h2 id="语法示例"&gt;语法示例&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'shenmegui'&lt;/span&gt;

&lt;span class="no"&gt;ShenmeGUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'Your Application'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'alert'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;onclick&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;alert&lt;/span&gt; &lt;span class="s1"&gt;'Hello World!'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'open an image'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;onclick&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_open_file_name&lt;/span&gt;
      &lt;span class="vi"&gt;@t.text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
      &lt;span class="vi"&gt;@i.src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="s1"&gt;'image path:'&lt;/span&gt;
      &lt;span class="vi"&gt;@t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textarea&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="s1"&gt;'100%'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="vi"&gt;@i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="s2"&gt;"http://7jpqbr.com1.z0.glb.clouddn.com/bw-2014-06-19.jpg"&lt;/span&gt;
    &lt;span class="vi"&gt;@p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@p.percent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@p.percent&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ShenmeGUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将会产生如图所示的界面：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://cichol.qiniudn.com/shenmegui_example.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;button 定义按钮，并通过 onclick 绑定上了点击事件。第一个按钮弹出一个对话框，第二个按钮弹出一个打开文件的对话框，将文件路径写到下方定义的 textarea 里，并改变 image 的 src 以显示这个图片。&lt;/p&gt;

&lt;p&gt;下方的两个按钮演示了进度条的增减。&lt;/p&gt;
&lt;h2 id="需要注意的事"&gt;需要注意的事&lt;/h2&gt;
&lt;p&gt;目前“打开文件”的功能是通过 fiddle 调用 windows api 实现的，所以只能在 windows 下使用，以后会尝试适配其他系统。不过其他功能是没有系统依赖的，可以试试看。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Fri, 20 Mar 2015 17:24:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/24754</link>
      <guid>https://ruby-china.org/topics/24754</guid>
    </item>
    <item>
      <title>binding 对象可以携带一个块？</title>
      <description>&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'erb'&lt;/span&gt;

&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= yield %&amp;gt;"&lt;/span&gt;

&lt;span class="n"&gt;erb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ERB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_binding&lt;/span&gt;
  &lt;span class="nb"&gt;binding&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;erb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_binding&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; "1"&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_binding&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;erb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; "1"&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; Binding&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;erb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; no block given (yield) (LocalJumpError)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kernel.binding方法不接受参数，ERB#result接受一个binding对象，这里get_binding方法接受了一个块，但是当这个方法返回的时候，那个块应该就被弃置了啊。&lt;/p&gt;

&lt;p&gt;获得 b 可以知道 b 确实是一个 Binding 对象，然而直接传入 erb.result 却可以起到块的作用，b 这个对象看起来携带了一个块。&lt;/p&gt;

&lt;p&gt;而且 erb.result(binding{1}) 在语法的角度看和 get_binding 应该没有区别，但是却报错了，其中肯定有一些特殊的机制在工作。&lt;/p&gt;

&lt;p&gt;disasm 一下代码的话发现二者对 binding 的调用是不同的，一个是 send，一个是 send_simple，说明这个语句可能有两种不同的实现方式。然后就再没办法获得更多线索了。&lt;/p&gt;
&lt;h2 id="更新"&gt;更新&lt;/h2&gt;
&lt;p&gt;stackoverflow 上的回答是在函数内产生的 binding，包括当时所有的上下文，也包含了传入的块，yield 便可以此访问函数调用时的 block，用“携带”一词可能不太合适，虽然表现上是这样。binding 在函数内和函数外生成是这种差异的原因。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Sun, 01 Mar 2015 01:37:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/24400</link>
      <guid>https://ruby-china.org/topics/24400</guid>
    </item>
    <item>
      <title>一个 Parino 博客，使用 chrome 登陆出现 attack prevented by Rack::Protection::AuthenticityToken，而 IE 正常</title>
      <description>&lt;p&gt;程序只是一个用 padrino 搭起来的非常简单的博客，使用了 Padrino Admin，代码在这个 repo，我没有修改过任何 app 的配置代码
&lt;a href="https://github.com/CicholGricenchos/cichol_blog" rel="nofollow" target="_blank"&gt;https://github.com/CicholGricenchos/cichol_blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在本地用 Linux 虚拟机运行良好，代码原封不动搬到 vps 上，原先也是良好的，但是不知道什么时候开始（今天发现）登陆管理页就会出现&lt;code&gt;WARN - attack prevented by Rack::Protection::AuthenticityToken&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;我重启了服务器，重新拷贝了一份代码，问题仍然出现，考虑可能不是代码的问题，换了 IE 就登陆成功了。实际上在 config/app.rb 里禁用 csrf 保护也可以登陆成功，但是在保存文章的时候会重定向回登陆页，我暂时还没有去看 Padrino 那个权限管理模块的源码，也不知道为什么。&lt;/p&gt;

&lt;p&gt;服务器端使用了 nginx+thin，nginx 只写了一行 proxy_pass。&lt;/p&gt;

&lt;p&gt;这个问题是由什么产生的呢？&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="更新"&gt;&lt;strong&gt;更新&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;del&gt;现在可以排除 nginx 的问题（未启用仍然出错），&lt;/del&gt;排除 thin 的问题（使用 webrick 一样出错），排除 csrf 标签错误，排除旧 session/cookie（chrome 新建隐形窗口仍旧出错，ios5 的 safari 首次访问即出错）。&lt;/p&gt;

&lt;p&gt;尝试用 curl 提交表单一样出现错误，问题肯定与这两个方面（本地服务器-vps，chrome-ie）都相关，浏览器端应该没有异常的可能，难道服务器还有未知的判断跨站的指标？或者客户端和 vps 的通信有问题？&lt;/p&gt;

&lt;p&gt;使用 sinatra 和 Rack::Protection::AuthenticityToken 在 vps 上测试良好，这样的话应该就可以认定是 padrino 的问题了。&lt;/p&gt;

&lt;p&gt;换了一个域名之后 post 可以正常工作了。。之前搞错了，并没有关闭 nginx，可能确实是 nginx 反代导致的&lt;/p&gt;

&lt;p&gt;问题已解决，是 nginx 配置有问题，我参照网上的反代配置加了几行 proxy_set_header 就可以正常工作了，还没有去了解这几行代码的含义&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Mon, 29 Dec 2014 16:27:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/23456</link>
      <guid>https://ruby-china.org/topics/23456</guid>
    </item>
    <item>
      <title>lambda binding 的一些行为</title>
      <description>&lt;p&gt;Proc 在创建的时候就会和周边的上下文绑定，如果想复用，就必须改变它的 binding。在对象内有 instance_eval 之类的方法可用，但是在对象外就没办法了，于是我想研究一下怎么在对象外复用 lambda。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="nb"&gt;local_variables&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [:b, :a] 没有:c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我的考虑是在 a 内重新创建一个和 b 一样的 lambda，于是将 b 展开，并重新用 lambda 语句创建（这里用 Proc.new 结果也相同），按道理这个新建的 lambda 应当有 a 内的 binding，但事实上是 b 的 binding 被保留了。&lt;/p&gt;

&lt;p&gt;打印这个新 lambda 和 b 的 object_id，发现是相同的。这样似乎不符合直觉，因为既然 b 展开了，行为应该和代码块是一样的才对，而且语句表达的应该是新建一个 lambda，为什么成了拷贝呢？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="nb"&gt;local_variables&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [:a]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是我试了真的传个代码块进去，结果还是不行，检查了之后发现这个块在传进 lambda 的时候已经变成了一个 Proc 对象，而它的 binding 还是在外面，也算讲得通吧。&lt;/p&gt;

&lt;p&gt;可是重新创建都不行，就没办法修改 lambda 的 binding 了，为了让它在其他地方可用，只能够用 eval 给它的 binding 注入一些需要的信息。期间我观察了一下 binding 的特性，有些出乎意料的地方。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意这一段，行为很诡异&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; NameError 找不到b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a 的 binding 应该就是 main，但却获取不到 b，难道 binding 还会保存变量的状态？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 2&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"b = 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;binding&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; NameError 找不到b&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"b = 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;binding&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; NameError 找不到b&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"b = 2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;binding&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 main 原先存在 b，就可以正确地获取到，而且值也可以更新，所以 binding 保存的不是变量的状态，而是域内存不存在这个变量。如果 lambda 创建时上下文不存在 b，即使通过 eval 在其 binding 执行了 b 的定义，这个定义也不会被带出 eval，不会为 main 带来变化，难道 eval 本身又生成了一个闭包？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;b&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过方法调用没有此限制，用 eval 给 lambda 注入方法是可行的。&lt;/p&gt;

&lt;p&gt;我觉得 eval 应该允许块作为参数，不然在对象外对块进行复用会十分困难，既然 instance_eval 这些可以存在，实现上应该也不会很难才对。&lt;/p&gt;

&lt;p&gt;======
&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/ef9d2485790898e51bf0aff90529fe13.jpg" title="" alt=""&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#b = 1&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;RubyVM&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InstructionSequence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disasm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 RubyVM::InstructionSequence.disasm 可以查看 lambda 编译后的代码，上图是 lambda 定义前不存在 b，下图是存在 b，可以看到上图调用了方法 b，而下图是个 getlocal，所以两个 lambda 本质上有不同&lt;/p&gt;

&lt;p&gt;======
&lt;strong&gt;再次补充&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我原先是想做一个模仿 js 的对象和原型继承的程序，使用纯 lambda，现在已经做出来了：&lt;br&gt;
&lt;a href="https://github.com/CicholGricenchos/tricks/blob/master/prototype/using_pure_lambda.rb" rel="nofollow" target="_blank"&gt;https://github.com/CicholGricenchos/tricks/blob/master/prototype/using_pure_lambda.rb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;其中遇到最大的问题就是 lambda 作用域的问题，不过如果 lambda 的 binding 不能改变，可以用一个全局变量向里面传递需要的参数，也就近似于改变了上下文。程序中对象的成员 lambda 几乎都是在外部生成的，没办法操作对象本身，于是我用 eval 及一个全局变量向这些 lambda 的作用域注入了一个指向对象的 this，这样它们的行为和 js 的函数应该就相同了。&lt;/p&gt;

&lt;p&gt;其实也可以不用全局变量，但是 eval 只能接收字符串代码，要获得某对象的引用必须知道它的名字，然而在这里没办法获取这个名字，期望在创建的时候设置名字也不现实，所以用这种方法传递了对象的引用。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Fri, 12 Dec 2014 16:07:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/23160</link>
      <guid>https://ruby-china.org/topics/23160</guid>
    </item>
    <item>
      <title>关于 “代码即数据” 的脑洞，如果内存里的方法可以还原成代码字符串？</title>
      <description>&lt;p&gt;最近看完了元编程，突然有个想法，如果方法可以用一种数据形式来描述，不就可以在不同的系统中传递了吗？虽然还不知道这样有什么确切的好处，但毕竟是很有意思的。&lt;/p&gt;

&lt;p&gt;关于代码的数据形式，最直观的肯定是字符串，比如可以把一个方法的源码通过 json 传到别的环境，再 eval 运行。这样就会有一种获取代码的手段，在遇到一个方法的时候，去源文件里找这个方法的源码。但是这个手段对动态生成的方法会略显无力，至少需要花一些功夫在解析上。&lt;/p&gt;

&lt;p&gt;然后我马上想到了 lisp 的“代码即数据”特性（ruby 内部应该也是类似的实现吧），内存里的方法实际上是一棵棵抽象语法树，对抽象语法树的描述就是对方法最完美的描述。&lt;/p&gt;

&lt;p&gt;现在看上去 ruby 是没有操纵这个 AST 的方法的，不知道 lisp 有没有，如果语言内置了这种功能，大概会出现不少好玩的 trick 吧。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;补充：找到了一个叫 sourcify 的 gem 可以实现此功能，还能用 ParseTree 转成 s 表达式，这样大概可以把 ruby 代码转成任意一种脚本语言。。&lt;/strong&gt;&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Fri, 21 Nov 2014 17:14:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/22804</link>
      <guid>https://ruby-china.org/topics/22804</guid>
    </item>
    <item>
      <title>HTML 5 的 Canvas 实在是太难用了</title>
      <description>&lt;p&gt;初学，拿来做点东西，结果发现这货不能斜向绘制文字，只能先旋转，绘制，再转回去，这算是开胃菜。&lt;/p&gt;

&lt;p&gt;接着，用那个 translate 为旋转移动原点，这移动竟然是叠加的，translate(100,100)，再 translate(1,1)，原点就变成了 (101,101)，而且一旦旋转后坐标系也变了，也就是说要找回原点，我必须逆着旋转一次，再逆着 translate 回去，一旦有些什么岔子，原点就丢失了，没错，它就丢失了，再也找不到了。&lt;/p&gt;

&lt;p&gt;后来，我想匹配一个斜向矩形的点击事件，为了方便我就想先把矩形转成正放的然后再匹配，结果失败了，我发现旋转后的矩形和我的鼠标坐标不是同一个参考系，而鼠标坐标是通过 mousemove 监听得到的，我没有办法在新的参考系中更新这个坐标。&lt;/p&gt;

&lt;p&gt;但是我还是错了，又过了一会，我才发现鼠标坐标是参照整个 canvas 画布的，而无论怎么旋转，都不会改变，也就是要匹配旋转后的鼠标点击事件，我不得不先对鼠标坐标做一次坐标变换。&lt;/p&gt;

&lt;p&gt;引用网上一句话，用 canvas 做东西就要“从橡胶树开始造轮子”，造出矢量等工具之后才可能谈得上开发效率。我现在觉得 canvas 唯一的作用就是做 banner 广告，稍微有一点交互都会累死人。&lt;/p&gt;

&lt;p&gt;不知道各位会用什么做图形丰富的交互应用，flash，svg，canvas，DOM？
之前都说 js+canvas 可以取代 flash，但是现在看来。。。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Sat, 26 Jul 2014 02:51:03 +0800</pubDate>
      <link>https://ruby-china.org/topics/20684</link>
      <guid>https://ruby-china.org/topics/20684</guid>
    </item>
    <item>
      <title>padrino 的 renderer，传进 HTML 码会连带标签原样输出</title>
      <description>&lt;p&gt;renderer 用的是 erb，本来想用 Maruku 转换 MD 为 HTML 再输出的，但是 render 把标签都按原样输出了，这样就没办法显示格式了？&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Mon, 14 Jul 2014 19:47:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/20488</link>
      <guid>https://ruby-china.org/topics/20488</guid>
    </item>
    <item>
      <title>mysql2 报错 Unsupported statement: BEGIN</title>
      <description>&lt;p&gt;京东云擎里使用 mysql2 的时候，包括 ActiveRecord 和 Sequel，使用 create save 等方法时会出现如下错误&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ActiveRecord::StatementInvalid - Mysql2::Error: Unsupported statement: BEGIN:&lt;/code&gt;
&lt;code&gt;Sequel::DatabaseError - Mysql2::Error: Unsupported statement:&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;【Sequel 没有给出那个 statement 是什么】
然而，若是 Sequel 中使用&lt;code&gt;DB[:users].insert()&lt;/code&gt;却可以成功完成查询，我猜测可能是 create 等方法使用了京东云不支持的查询语句（上面的 BEGIN？），但是我不知道怎么配置 AR 来禁用这些查询语句。&lt;/p&gt;

&lt;p&gt;京东云擎 mysql 的可用查询有 insert delete update select create。&lt;/p&gt;

&lt;p&gt;请问有什么方法可以解决此问题？谢谢。&lt;/p&gt;

&lt;p&gt;贴上完整的报错代码&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sequel::DatabaseError - Mysql2::Error: Unsupported statement:
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/logging.rb:58:in `query'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/logging.rb:58:in `block in log_connection_execute'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/logging.rb:33:in `log_yield'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/logging.rb:58:in `log_connection_execute'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/adapters/shared/mysql.rb:301:in `begin_new_transaction'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/transactions.rb:224:in `begin_transaction'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/adapters/shared/mysql.rb:310:in `begin_transaction'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/transactions.rb:116:in `_transaction'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/transactions.rb:100:in `block in transaction'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/connecting.rb:229:in `block in synchronize'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/connection_pool/threaded.rb:104:in `hold'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/connecting.rb:229:in `synchronize'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/database/transactions.rb:89:in `transaction'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/model/base.rb:1978:in `checked_transaction'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/model/base.rb:1520:in `block in save'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/model/base.rb:1966:in `checked_save_failure'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/model/base.rb:1520:in `save'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sequel-4.11.0/lib/sequel/model/base.rb:148:in `create'
/home/vcap/app/app.rb:19:in `block in '
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1603:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1603:in `block in compile!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:966:in `[]'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:966:in `block (3 levels) in route!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:985:in `route_eval'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:966:in `block (2 levels) in route!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1006:in `block in process_route'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1004:in `catch'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1004:in `process_route'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:964:in `block in route!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:963:in `each'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:963:in `route!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1076:in `block in dispatch!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `block in invoke'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `catch'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `invoke'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1073:in `dispatch!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:898:in `block in call!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `block in invoke'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `catch'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1058:in `invoke'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:898:in `call!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:886:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-protection-1.5.3/lib/rack/protection/xss_header.rb:18:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-protection-1.5.3/lib/rack/protection/path_traversal.rb:16:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-protection-1.5.3/lib/rack/protection/json_csrf.rb:18:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-protection-1.5.3/lib/rack/protection/frame_options.rb:31:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-1.5.2/lib/rack/logger.rb:15:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-1.5.2/lib/rack/commonlogger.rb:33:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:217:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:210:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-1.5.2/lib/rack/head.rb:11:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-1.5.2/lib/rack/methodoverride.rb:21:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:180:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:2014:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1478:in `block in call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1788:in `synchronize'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/sinatra-1.4.5/lib/sinatra/base.rb:1478:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/rack-1.5.2/lib/rack/deflater.rb:25:in `call'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/connection.rb:86:in `block in pre_process'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/connection.rb:84:in `catch'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/connection.rb:84:in `pre_process'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/connection.rb:53:in `process'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/connection.rb:39:in `receive_data'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in `run_machine'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in `run'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/backends/base.rb:73:in `start'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/server.rb:162:in `start'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/controllers/controller.rb:87:in `start'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/runner.rb:199:in `run_command'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/lib/thin/runner.rb:155:in `run!'
/home/vcap/app/vendor/bundle/ruby/1.9.1/gems/thin-1.6.2/bin/thin:6:in `'
/home/vcap/app/vendor/bundle/ruby/1.9.1/bin/thin:23:in `load'
/home/vcap/app/vendor/bundle/ruby/1.9.1/bin/thin:23:in `
'
221.203.22.245, 172.18.137.55 - - [01/Jul/2014 17:57:43] "GET /test HTTP/1.1" 500 - 0.0038
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;

&lt;p&gt;顺带一问，有没有人成功在 Win 下安装过 bson(~&amp;gt;2.2.0) 这个包？总是在安装过程中编译出错，按照官方 github 给出的方案修改 ext/native.c 仍然无法解决，错误信息中涉及 ruby 的 win32.h。这个包是 mongoid 的必需项，但是似乎没考虑支持 win 啊，类似的 issue 和提问有很多，但官方似乎根本没在意这个问题。。&lt;/p&gt;</description>
      <author>cichol</author>
      <pubDate>Tue, 01 Jul 2014 16:57:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/20257</link>
      <guid>https://ruby-china.org/topics/20257</guid>
    </item>
  </channel>
</rss>
