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

bhuztez · 发布于 2017年09月19日 · 最后由 sennmac 回复于 2017年09月20日 · 2145 次阅读
96
本帖已被设为精华帖!

代码以及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。因为代码已经太多了。

共收到 7 条回复
1107 jasl 将本帖设为了精华贴 09月19日 21:59
Fd2847

👍

4楼 已删除
11897

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

30806

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

2880
11897sennmac 回复

不, 费曼是物理界的垠神

3253

Erlang(/ˈɜːrlæŋ/)

11897
2880luikore 回复

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

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