Welcome to BlindingdarkSpace!

碰巧会写点程序的摇滚爱好者
施工中 ......


Erlang 学习笔记/1 简单尝试 gen_server

`Erlang` `gen_server ` --- ## 直接上代码 ``` erlang -module(study). -behaviour(gen_server). -export([init/1, handle_call/3, handle_cast/2, terminate/2]). -export([start_link/0]). -export([alloc/0,free/1]). -export([stop/0]). start_link() -> gen_server:start_link({local, my_study}, study, [], []). init(_Args) -> {ok, channels()}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% alloc() -> gen_server:call(my_study, alloc). handle_call(_Request, _From, State) -> io:format("取出之前的状态 ~w~n", [State]), {Ch, State2} = alloc(State), io:format("取出的数字 ~w~n", [Ch]), io:format("取出之后的状态 ~w~n", [State2]), {reply, Ch, State2}. free(Ch) -> gen_server:cast(my_study, {free, Ch}). stop() -> gen_server:cast(my_study, stop). handle_cast({free, Ch}, Chs) -> Chs2 = free(Ch, Chs), {noreply, Chs2}; handle_cast(stop, State) -> {stop, normal, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% terminate(normal, State) -> io:format("停止时的状态 ~w~n", [State]), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% channels() -> {_Allocated = [], _Free = lists:seq(1,100)}. % 初始状态 alloc({Allocated, [H|T] = _Free}) -> {H, {[H|Allocated], T}}. free(Ch, {Alloc, Free} = Channels) -> case lists:member(Ch, Alloc) of true -> {lists:delete(Ch, Alloc), [Ch|Free]}; false -> Channels end. ``` 前两句是声明模块名和引入 gen_server 行为 ``` erlang -module(study). -behaviour(gen_server). ``` 然后是类似要实现对应的协议?接口?的感觉 (Erlang 里不写 export 的方法都是私有方法。) ``` erlang -export([init/1, handle_call/3, handle_cast/2, terminate/2]) ``` 上面这几个是 gen_server 需要的几个函数。接下来就是实现这些函数了。 首先先过一下运行流程。 --- ## 基本运行流程 ### 第零步,编译 代码保存为 study.erl 注意文件名要和模块名一致。 然后 `erl` 进入控制台,cd 到源文件所在目录,执行 `c(study).` 对源文件进行编译。 ### 第一步,初始化 执行 `study:start_link().` 这个没什么说的,肯定会执行下面这段代码 ``` erlang start_link() -> gen_server:start_link({local, my_study}, study, [], []). ``` 再观察函数里面的情况,执行了 `gen_server:start_link/4` 是干什么的呢。 这个例子中,第一个参数是个元组,表示要在 local 本地注册一个名叫 my_study 的 server。 第二个参数就是模块名了。 第三个参数是要传给 `init` 函数的参数,所以这里可以推测出,执行了 `gen_server:start_link/4` 之后它就会去执行 `study:init/1`,也就是 ``` erlang init(_Args) -> {ok, channels()}. ``` 里面又调用了 `channels/0` 也就是 ``` erlang channels() -> {_Allocated = [], _Free = lists:seq(1,100)}. % 初始状态 ``` 到这里就停了。只声明了两个变量 `_Allocated` 和 `_Free`。 其实不然。 `init(_Args)` 如果返回了 `{ok, SomeState}`,那么 `SomeState` 这个变量就会被维护保存起来。(gen_server 的具体实现中,应该是在尾递归循环的参数中保存,专有名词叫 Continuation。) 如果不 ok,那初始化就会出错。 所以这里 `{_Allocated = [], _Free = lists:seq(1,100)}` 这个元组就被保存起来,之后怎么存取它我们往下看。 ### 第二步,执行 alloc erl 中输入 `study:alloc().` 毋庸置疑肯定执行下面这段代码 ``` erlang alloc() -> gen_server:call(my_study, alloc). ``` 所以 `gen_server:call(my_study, alloc).` 这句又是做什么的呢。其实就是调用注册名为 `my_study` 对应的 `alloc` 函数?不对,由于没有指定函数参数个数,Erlang 不可能知道去调哪个函数。 其实,这里,调用(回调?)的是注册名为 `my_study` 对应的 `handle_call/3`。 ``` erlang handle_call(_Request, _From, State) -> io:format("~w ~w~n", [_Request, _From]), io:format("取出之前的状态 ~w~n", [State]), {Ch, State2} = alloc(State), io:format("取出的数字 ~w~n", [Ch]), io:format("取出之后的状态 ~w~n", [State2]), {reply, Ch, State2}. ``` `handle_call/3` 第一个参数接收的就是 `gen_server:call(my_study, alloc).` 里第二个参数的值。也就是 `alloc` 这个原子。 第二个参数是调用方的信息,比如 `{<0.64.0>, #Ref<0.3946304990.3179544577.15636>}` 第三个参数,就是我们上面第一步中最后提到的那个值! 拿到这个值之后,我们就可以进行真正的操作了,也就是执行 `{Ch, State2} = alloc(State),` 先不看具体的执行逻辑,最后 `handle_call/3` 返回了 `{reply, Ch, State2}`,那这个是什么意思呢? 我的理解就是 reply 表示可以携带一个返回值出去,返回值内容就是元组的第二个(0 基的话就是第一个)元素的值,第三个就是要更新的『server 维护的那个 state 的新值』 所以最终,维护的内容就变成了 `State2`。 再回头看看我们的 `alloc/1` 和 `free/2` 都做了点啥。 ``` erlang alloc({Allocated, [H|T] = _Free}) -> {H, {[H|Allocated], T}}. free(Ch, {Alloc, Free} = Channels) -> case lists:member(Ch, Alloc) of true -> {lists:delete(Ch, Alloc), [Ch|Free]}; false -> Channels end. ``` 不难看出 `alloc/1` 大概就是从 1 到 100 的数字中取出一个数,注意这个 `_Free` 就是我们初始化的那个列表。 而 `free/2` 就是把取出的数再放回去。 所以总的来说这模拟了一个申请资源和释放资源的动作流程。 ### 第三步,执行 free 第二步的最后我们已经分析了 `free/2` 的代码,和 alloc 类似,当我们调用 `study:free(1).` 的时候首先会执行 ``` erlang free(Ch) -> gen_server:cast(my_study, {free, Ch}). % 注意这里是 gen_server:cast 不是 gen_server:call ``` 然后执行的是 `handle_cast/2` ``` erlang handle_cast({free, Ch}, Chs) -> Chs2 = free(Ch, Chs), {noreply, Chs2}; ``` 所以最终是调用了 `free/2`,并使用 `{noreply, Chs2}` 对 server 维护的状态进行更新。 noreply 和 reply 的区别就是 noreply 没有返回值了,最后一个元素依然是要更新的值。 所以通过调用 alloc 和 free 就可以进行申请和释放的动作了。 ### 第四步,stop 为了让这个 server 停下来,如果你把它加入了 `Supervisor` 中,那就由 `Supervisor` 来管理了。 如果是像本例中单独启动的情况,可以通过实现 `terminate/2` 来解决停止的问题。 执行 `study:stop().` 函数 ``` erlang stop() -> gen_server:cast(my_study, stop). ``` 分析过前几步的例子,这里就比较清晰了,它会触发 ``` erlang handle_cast(stop, State) -> {stop, normal, State}. ``` 注意到这里并没有显式的调用 `terminate/2`,是由 gen_server 负责调用,做最后的处理工作,处理完毕就会退出这个进程了。 再次通过 init 启动后,之前维护的值就自然也跟着不见了。反之如果你不终止就开启一个同名的服务,那肯定是会报错的。 ## 结语 以上是黑盒分析的结果,其实实现一个简化版的 `gen_server` 只需几行代码。参见「坚强哥」的博文[理解Erlang/OTP gen_server](http://www.cnblogs.com/me-sa/archive/2011/12/20/erlang0023.html) 拆开来看能更深的理解背后的原理。 `gen_server` 还有许多功能,比如热更新,与 `Supervisor` 配合使用等。下回慢慢分析。 --- ### 参考链接 [理解Erlang/OTP gen server ](http://www.cnblogs.com/me-sa/archive/2011/12/20/erlang0023.html) [OTP Design Principles User's Guide Chapters 2 gen server Behaviour](http://erlang.org/doc/design_principles/gen_server_concepts.html) [[Erlang 学习笔记]erlang behaviour小结之gen_server](http://blog.csdn.net/lqg1122/article/details/7484413)

无题

--- 今天在学习的时候又看到了这句话 > 庄子曰:「吾生也有涯,而知也无涯。以有涯随无涯,殆已;已而为知者,殆而已矣。」 > > --- 《庄子·养生主》 什么意思呢? 生命短暂,而知识是却是无穷无尽的。企图以有限的生命去追寻无尽的知识,那还不把你丫累死。明知道这个道理却还要继续,那可是真的要死了。 按他庄老人家的说法,难道我们就不该继续钻研知识了么? 知乎用户 @江山依旧 指出了这样的答案 > 为学日益,为道日损,损之又损,以至于无为,无为而无不为。取天下常以无事,及其有事,不足以取天下。 > > --- 《道德经》 这又是什么意思呢? 在你刚开始仅仅想要学知识的时候,你获得的知识日趋增加,但是当你想要探寻“道”是个什么,你就必须慢慢忘记你曾经学过的东西,最后什么都忘记了,也就什么都知道了。 后半句讲治国,让天下顺服基本什么也不用做,如果你整天找事儿,那不足以取天下。 这里“道”可以理解为真理。什么意思呢,知识的表象浩如烟海,但是一切的背后都有着同一种最基本的思想,只有看穿表象,才能真正触及到平实的本质。 后半句,怎么理解呢?我们就操一点中南海的心。来看孟老夫子说的 > 孟子曰:「以力假仁者霸,霸必有大国。以德行仁者王,王不待大:汤以七十里,文王以百里。以力服人者,非心服也,力不赡也。以德服人者,中心悦而诚服也,如七十子之服孔子也,诗云:『自西自东,自南自北,无思不服。』此之谓也。」 > > --- 《孟子·公孙丑》 这句比较长,意思是 以力量假借仁义那叫称霸,想称霸那你得是大国。靠品德仁义,那才叫称王,称王不一定是大国。商汤称王的时候只有方圆七十里地,周文王也只有方圆一百里。以手腕强迫别人服你,那不是真服你,只是打不过你罢了。如果你以德服人,那从头到脚,心悦诚服,就像孔子的七十多个弟子服孔子那样,诗经说「东南西北,没有一个不服的。」差不多就是这样了。(这个诗经说的是文王) 大国现在看来不仅指地方大,应该是国力强盛。孟子只说仁只是不一定是大国,也没说仁就成不了大国。服你只是打不过你,有一天打得过呢。。。 所以仁义大国,就是你既打不过,又不得不服。 这事儿不能多想,我还是先追求长征第一步,好好学习吧。 --- ### 参考链接 [https://www.zhihu.com/question/49553312](https://www.zhihu.com/question/49553312) [https://www.zhihu.com/question/21752167](https://www.zhihu.com/question/21752167) [https://www.cnease.cn/read-htm-tid-11663.html](https://www.cnease.cn/read-htm-tid-11663.html)

在 Elixir/Ecto 中使用 PostgreSQL JSON 数据类型

`Elixir` `PostgreSQL` `index` `JSON` `索引` `Ecto` --- PostgreSQL 提供了丰富的数据类型。在 9.3 版本之后,PostgreSQL 内置了 JSON 数据类型以及一系列配套的用以操作 JSON 的内置函数。对于一些灵活的数据,使用 JSON 格式来存储就再好不过了,而且还可以对其添加索引,所以在性能上也有一定程度上的保证。 ### 在 Elixir/Ecto 中使用 JSON 格式 Ecto 可以直接将 Elixir 中的 map 格式转为 JSON 进行存储,所以使用起来非常的“原生”。 首先建立 Model schema ```elixir schema "users" do field :name, :string field :content, :map # 指定 content 为 map 格式 timestamps() end ``` 然后建立 Migration change ```elixir def change do create table(:users) do add :name, :string add :content, :json # 指定 content 为 json 格式 timestamps() end end ``` 这样就可以进行存储了 ```elixir %BlindingdarkSpace.User{ name: "blindingdark", content: %{ real_name: "zhua_kuang", phone_number: 123_1234_5678, home_page: "http://blindingdark.space", github: "https://github.com/BlindingDark" } } |> BlindingdarkSpace.User.changeset(%{}) |> BlindingdarkSpace.Repo.insert ``` 当然直接查询出来也是 map 格式,非常自然。 如果你想查 select map 中的某一项也是可以的! ```elixir query = Ecto.Query.from user in BlindingdarkSpace.User, where: fragment("?->>'real_name' = ?", user.content, "zhua_kuang") ``` 双箭头 `->>` 为 PostgreSQL 提供的取 JSON 值的语法。(对应的还有用于取多级 JSON 的 `->`,以及取数组的 `#>`) 根据 PostgreSQL 的说法,双箭头 `->>` 查出来的都是 TEXT 类型。也就是说即使你存的时候是 INT 类型,双箭头取出来却是 TEXT。 比如 phone_number 这个字段,如果我们这样写 where 子句是报错的,会提示 INT 和 TEXT 不匹配。 ```elixir where: fragment("?->>'phone_number' = ?", user.content, 123_1234_5678) ``` 怎么办?需要使用双冒号 `::` 进行类型转换。 ```elixir where: fragment("(?->>'phone_number')::INT = ?", user.content, 123_1234_5678) ``` 这里的 `(?->>'phone_number')::INT` 就可以把 `?->>'phone_number'` 也就是把 `user.content.phone_number` 的值转为 INT。 或者将第二个参数的 INT 转为 TEXT ```elixir where: fragment("?->>'phone_number' = ?::TEXT", user.content, 123_1234_5678) ``` 这里省略了小括号,也就是把紧挨着的那个值进行转换。把 INT 转 TEXT 更灵活一点,这样查询的时候 TEXT 和 INT 都能进行正常的查询(TEXT 转 TEXT 还是 TEXT)。也就是说下面这样也是对的。 ```elixir where: fragment("?->>'phone_number' = ?::TEXT", user.content, "12312345678") ``` ### 建立 JSON 索引 为了提升查询性能,我们可以在 JSON 列上建立索引。 增加新的 Migration change,创建索引。 ```elixir create index(:users, ["(content->>'phone_number')"], unique: true) ``` 这样就在 `content.phone_number` 上建立了一个带有唯一性约束的索引。 ### 结语 目前 Ecto 好像还没有直接操作 JSON 格式的方法,只能通过 `fragment` 来直接写 SQL,我相信以后会有封装过后更直接的操作方式。 --- #### 参考链接 [Gabriel Jaldon - Using JSON Type in Ecto](http://gabrieljaldon.com/articles/using-json-type-in-ecto.html) [Dave Clark - What can you do with PostgreSQL and JSON?](http://clarkdave.net/2013/06/what-can-you-do-with-postgresql-and-json/) [Stack Overflow - How to create index on json field in Postgres 9.3](https://stackoverflow.com/questions/17807030/how-to-create-index-on-json-field-in-postgres-9-3)

浅析关于 JS 作用域的几个高频知识点

`闭包` `词法作用域` `变量提升` --- ## 变量提升 #### 什么是变量提升 顾名思义,变量提升指的是,在声明变量的时候,变量的声明位置会被提升至当前作用域最前面。 看这个例子 ```javascript var foo = "before"; function bar() { if (!foo) { var foo = "after"; } console.log(foo); } bar(); ``` 由于变量提升,这里输出的值为 `"after"`。 也就是说,上面的代码其实等价于下面的代码 ```javascript var foo = "before"; function bar() { var foo; // foo == undefined if (!foo) { // 判断成立 foo = "after"; } console.log(foo); } bar(); ``` 变量提升会提到哪里呢?在 js 里,只有函数级(function level)的作用域,所以上面代码中的 foo 被提到了函数的顶部。 如果上面没有函数,则作为全局变量。 #### 为什么有变量提升 这是一个历史问题,Javascript 语言设计者为了实现相互递归定义,参考了《SICP》4.1.6 节中给出的解决方式,也就是变量提升。 Brendan Eich 的原话为 > Function declaration hoisting is for mutual recursion & generally to avoid painful bottom-up ML-like order 那什么是相互递归定义呢?看下面这部分代码。 ```javascript function is_even(n) { if (n == 0) { return true; } else { return is_odd(n - 1); } } is_even(2); // true function is_odd(n) { if (n == 0) { return false; } else { return is_even(n - 1); } } ``` `is_odd` 和 `is_even` 函数互相调用对方,按理说 `is_even` 定义的时候 `is_odd` 还没有定义,应该会报错,但由于有变量提升,所以就可以正常执行了。 --- ## 词法作用域和闭包 先来看这样一段代码 ```javascript var a = "before"; function foo(){ console.log(a); } function bar(fun){ var a = "after"; fun(); } bar(foo); ``` 输出结果为 "before"。 为什么不是根据“就近原则”选择变量 a 呢? 这是由于 JS 采用的是词法作用域 (lexical scoping),又叫静态作用域 (static scoping)。 也就是说,变量的绑定在声明的时候就已经确定,而不是在执行的时候再根据上下文就近绑定。 上面的代码中,`foo` 在声明的时候就已经把 `a` 绑定为 `"before"` 了。 这种特性也被称为闭包。或者说,**闭包是实现词法作用域的一种方式**。 其实有些古老的语言的确采用动态作用域 (dynamic scoping) (所谓动态作用域就是在执行的时候才确立变量绑定),比如 shell 脚本,emacslisp 等。 所以如果 JS 采用动态作用域,那么上面的代码将会输出 "after"。 --- ## let 和 var 上面我们提到,JS 只有函数级 (function level) 的作用域,这会导致什么问题呢?看下面这段代码。 ```javascript <ul id="list"> </ul> <script> var list = document.getElementById("list"); for (var i = 1; i <= 5; i++) { var item = document.createElement("li"); item.appendChild(document.createTextNode("Item " + i)); item.onclick = function(ev) { alert("Item " + i + " is clicked."); }; list.appendChild(item); } </script> ``` ([上述代码的 JSFiddle 在线测试地址](https://jsfiddle.net/jiacai2050/w6agke9d/)) 你会发现无论你点击哪个 Item,都只会显示 6。 其是这是由于 `i` 是在 `for` 循环中定义的,而不是在函数中定义的,所以它是全局变量,循环完毕之后只有一个 `i` ,其实此时所有的函数中的 `i` 都绑定到了那一个 `i` 上。 上述代码也可以简化为 ```javascript var array = new Array(); for (var i = 1; i <= 5; i++) { array[i] = function(){ return i; } } array.forEach(function(fun){ console.log(fun()); }) ``` 为了解决这个问题,ES6 中引入了 `let` 关键字,它有块级(block level)作用域的性质。所谓块级作用域,也就是该变量的有效作用域以花括号 `{}` 作为边界。所以就不会提前到全局变量中了,而是以 `for` 循环的花括号作为边界了。 把上面的 `var` 定义改为 `let` 或者用一个函数包裹起来,那么点击各个 Item 就会出现不同的数字了。 --- ## 参考链接 [编程语言中的变量作用域与闭包](http://liujiacai.net/blog/2016/05/28/scope-closure/)

遗书

`认真的` `以备不时之需` --- > 葬礼曲目 [À Tout Le Monde - Megadeth](http://music.163.com/#/song?id=21162906) [Stairway To Heaven - Led Zeppelin](http://music.163.com/#/song?id=29719536) 我也不知道要走什么法律手续,还要麻烦在世的人,抱歉了。 器官什么的能研究的研究,能捐献的捐献。 不要任何仪式。不要举行葬礼。不要墓地。不要宴请。只需要随便找个地儿播放上面的歌,就是我的葬礼了。 没啥遗憾,挺开心的。 就是有点孤独。 2017/7/16 23:00 ---

联系我

嗜好

  • Functional Programming
  • Music
  • Game