Erlang/Elixir [RubyConfChina2017 话题分享] 用 Erlang 快速开发 Web 快速开发框架

bhuztez · 2017年09月19日 · 最后由 kafei 回复于 2017年12月21日 · 6126 次阅读
本帖已被设为精华帖!

代码以及 slides 都在仓库里 https://github.com/bhuztez/razor

参考资料

Regular Expression Matching in the Wild https://swtch.com/~rsc/regexp/regexp3.html

Efficient submatch addressing for regular expressions https://laurikari.net/ville/regex-submatch.pdf

How Pony ORM translates Python generators to SQL queries https://av.tib.eu/media/19938

Programming Is Hard, Let's Go Shopping! https://blog.codinghorror.com/programming-is-hard-lets-go-shopping/

补充

因为时间有限删掉,或者一开始就没想到要加进去的一些东西,演讲结束后,在后面的交流中提到的,补充在这里

C-c C-k 大法好

大部分框架都是用 inotify 之类的 API 来发现文件改动,有改动就自动重启 server。可是有时候你文件改了一半,连语法都有错误,此时重启就会出现一堆莫名其妙的错误。造框架的时候,要把这问题搞好也不容易。

而用 Emacs 编辑 Erlang 代码,编辑好了,存一下,C-c C-k,会先编译成 beam 文件,假如编译失败,那就停了。假如生成了 beam 文件,就会通过热更新替换进去,不需要重启 server 就立即生效了,且能替换进去的至少是编译通过了的。而造框架并不需要去搞什么监听文件改动的事,省去了很多麻烦。

所以,后面框架的设计,主要的一条就是在编译的时候,做更多检查,生成代码。而不是在运行期根据不同的配置有不同的行为。尽管做这些是为了开发的时候,热更新代码开发体验更好,同时因为代码都在编译期生成好了,运行期没有不必要的额外的开销。

关于 razor_url_dispatch

有些网站 Apache 或者 nginx 为了一些特殊的活动页,配置了超级多的正则表达式规则,因为他们用的语言本身不支持 pattern matching,所以用正则表达式规则在弥补这个缺陷。但是这不是没有代价的。

而在 razor_url_dispatch 里,你这么写直接会告诉你 rule conflict,不让你通过

-dispatch({page,"0", {endpoint, special_page}}).
-dispatch({page,"{id:integer}", {endpoint, page}}).

推荐的做法是像下面这样

-dispatch({page,"{id:integer}", {endpoint, page}}).

handle_request({'GET', page, #{id := 0}}) ->
  ok;
handle_request({'GET', page, #{id := ID}}) ->
  ok;

而在 Erlang 里,下面这样的代码同样会提示你 Warning: this clause cannot match because ...

handle_request({'GET', page, #{id := 0}}) ->
  ok;
handle_request({'GET', page, #{id := 0}}) ->
  ok;
handle_request({'GET', page, #{id := ID}}) ->
  ok;

因为更合理的划分,我们在编译的时候就可以得到更多 warning。同时也简化了正则表达式的实现,可以使用 tagged DFA 来一遍扫描。

失败人士的口号是,performance comes mostly from correctness。所以失败人士拒绝跑分。

关于 razor_db

为什么要支持子查询

子查询和 aggregation 一起的时候,需要正确判断到底是 where 还是 having。我们不能只看表达式里有没有出现 aggregation。举个例子

test(DB, subquery_having) ->
    razor_db:select(
      DB,
      [ Name
        || #{id := ID, name := Name} <- from(i1),
           group_by(Name),
           Count <- [count(ID)],
           exists(
             select(
               [ #{count => C}
                 || #{count := C} <- from(i2),
                    C == Count
               ]))
      ]);

生成的代码是这样的

test(DB, subquery_having) ->
    [Name
     || {Name}
            <- razor_db:query(DB,
                              "SELECT T1.name FROM i1 AS T1 GROUP BY "
                              "T1.name HAVING (EXISTS((SELECT T2.count "
                              "AS count FROM i2 AS T2 WHERE (T2.count "
                              "= count(T1.id)))))",
                              [])];

为什么一定要支持 Common Table Expression

因为根据 SQL 标准,Common Table Expression 必须 fencing

也就是我们把过滤条件写在最后的 SELECT 里,SQL 标准不允许 planner 把这个过滤条件挪到 Common Table Expression 里。所以必须把过滤条件写在 Common Table Expression 里。只要这个过滤条件里有参数,那么就意味着不可以手工写一段 SQL,拼在最前面了。

在 Erlang 中表示 HTML

<br />
{br, []}

<span><a href="index.html">Index</a></span>
{span, [], [{a, [{href, <<"index.html">>}], [<<"Index">>]}]}

更多 Live Coding?

要注意的是,最后的 razor_api_example,就已经是接近完整的代码了。

包括前面各种例子,都是用 pattern matching 来展示结果的,并没有太大必要增加 Live Coding。因为代码已经太多了。

共收到 9 条回复
jasl 将本帖设为了精华贴 09月19日 21:59
4楼 已删除

@bhuztez 自称自己是计算机界的 freeman 是哪位.

大部分函数式编程语言, 都很享受 C-c C-k大法好 的 开发体验. 😍 而且 Lisp 语言还很享受 C-x C-e大法好 的 开发 体验. 😜

sennmac 回复

不, 费曼是物理界的垠神

Erlang(/ˈɜːrlæŋ/)

luikore 回复

我在群里问了一下,才知道原来这个梗是给垠神的。。

最近在学 emacs, 但是遇到点小问题:在 emacs 内打开 erlang shell, 貌似不能用 tab 自动补全了,楼主怎么解决的

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