-
Notifications
You must be signed in to change notification settings - Fork 0
/
data.json
1 lines (1 loc) · 540 KB
/
data.json
1
[{"title":"HTML5 Canvas的命中区域检测以及如何侦听Canvas形状上的Click事件","created":"2020/12/09","link":"2020/12/09/hit-region-detection-for-html5-canvas-and-how-to-listen-to-click-events-on-canvas-shapes","description":"HTML5 Canvas的命中区域检测以及如何侦听Canvas形状上的Click事件","content":"<h1>HTML5 Canvas的命中区域检测以及如何侦听Canvas形状上的Click事件</h1>\n<p>您需要一个简单的 onClick 画布形状吗? 但是 canvas 没有此类监听器的 API。 您只能在整个画布上监听事件,而不能在部分画布上监听事件。 我将描述两种主要方法来解决此问题。</p>\n<p>让我们从简单的 html5 canvas 图形开始。假设我们要在页面上绘制几个圆圈。</p>\n<pre><code class=\"language-js\">const canvas = document.getElementById('canvas');\nconst ctx = canvas.getContext('2d');\n\n// create circles to draw\nconst circles = [\n {\n x: 40,\n y: 40,\n radius: 10,\n color: 'rgb(255,0,0)'\n },\n {\n x: 70,\n y: 70,\n radius: 10,\n color: 'rgb(0,255,0)'\n }\n];\n\n// draw circles\ncircles.forEach(circle => {\n ctx.beginPath();\n ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);\n ctx.fillStyle = circle.color;\n ctx.fill();\n});\n</code></pre>\n<p>查看 demo http://codepen.io/lavrton/pen/QdePBY</p>\n<p>现在,我们可以简单地聆听整个画布上的点击:</p>\n<pre><code class=\"language-js\">canvas.addEventListener('click', () => {\n console.log('canvas click');\n});\n</code></pre>\n<p>但是如果我们想监听一个圆圈上的点击。这该怎么做?如何检测到我们点击了一个圆圈?</p>\n<h2>方法#1 - 利用数学的力量</h2>\n<p>有了有关圆的坐标和大小的信息后,我们可以简单地使用数学方法通过简单的计算来检测圆的点击。我们需要做的就是从click事件中获取鼠标位置,并检查所有圆的交点:</p>\n<pre><code class=\"language-js\">function isIntersect(point, circle) {\n return Math.sqrt((point.x-circle.x) ** 2 + (point.y - circle.y) ** 2) < circle.radius;\n}\n\ncanvas.addEventListener('click', (e) => {\n const pos = {\n x: e.clientX,\n y: e.clientY\n };\n circles.forEach(circle => {\n if (isIntersect(mousePoint, circle)) {\n alert('click on circle: ' + circle.id);\n }\n });\n});\n</code></pre>\n<p>这种方法非常普遍,并在许多项目中广泛使用。您可以轻松找到更复杂形状的数学函数,例如矩形,椭圆形,多边形等。</p>\n<p>这种方法非常好,如果您没有大量的形状,则可以超快。</p>\n<p>但是很难将这种方法用于非常复杂的形状。例如,您正在使用具有二次曲线的线。</p>\n<h2>方法#2 - 模拟点击区域</h2>\n<p>命中区域的想法很简单-我们只需要将像素放在点击区域下方,然后找到具有相同颜色的形状即可:</p>\n<pre><code class=\"language-js\">function hasSameColor(color, circle) {\n return circle.color === color;\n}\n\ncanvas.addEventListener('click', (e) => {\n const mousePos = {\n x: e.clientX - canvas.offsetLeft,\n y: e.clientY - canvas.offsetTop\n };\n // get pixel under cursor\n const pixel = ctx.getImageData(mousePos.x, mousePos.y, 1, 1).data;\n\n // create rgb color for that pixel\n const color = `rgb(${pixel[0]},${pixel[1]},${pixel[2]})`;\n\n // find a circle with the same colour\n circles.forEach(circle => {\n if (hasSameColor(color, circle)) {\n alert('click on circle: ' + circle.id);\n }\n });\n });\n</code></pre>\n<p>但是正是这种方法行不通,因为它可能具有相同颜色的形状,对吗?为了避免这种冲突,我们应该创建一个特殊的“命中图”画布。它将具有几乎相同的形状,但是每个形状将具有独特的颜色。因此,我们需要为每个圆生成随机颜色:</p>\n<pre><code class=\"language-js\">// colorsHash for saving references of all created circles\nconst colorsHash = {};\n\nfunction getRandomColor() {\nconst r = Math.round(Math.random() * 255);\nconst g = Math.round(Math.random() * 255);\nconst b = Math.round(Math.random() * 255);\nreturn `rgb(${r},${g},${b})`;\n}\n\n\n\nconst circles = [{\n id: '1', x: 40, y: 40, radius: 10, color: 'rgb(255,0,0)'\n}, {\n id: '2', x: 100, y: 70, radius: 10, color: 'rgb(0,255,0)'\n}];\n\n// generate unique colors\ncircles.forEach(circle => {\n // repeat until we find trully unique colour\n while(true) {\n const colorKey = getRandomColor();\n // if colours is unique\n if (!colorsHash[colorKey]) {\n // set color for hit canvas\n circle.colorKey = colorKey;\n // save reference \n colorsHash[colorKey] = circle;\n return;\n }\n }\n});\n</code></pre>\n<p>之后,我们需要绘制每个形状两次。首先在可见的画布上,然后在“命中”画布上。</p>\n<pre><code class=\"language-js\">circles.forEach(circle => {\n // draw on "scene" canvas first\n ctx.beginPath();\n ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);\n ctx.fillStyle = circle.color;\n ctx.fill();\n \n // then draw on offscren "hit" canvas\n hitCtx.beginPath();\n hitCtx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI, false);\n hitCtx.fillStyle = circle.colorKey;\n hitCtx.fill();\n});\n</code></pre>\n<p>现在,当您在画布上单击时,您需要的是在命中的画布上获取一个像素并找到具有相同颜色的形状。而且此操作非常快速,您无需遍历所有形状。另外,形状的复杂程度也无关紧要。绘制任何您想要的颜色,并对每个形状使用不同的颜色。</p>\n<p>查看完整 demo :http://codepen.io/lavrton/pen/OWKYMr</p>\n<h2>哪种方法更好?</h2>\n<p>这取决于。第二种“命中”方法的主要瓶颈是必须绘制两次形状。因此性能可能会下降两次!但是在热门画布上绘画可能会更简单。您可以在那里跳过阴影和笔触,可以简化某些形状,例如,仅用矩形替换文本。但是在完成绘制之后,这种方法可以超快。因为获取像素并访问存储的形状的散列是非常快的操作。</p>\n<h2>它们可以一起使用吗?</h2>\n<p>当然。一些画布库使用这种混合方法。</p>\n<p>它以这种方式工作:</p>\n<p>对于每种形状,您必须计算简化的边界矩形(x,y,宽度,高度)。然后,您可以使用第一种“数学”方法来过滤与鼠标位置和边界矩形相交的形状。之后,您可以使用第二种方法绘制命中并测试相交,以获得更准确的结果。</p>\n<h2>为什么不只在这种情况下使用SVG?</h2>\n<p>因为有时候画布可以更高性能,并且更适合您的高级任务。同样,这取决于任务。所以canvas vs SVG不在本文的讨论范围之内。如果您想使用画布并进行命中检测,则必须使用某些工具,对吗?</p>\n<h2>那其他事件呢?像 mousemove,mouseenter 等?</h2>\n<p>您只需要在描述的方法中添加一些额外的代码即可。一旦您可以100%检测到鼠标下方的形状,就可以模拟所有其他事件。</p>\n<h2>有什么好的即用型解决方案吗?</h2>\n<p>当然。只需尝试使用 Google “ html5 canvas框架” 即可。但是我个人的建议是 http://konvajs.github.io/。对了,我是该库的维护者。Konva 仅使用第二种方法,它支持我们通常对DOM元素具有的所有鼠标和触摸事件(甚至更多,例如拖放)</p>\n<p>翻译自: https://lavrton.com/hit-region-detection-for-html5-canvas-and-how-to-listen-to-click-events-on-canvas-shapes-815034d7e9f8/</p>\n","author":"lanqy"},{"title":"git 常用命令","created":"2019/12/27","link":"2019/12/27/git-common-commands","description":"git 常用命令","content":"<h1>git 常用命令</h1>\n<h2>解决冲突:</h2>\n<pre><code class=\"language-text\">git stash\ngit pull origin develop\ngit stash pop\n</code></pre>\n<h2>切换分支:</h2>\n<pre><code class=\"language-text\">// 切换到 develop 分支\ngit checkout -b develop \n</code></pre>\n<h2>删除分支</h2>\n<pre><code class=\"language-text\">git branch -d feature_x\n</code></pre>\n<h2>合并分支到你当前的分支</h2>\n<pre><code class=\"language-text\">git merge <branch>\n</code></pre>\n<h2>查看两个分支的差异</h2>\n<pre><code class=\"language-text\">git diff <source_branch> <target_branch>\n</code></pre>\n<h2>提交流程:</h2>\n<pre><code class=\"language-text\">git add *\n\ngit commit -m "提交备注"\n\ngit push origin develop\n</code></pre>\n<h2>Git回滚代码到某个commit</h2>\n<pre><code class=\"language-text\">$ git reset --hard HEAD^ // 回退到上个版本\n$ git reset --hard HEAD~3 // 回退到前3次提交之前,以此类推,回退到n次提交之前\n$ git reset --hard commit_id // 退到/进到 指定commit的sha码\n</code></pre>\n<h2>删除某个标签</h2>\n<pre><code class=\"language-text\">git push origin :refs/tags/v2.7.72 // 删除标签 v2.7.72 \n</code></pre>\n","author":"lanqy"},{"title":"ES6:var,let和const - 函数作用域和块范围之间的争斗","created":"2018/11/21","link":"2018/11/21/es6-var-let-and-const-the-battle-between-function-scope-and-block-scope","description":"ES6:var,let和const - 函数作用域和块范围之间的争斗TypeScript","content":"<h1>ES6:var,let和const - 函数作用域和块范围之间的争斗</h1>\n<p>译自:http://www.deadcoderising.com/2017-04-11-es6-var-let-and-const-the-battle-between-function-scope-and-block-scope/</p>\n<p>在 ES6 之前的时代,只有一种方法可以在 JavaScript 中声明变量 - 即使用 var。</p>\n<p>var 总是有这种特殊的误解光环 - 这可能是因为用 var 声明的变量的行为与大多数其他编程语言的区别。话虽如此,整个事情有一个非常自然的解释 - 作用域。</p>\n<p>问题是,var 是函数作用域。这种类型的作用域与更常用的块范围略有不同。</p>\n<p>让我们来看看这意味着什么。</p>\n<h2>var - 函数作用域</h2>\n<p>如前所述,使用 var 声明的变量将是函数作用域,这意味着它将存在于其声明的函数作用域内。</p>\n<pre><code class=\"language-javascript\">function myFunc() {\n var name = 'Luke'\n console.log(name); // 'Luke'\n}\n\nmyFunc();\n\nconsole.log(name); // name is not defined\n</code></pre>\n<p>如您所见,函数内部使用 var 声明的变量无法从函数外部访问。</p>\n<p>话虽如此,其他类型的块 - 如 if 语句,循环等 - 将不被视为作用域。</p>\n<pre><code class=\"language-javascript\">if(true) {\n var name = 'Luke'\n}\n\nconsole.log(name); // 'Luke'\n</code></pre>\n<p>使用 var,变量名在其声明的 if 语句之外可用。这是因为它们属于同一作用域。</p>\n<p>然而,随着 ES6 的引入,引入了两种声明变量的新方法。</p>\n<h2>let 和 const - 块作用域的介绍</h2>\n<p>在ES6中,let 和 const 被引入作为声明变量的替代方法 - 两者都块作用域。</p>\n<p>如果你习惯了除 JavaScript 之外的任何其他语言,这可能会更好地与你产生共鸣。</p>\n<p>在块作用域内,任何块都是范围。这将提供更一致的行为。</p>\n<p>这意味着函数仍然是有效作用域,就像使用 var 一样。</p>\n<pre><code class=\"language-javascript\">function myFunc() {\n let name = 'Luke'\n console.log(name); // 'Luke'\n}\n\nmyFunc();\nconsole.log(name); // name is not defined\n</code></pre>\n<p>但在这种情况下,其他类型的块也可以作为作用域 - 如 if 语句。</p>\n<pre><code class=\"language-javascript\">if(true) {\n let name = 'Luke'\n}\n\nconsole.log(name); // name is not defined\n</code></pre>\n<h2>当函数作用域变得混乱时</h2>\n<p>现在我们知道了功能作用域和块范围之间的区别 - 让我们看看为什么这很快就会让人感到困惑。</p>\n<p>在作用域内部具有与外部作用域中的变量同名的局部变量是完全正确的。</p>\n<pre><code class=\"language-javascript\">var name = 'Luke';\n\nconst func = () => {\n var name = 'Phil';\n console.log(name); // 'Phil'\n}\n\nfunc();\n\nconsole.log(name); // 'Luke'\n</code></pre>\n<p>正如预期的那样,即使在 func (包含一个同样命名的局部变量)执行之后,外部作用域中的 name 仍然保持初始声明值“Luke”。</p>\n<p>然而问题是,由于函数作用域只包含函数而不是其他类型的块,我们会得到与其他块完全不同的行为。</p>\n<pre><code class=\"language-javascript\">var name = 'Luke';\n\nif(true) {\n var name = 'Phil';\n console.log(name); // ‘'Phil'\n}\n\nconsole.log(name); // 'Phil'\n</code></pre>\n<p>在这种情况下,'Phil' 将在两个地方打印。这是因为两个变量都在相同的作用域内,导致 'Phil' 覆盖第一个变量声明。</p>\n<p>可以想象,随着复杂性的增加,这很快就会变成一个真正的头痛。</p>\n<h2>与块作用域保持一致</h2>\n<p>如果我们看看 let - 这是块作用域 - 这将对所有块保持一致。</p>\n<pre><code class=\"language-javascript\">let name = 'Luke';\n\nconst func = () => {\n let name = 'Phil'\n console.log(name); // 'Phil'\n}\n\nfunc();\n\nconsole.log(name); // 'Luke'\n</code></pre>\n<pre><code class=\"language-javascript\">let name = 'Luke';\n\nif (true) {\n let name = 'Phil';\n console.log(name); // 'Phil'\n}\n\nconsole.log(name); // 'Luke'\n</code></pre>\n<h2>循环怎么样?</h2>\n<p>让我们看看另一个例子来真正理解不同的行为。</p>\n<p>假设我们想要创建一个将惰性函数推送到数组的循环。这些函数中的每一个都将打印当前索引。</p>\n<p>让我们首先看看如果我们使用 var 会发生什么。</p>\n<pre><code class=\"language-javascript\">var printsToBeExecuted = [];\n\nfor (var i = 0; i < 3; i++) {\n printsToBeExecuted.push(() => console.log(i));\n}\n\nprintsToBeExecuted.forEach(f => f());\n// Output: 3, 3, 3\n</code></pre>\n<p>再说一次,如果你习惯于块作用域,那会觉得有点奇怪。你会期望 0, 1, 2 对吗?</p>\n<p>解释只是在使用 var 时循环不是作用域。因此,不是为每个增量创建局部变量 i ,而是最终为所有函数打印变量的最终值。</p>\n<pre><code class=\"language-javascript\">var printsToBeExecuted = [];\n\nfor (var i = 0; i < 3; i++) {\n printsToBeExecuted.push(\n ((ii) => () => console.log(ii))()\n )\n}\n\nprintsToBeExecuted.forEach(f => f());\n\n// Output: 0, 1, 2\n</code></pre>\n<p>太棒了,我们得到了我们预期的输出,但它有点冗长吧?</p>\n<p>如果我们现在看一下使用块作用域的解决方案来获取迭代变量,我们将获得第一个示例的简单性以及预期的结果。</p>\n<pre><code class=\"language-javascript\">var printsToBeExecuted = [];\n\nfor (let i = 0; i < 3; i++) {\n printsToBeExecuted.push(() => consolt.log(i));\n}\n\nprintsTBeExecuted.forEach(f => f());\n// Output: 0, 1, 2\n</code></pre>\n","author":"lanqy"},{"title":"OCaml 符号","created":"2018/10/23","link":"2018/10/23/OCaml-symbols","description":"OCaml 符号","content":"<h1>OCaml 符号</h1>\n<h2>1、 (* ... *) ,(** ... **)</h2>\n<p>标识注释,(* ... *) 一般用于代码注释,可嵌套。 (** ... **) 一般用于文档注释,例如:</p>\n<pre><code class=\"language-ocaml\"> (** 这里是注释. **)\nval touch : (string * int) list -> string -> (string * int) list\n</code></pre>\n<h2>2、 [ ... ]</h2>\n<p>生成一个列表(类型)。 使用 ; 分隔符列表元素,例如:</p>\n<pre><code class=\"language-ocaml\"># let words = ["foo"; "bar"; "baz"];;\nval words : string list = ["foo"; "bar"; "baz"]\n</code></pre>\n<h2>3、 ,</h2>\n<p>生成一个元组。但是,元组的类型是通过 * 号来分隔每个元素的,例如:</p>\n<pre><code class=\"language-ocaml\">(* 类型定义 *)\ntype name = string * string\n\n(* 元组生成 *)\nlet johndoe = ("John", "Doe")\n\n(* 模式匹配 *)\nmatch s with\n| (first, last) -> Printf.printf "my name is %s %s" first last\n</code></pre>\n<h2>4、 ;</h2>\n<p>这是一个句子休息。尽管 OCaml 的大部分语法都不需要这个分隔符,但它对短代码的函数并没有多大用处。正如上面已经提到的那样,这个符号也被用来分隔列表(下面)和记录元素</p>\n<h2>5、 &&</h2>\n<p>这是一个逻辑与(AND)运算符。我想你可以想象。还有,这是用于另一个目的的语法。</p>\n<h2>6、 ||</h2>\n<p>它是一个逻辑或(OR)运算符。or 也包含在保留字中,但现在已被弃用。</p>\n<h2>7、 ::</h2>\n<p>合成列表的操作符, x :: xs 在 xs 列表的开头添加一个 x 元素,例如:</p>\n<pre><code class=\"language-ocaml\"># let a = [2;3;4;5];;\nval a : int list = [2; 3; 4; 5]\n# let b = 1 :: a;;\nval b : int list = [1; 2; 3; 4; 5]\n\n# let a = 1 :: 2 :: 3 :: [];;\nval a : int list = [1; 2; 3]\n</code></pre>\n<h2>8、 '</h2>\n<p>单引号可以用于标识符。 x' 你也可以说变量名 f' 和函数名都可以。</p>\n<p>但是, 你不能以 'a 的形式定义变量。这被称为类型变量,它用于类型符号。</p>\n<h2>9、 |</h2>\n<p>用于编写由变体类型和模式匹配分隔的多个模式。例如:</p>\n<pre><code class=\"language-ocaml\">(* 变体类型的定义 *) \ntype foobar = \n Foo (* 可能是 Foo *) \n | Bar \n | Baz \n(* 模式匹配 *) \nmatch v with \n| Foo -> ... \n| Bar -> ... \n| Baz -> ...\n</code></pre>\n<h2>10、 -></h2>\n<p>它用于模式匹配守卫或类型的符号。例如: String.get 获取字符串中特定位置的字符的函数类型是 string -> int -> char</p>\n<pre><code class=\"language-ocaml\">(* 模式匹配 *) \nmatch v with \n| Foo -> ... \n| Bar -> ... \n| Baz -> ...\n</code></pre>\n<h2>11、 ()</h2>\n<p>括号内没有任何内容。这是一个 unit ,它没有任何称为类型的东西......它在其他语言中是无效的。</p>\n<p>() 如果你发现一个指定参数的函数,它是一个不需要任何参数的函数。函数不能在没有参数的情况下调用,因此可以通过指定来调用类型以()开头的 unit - > 函数。例如:</p>\n<pre><code class=\"language-ocaml\"># Sys.getcwd () ;; (* 获取当前目录 *) \n-: string = "/Volumes/Users/szktty"\n</code></pre>\n<p>入口点是 let () = ... 也是这个符号,这是模式匹配,但不是一个参数。</p>\n<h2>12、 ^</h2>\n<p>连接字符串的运算符,这个操作符会生成一个新的字符串。例如:</p>\n<pre><code class=\"language-ocaml\"># let name = "hello " ^ "lanqy";;\nval name : string = "hello lanqy"\n</code></pre>\n<h2>13、 +. -. *. /.</h2>\n<p>浮点数的加减乘除。例如:</p>\n<pre><code class=\"language-ocaml\">(* 相加 *)\n# 1.2 +. 1.3;;\n- : float = 2.5\n(* 相减 *)\n# 1.3 -. 1.2;;\n- : float = 0.10000000000000009\n(* 相乘 *)\n# 3.0 *. 2.0;;\n- : float = 6.\n(* 相除 *)\n# 6.0 /. 2.0;;\n- : float = 3.\n</code></pre>\n<h2>14、 _</h2>\n<p>这是一个可以用于标识符的符号。在模式匹配中,它被称为通配符,但与其他变量相比,它实际上并没有特殊的功能。 _ 即 v 它匹配任何值:</p>\n<pre><code class=\"language-ocaml\">match exp with \n| 0 -> ... \n| v -> ... (* 匹配 0 以外的值 *)\n</code></pre>\n<p>但是,如果将此符号添加到变量名称(或函数名称)的开头,则该变量将绕过警告。通常情况下,如果您根本没有使用任何已定义的变量,编译器将被警告为未使用的变量,但该检查不会完成,不要写 let _ = 。</p>\n<pre><code class=\"language-ocaml\">match exp with \n| 0 -> 0 \n| v -> 1 (* w 被警告为未使用的变量,因为 v 未使用 *) \n\nmatch exp with \n| 0 -> 0 \n| _ -> 1 (* 我不会收到警告 *)\n</code></pre>\n<p>另外,这不是一个单独的语言规范,也不是强制性的,但作为命名惯例_it有时附加到标识符的末尾(特别是记录字段名称),我们经常看到类似 end_ , to_ 这样的命名方式,原因很简单,因为有同名的end, to 保留字,与其绞尽脑汁想一个命名,不如直接在单词后面加 _ ?</p>\n<pre><code class=\"language-ocaml\">type location = {\n start : int;\n end_ : int;\n}\n</code></pre>\n<h2>15、 [| ... |]</h2>\n<p>用于生成数组的语法(类型), [...] 与这个区别是完美的,例如:</p>\n<pre><code class=\"language-ocaml\"># [|1; 2; 3|];;\n- : int array = [|1; 2; 3|]\n</code></pre>\n<h2>16、 := <-</h2>\n<p>:= 引用(指针)(赋值(当解引用不提供左值)), <- 变量赋值或声明(赋值), 例如:</p>\n<pre><code class=\"language-ocaml\"># let y = ref None;;\nval y : '_a option ref = {contents = None}\n# y;;\n- : '_a option ref = {contents = None}\n# y := Some 3;;\n- : unit = ()\n# y;;\n- : int option ref = {contents = Some 3}\n# y := None;;\n# y;;\n- : int option ref = {contents = None}\nlet s = "hello world";;\nlet s' = s;;\ns.[0] <- 'x';;\ns';;\n- : string = "xello world"\n(* 数组 *)\nlet x = [| 2; 8; 3 |];;\n\n(* 修改数组元素 *)\nx.(1) <- 9;; (* 设置索引为 1 的元素为 9 *)\nx;;\n- : int array = [|2; 9; 3|]\n</code></pre>\n<h2>17、 = <> == !=</h2>\n<p>= 相等, <> 不相等( 深层 ), == 相等, != 不相等 ( 浅层次 ),OCaml中有两个相等运算符 = 和 == ,相应的不等式运算符 <> 和 !=。= 和 <> 检查结构相等,而 == 和 != 检查物理相等,例如:</p>\n<pre><code class=\"language-ocaml\"># "a" == "a";;\n- : bool = false\n# "a" = "a";;\n- : bool = true\n# "a" != "a";;\n- : bool = true\n# "a" <> "a";;\n- : bool = false\n</code></pre>\n<h2>18、 ~</h2>\n<p>标签参数中使用的符号,例如:</p>\n<pre><code class=\"language-ocaml\"># let f ~x ~y = x - y;;\nval f : x:int -> y:int -> int = <fun>\n\n# let x = 3 and y = 2 in f ~x ~y;;\n- : int = 1\n\n# let f ~x:x1 ~y:y1 = x1 - y1;;\nval f : x:int -> y:int -> int = <fun>\n\n# f ~x:3 ~y:2;;\n- : int = 1\n</code></pre>\n<h2>19、 ?</h2>\n<p>可选参数 = 用于标记参数的符号,可以省略。例如:</p>\n<pre><code class=\"language-ocaml\"># let foo ?(z = 0) x y = x + y > z;;\nval foo : ?z:int -> int -> int -> bool = <fun>\n# foo 3 3 ~z: 2;;\n- : bool = true\n# foo 3 3 ~z: 10;;\n- : bool = false\n# foo 2 1;;\n- : bool = true\n</code></pre>\n<p>(* 代码来自 https://stackoverflow.com/questions/23703470/ocaml-optional-argument *)</p>\n<h2>20、 `</h2>\n<p>表示多态变体的符号。还有其他多态变体符号 [> 和 [< 。</p>\n<p>可以结合 http://blog.klipse.tech/reason/2018/03/12/blog-reason-types.html 这篇文章来理解 [> 和 [< 。</p>\n<pre><code class=\"language-ocaml\"># type card = [ `Jorker | `Num of int ];; (* 多态变体类型 *) \ntype card = [ `Jorker | `Num of int ] \n\n# type in_data = [ `Str of string | `Num of int ] ;; (* 多态变体类型 *) \ntype in_data = [ `Num of int | `Str of string ] \n\n# let get_number= function (* 接收多态变体参数 *) \n `Num i -> i\n | _ -> failwith " not a number " ;; \nval get_number: [> ` Num of 'a ] -> ' a = <fun > \n\n# get_number ( `Num 3 ) ;; (* 应用多态变体类型 *) \n-: int = 3\n</code></pre>\n<p>(* 代码来自 http://osiire.hatenablog.com/entries/2009/05/10 *)</p>\n<h2>21、 @@ 和 |></h2>\n<p>OCaml 4.01 新添加的两个内置运算符: @@ 和 |> ,它们非常简单,你可以在像这样的旧版本中自己定义它们:</p>\n<pre><code class=\"language-ocaml\">let (@@) fn x = fn x\nlet (|>) x fn = fn x\n(* 以下这两个是一样的 *)\nprint @@ "Hello";;\nprint "Hello";;\n</code></pre>\n<p>例如,这两行是等价的(我们加载一个文件,将其解析为XML,将生成的文档解析为选择文档,然后执行选择)</p>\n<pre><code class=\"language-ocaml\">execute (parse_selections (parse_xml (load_file path)))\n\nexecute @@ parse_selections @@ parse_xml @@ load_file path\n</code></pre>\n<p>(* 代码来自 http://roscidus.com/blog/blog/2013/10/13/ocaml-tips/ *)</p>\n<p>这样做的好处是,当你阅读一个 ( ,你必须沿着括号中的其余行扫描来找到匹配的。当你看到 @@ 时,你知道表达式的其余部分是前一个单一的参数功能。</p>\n<p>管道运算符 |> 是相似的,但函数和参数是相反的。以下两行是相同的:</p>\n<pre><code class=\"language-ocaml\">execute @@ parse_selections @@ parse_xml @@ load_file path\n\nload_file path |> parse_xml |> parse_selections |> execute\n</code></pre>\n<p>(* 代码来自 http://roscidus.com/blog/blog/2013/10/13/ocaml-tips/ *)</p>\n<h2>22、 ;;</h2>\n<p>顶层环境的结束符。例如:</p>\n<pre><code class=\"language-ocaml\">let a = [2;3;4;5];;\nval a : int list = [2; 3; 4; 5]\n</code></pre>\n<h2>23、 @</h2>\n<p>列表的连接符。例如:</p>\n<pre><code class=\"language-ocaml\">let a = [2;3] @ [4;5;6];;\nval a : int list = [2; 3; 4; 5; 6]\n</code></pre>\n<p>参考资料:</p>\n<ul>\n<li>https://qiita.com/szktty/items/05cb2b754c88fbacc274</li>\n<li>http://roscidus.com/blog/blog/2013/10/13/ocaml-tips/</li>\n<li>https://stackoverflow.com/questions/23703470/ocaml-optional-argument</li>\n<li>http://rigaux.org/language-study/syntax-across-languages-per-language/OCaml.html</li>\n</ul>\n","author":"lanqy"},{"title":"JavaScript 的工作原理:引擎,运行时和调用堆栈的概述","created":"2018/10/22","link":"2018/10/22/how-does-javascript-actually-work","description":"JavaScript 的工作原理:引擎,运行时和调用堆栈的概述","content":"<h1>JavaScript 的工作原理:引擎,运行时和调用堆栈的概述</h1>\n<p>随着 JavaScript 变得越来越流行,团队正在利用其在堆栈中的多个级别的支持 - 前端,后端,混合应用程序,嵌入式设备等等。</p>\n<p>这篇文章旨在成为系列中的第一篇,旨在深入挖掘 JavaScript 及其实际工作方式:我们认为通过了解 JavaScript 的构建块以及它们如何一起发挥,您将能够编写更好的代码和应用。我们还将分享我们在构建 <a href=\"https://www.sessionstack.com/?utm_source=medium&utm_medium=source&utm_content=javascript-series-post1-intro\">SessionStack</a> 时使用的一些经验法则,这是一个轻量级JavaScript应用程序,必须具有强大且高性能才能保持竞争力。</p>\n<p>如 <a href=\"https://githut.info/\">GitHut</a> 统计数据所示,JavaScript 在 GitHub 中的 Active Repositories和 Total Pushes 方面处于领先地位。它也不会落后于其他类别</p>\n<p><img src=\"/images/1_Zf4reZZJ9DCKsXf5CSXghg.png\" alt=\"console\" />\n(Check out up-to-date GitHub language stats).</p>\n<p>如果项目越来越依赖于JavaScript,这意味着开发人员必须利用语言和生态系统提供的所有内容,对内部进行更深入和更深入的了解,以便构建出色的软件。</p>\n<p>事实证明,有很多开发人员每天都在使用JavaScript,但却不了解幕后发生的事情。</p>\n<h2>概貌</h2>\n<p>几乎每个人都已经听说过 V8 引擎作为一个概念,大多数人都知道 JavaScript 是单线程的,或者它使用的是回调队列。</p>\n<p>在这篇文章中,我们将详细介绍所有这些概念,并解释 JavaScript 实际运行的方式。通过了解这些详细信息,您将能够编写更好的,非阻塞的应用程序,这些应用程序正确地利用了所提供的 API。</p>\n<p>如果您对 JavaScript 比较陌生,那么这篇博文将帮助您理解为什么 JavaScript 与其他语言相比如此“奇怪”。</p>\n<p>如果您是一位经验丰富的 JavaScript 开发人员,希望它会为您提供一些关于您每天使用的 JavaScript 运行时实际工作方式的新见解。</p>\n<h2>JavaScript 引擎</h2>\n<p>JavaScript 引擎的一个流行示例是 Google 的 V8 引擎。例如,V8 引擎用于 Chrome 和 Node.js。这是一个非常简化的视图:</p>\n<p><img src=\"/images/1_OnH_DlbNAPvB9KLxUCyMsA.png\" alt=\"\" /></p>\n<p>引擎包含两个主要组件:</p>\n<ul>\n<li>内存堆 - 这是内存分配发生的地方</li>\n<li>调用堆栈 - 这是您的代码执行时堆栈帧的位置</li>\n</ul>\n<h2>运行时</h2>\n<p>浏览器中有几乎所有 JavaScript 开发人员都使用过的 API(例如“setTimeout”)。但是,引擎不提供这些 API。</p>\n<p>那么,他们来自哪里?</p>\n<p>事实证明,现实有点复杂。</p>\n<p>所以,我们有引擎,但实际上还有很多。我们有一些叫做 Web API 的东西,它们是由浏览器提供的,比如 DOM,AJAX,setTimeout 等等。</p>\n<p>然后,我们有如此受欢迎的事件循环和回调队列。</p>\n<h2>调用堆栈</h2>\n<p>JavaScript 是一种单线程编程语言,这意味着它只有一个调用堆栈。因此,它可以一次做一件事。</p>\n<p>调用栈是一种数据结构,它基本上记录了程序中的位置。如果我们进入函数,我们将它放在堆栈的顶部。如果我们从函数返回,我们会弹出堆栈的顶部。这就是所有堆栈都可以做到的。</p>\n<p>我们来看一个例子吧。看一下下面的代码:</p>\n<pre><code class=\"language-javascript\">function multiply(x, y) {\n return x * y\n}\n\nfunction printSquare(x) {\n var s = multipy(x, x);\n console.log(s);\n}\n\nprintSquare(5);\n</code></pre>\n<p>当引擎开始执行此代码时,调用堆栈将为空。之后,步骤如下:</p>\n<p><img src=\"/images/1_Yp1KOt_UJ47HChmS9y7KXw.png\" alt=\"\" /></p>\n<p>调用堆栈中的每个条目称为堆栈帧。</p>\n<p>这正是抛出异常时堆栈跟踪的构造方式 - 它基本上是异常发生时调用堆栈的状态。看一下下面的代码:</p>\n<pre><code class=\"language-javascript\">function foo() {\n throw new Error('SessionStact will help you resolve crashes :)')\n}\n\nfunction bar() {\n foo();\n}\n\nfunction start() {\n bar();\n}\n\nstart();\n</code></pre>\n<p>如果在 Chrome 中执行此操作(假设此代码位于名为 foo.js 的文件中),则将生成以下堆栈跟踪:</p>\n<p><img src=\"/images/1_T-W_ihvl-9rG4dn18kP3Qw.png\" alt=\"\" /></p>\n<p>“吹掉堆栈” - 当达到最大调用堆栈大小时会发生这种情况。这很容易发生,特别是如果你使用递归而不是非常广泛地测试你的代码。看看这个示例代码:</p>\n<pre><code class=\"language-javascript\">function foo() {\n foo();\n}\n\nfoo();\n</code></pre>\n<p>当引擎开始执行此代码时,它首先调用函数“foo”。但是,此函数是递归的,并且在没有任何终止条件的情况下开始调用自身。因此,在执行的每个步骤中,相同的函数一次又一次地添加到调用堆栈中。它看起来像这样:</p>\n<p><img src=\"/images/1_AycFMDy9tlDmNoc5LXd9-g.png\" alt=\"\" /></p>\n<p>但是,在某些时候,调用堆栈中的函数调用数量超过了调用堆栈的实际大小,并且浏览器决定通过抛出错误来执行操作,该错误看起来像这样:</p>\n<p><img src=\"/images/1_e0nEd59RPKz9coyY8FX-uw.png\" alt=\"\" /></p>\n<p>在单个线程上运行代码非常简单,因为您不必处理多线程环境中出现的复杂场景 - 例如,死锁。</p>\n<p>但是在单个线程上运行也是非常有限的。由于JavaScript只有一个调用堆栈 (Call Stack),当事情变慢时会发生什么?</p>\n<h2>并发和事件循环</h2>\n<p>如果在调用堆栈中有函数调用需要花费大量时间才能处理,会发生什么?例如,假设您想在浏览器中使用 JavaScript 进行一些复杂的图像转换。</p>\n<p>你可能会问,为什么这甚至是一个问题?问题是,虽然调用堆栈有函数要执行,但浏览器实际上不能做任何其他事情——它被阻塞了。这意味着浏览器不能呈现,不能运行任何其他代码,它只是卡住了。如果你想在应用中使用流畅的 UI,这就会产生问题。</p>\n<p>这不是唯一的问题。一旦您的浏览器开始在调用堆栈中处理如此多的任务,它可能会在相当长的时间内停止响应。大多数浏览器通过引发错误来采取行动,询问您是否要终止网页。</p>\n<p><img src=\"/images/1_WlMXK3rs_scqKTRV41au7g.jpeg\" alt=\"\" /></p>\n<p>现在,那不是最好的用户体验,是吗?</p>\n<p>那么,如何在不阻止 UI 并使浏览器无响应的情况下执行繁重的代码呢?好吧,解决方案是异步回调。</p>\n<p>这将在“如何实际运行JavaScript”教程的第2部分中进行更详细的解释:“<a href=\"https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e\">在V8引擎内部+有关如何编写优化代码的5个技巧</a>”。与此同时,如果您在JavaScript应用程序中难以复制和理解问题,请查看 <a href=\"https://www.sessionstack.com/?utm_source=medium&utm_medium=blog&utm_content=Post-1-overview-outro\">SessionStack</a> 。SessionStack 记录 Web 应用程序中的所有内容:所有 DOM 更改,用户交互,JavaScript 异常,堆栈跟踪,网络请求失败和调试消息。</p>\n<p>使用 SessionStack,您可以将网络应用中的问题作为视频重播,并查看用户发生的所有事情。</p>\n<p>有一个免费的计划,不需要信用卡。现在就<a href=\"https://www.sessionstack.com/?utm_source=medium&utm_medium=blog&utm_content=Post-1-overview-getStarted\">开始</a>。</p>\n<p><img src=\"/images/1_kEQmoMuNBDfZKNSBh0tvRA.png\" alt=\"\" /></p>\n","author":"lanqy"},{"title":"理解 JavaScript 调用堆栈","created":"2018/10/22","link":"2018/10/22/understanding-the-javascript-call-stack","description":"理解 JavaScript 调用堆栈","content":"<h1>理解 JavaScript 调用堆栈</h1>\n<p>译自:https://medium.freecodecamp.org/understanding-the-javascript-call-stack-861e41ae61d4</p>\n<p>JavaScript 引擎(在浏览器等托管环境中找到)是一个包含堆和单个调用堆栈的单线程解释器。浏览器提供 DOM,AJAX 和 Timers 等 Web API。</p>\n<p>本文旨在解释调用堆栈是什么以及为什么需要它。对调用堆栈的理解将清楚地说明“函数层次结构和执行顺序”在 JavaScript 引擎中的工作原理。</p>\n<p>调用堆栈主要用于函数调用(调用)。由于调用堆栈是单个的,所以函数执行一次一个地完成,从上到下。这意味着调用堆栈是同步的。</p>\n<p>对调用堆栈的理解对异步编程至关重要(我们将在后面的文章中介绍)。</p>\n<p>在异步 JavaScript 中,我们有一个回调函数,一个事件循环和一个任务队列。在通过事件循环将回调函数推送到堆栈之后,调用堆栈在执行期间对回调函数起作用。</p>\n<p>在最基本的层面上,调用堆栈是一种数据结构,它使用后进先出(LIFO)原则来临时存储和管理函数调用(调用)。</p>\n<p>让我们分解我们的定义:</p>\n<p>LIFO:当我们说调用堆栈由 Last In,First Out 的数据结构原理操作时,这意味着当函数返回时,最后一个被推入堆栈的函数是第一个被弹出的函数。</p>\n<p>让我们看一下代码示例,通过向控制台打印堆栈跟踪错误来演示 LIFO。</p>\n<pre><code class=\"language-javascript\">function firstFunction() {\n throw new Error('Stack Trace Error');\n}\n\nfunction secondFunction(){\n firstFunction();\n}\n\nfunction thirdFunction() {\n secondFunction();\n}\n\nthirdFunction();\n</code></pre>\n<p>代码运行时,我们收到错误。打印堆栈显示函数如何堆叠在彼此之上。看一下图表。</p>\n<p><img src=\"/images/1_LIuELJ2RTtwWExRWGdu_Hw.png\" alt=\"console\" /></p>\n<p>你会注意到函数作为一个堆栈的排列开始于 <code>firstFunction()</code>(这是进入堆栈的最后一个函数,并弹出以抛出错误),然后是<code>secondFunction()</code> ,然后是 <code>thirdFunction()</code>(这是执行代码时第一个被推入堆栈的函数)。</p>\n<p>暂时存储:当调用(called)函数时,函数,其参数和变量被推入调用堆栈以形成堆栈帧。此堆栈帧是堆栈中的内存位置。当函数从堆栈中弹出时,内存被清除。\n<img src=\"/images/1_PPkrowy4n_Pyehb_NdhLrg.png\" alt=\"stack\" /></p>\n<p>管理函数调用(called):调用堆栈维护每个堆栈帧的位置记录。它知道要执行的下一个函数(并将在执行后将其删除)。这就是使 JavaScript 中的代码执行同步的原因。</p>\n<p>想象一下你自己站在一个杂货店的现金点排队。只有在你面前的人被照顾后才能照顾你。这是同步的。</p>\n<p>这就是我们所说的“管理函数调用”。</p>\n<h2>调用堆栈如何处理函数调用?</h2>\n<p>我们将通过查看调用另一个函数的函数的示例代码来回答这个问题。这是示例代码:</p>\n<pre><code class=\"language-javascript\">function firstFunction(){\n console.log("Hello from firstFunction");\n}\nfunction secondFunction(){\n firstFunction();\n console.log("The end from secondFunction");\n}\nsecondFunction();\n</code></pre>\n<p><img src=\"/images/1_9iSkoJoXM0Ok8iQ5mOHl5Q.png\" alt=\"console output\" /></p>\n<p>这是代码运行时发生的情况:</p>\n<p>1.当执行 secondFunction() 时,会创建一个空堆栈帧。它是该程序的主要(匿名)入口点。\n2. secondFunction() 然后调用 firstFunction(),将其推入堆栈。\n3. firstFunction() 返回并将“Hello from firstFunction”打印到控制台。\n4. firstFunction() 从堆栈中弹出。\n5. 执行顺序然后移动到 secondFunction()。\n6. secondFunction() 返回并打印“The end from secondFunction”到控制台。\n7. 从堆栈中弹出 secondFunction(),清除内存。</p>\n<h2>是什么导致堆栈溢出?</h2>\n<p>当存在递归函数(一个自己调用的函数)而没有退出点时,会发生堆栈溢出。浏览器(托管环境)具有最大堆栈调用,它可以在抛出堆栈错误之前容纳。</p>\n<p>这是一个例子:</p>\n<pre><code class=\"language-javascript\">function callMyself(){\n callMyself();\n}\ncallMyself();\n</code></pre>\n<p>callMyself() 将一直运行,直到浏览器抛出“Maximum call size exceeded”。那是堆栈溢出。</p>\n<p><img src=\"/images/1_JFRlgLp2uvbdVrh7WdmMrQ.png\" alt=\"Maximum call stack error\" /></p>\n<h2>综上所述</h2>\n<p>调用堆栈的关键点是:</p>\n<ul>\n<li>它是单线程的。这意味着它一次只能做一件事。</li>\n<li>代码执行是同步的。</li>\n<li>函数调用会创建占用临时内存的堆栈帧。</li>\n<li>它作为LIFO - Last In,First Out 数据结构。</li>\n</ul>\n<p>我们使用了调用堆栈文章为我们将在异步 JavaScript 上看到的系列奠定了基础(我们将在另一篇文章中看到)。</p>\n<p>所有代码示例都可以在此 <a href=\"https://github.com/charlesfreeborn/JS-CallStack-CodeSamples/blob/master/codesamples.md\">github仓库</a> 中找到。</p>\n","author":"lanqy"},{"title":"OCaml 操作符备忘单","created":"2018/09/17","link":"2018/09/17/ocaml-operator-cheatsheet","description":"OCaml 操作符备忘单","content":"<h1>OCaml 操作符备忘单</h1>\n<p>译自:https://www.brendanlong.com/ocaml-operator-cheatsheet.html</p>\n<p>学习 OCaml 最困难的部分之一就是弄清楚中缀运算符的作用,因为它们只是一串符号而你无法通过谷歌搜索找到它们。这是我试图制作一个备忘单,无论何时你想知道一系列随机符号是什么意思。在此页面上搜索应该找到有关任何常见 OCaml 运算符的基本信息。请注意,某些库定义了自己的运算符,例如 Jane Street 的<a href=\"https://ocaml.janestreet.com/ocaml-core/latest/doc/core/Core/Command/Spec/index.html#val-(++)\">Command.Spec</a> 如何定义 ++,+> 和 +<。</p>\n<h2>关于中缀函数的一般信息</h2>\n<p>在 OCaml 中,如果函数名称以以下字符之一开头,则函数为中缀:</p>\n<pre><code class=\"language-ocaml\">= @ ^ | & + - * / $ %\n</code></pre>\n<p>其次是零个或多个这些字符:</p>\n<pre><code class=\"language-ocaml\">! $ % & * + - . / : ? @ ^ | ~\n</code></pre>\n<p>定义中缀函数时,需要在 “name” 周围加上 ()。 例如,在 <a href=\"https://github.com/diml/utop\">utop</a> 中:</p>\n<pre><code class=\"language-ocaml\"># let (=<>@^|&~+-*/$%!?:.) a b = a + b ;;\nval ( =<>@^|&~+-*/$%!?:. ) : int -> int -> int = <fun>\n\n# 1 =<>@^|&~+-*/$%!?:. 2;;\n- : int = 3\n</code></pre>\n<p>此外,您可以通过再次将函数名称包装在括号中来查看 utop 中的中缀运算符的类型:</p>\n<pre><code class=\"language-ocaml\"># (=<>@^|&~+-*/$%!?:.);;\n\nval ( =<>@^|&~+-*/$%!?:. ) : int -> int -> int = <fun>\n</code></pre>\n<p>这里的<a href=\"https://caml.inria.fr/pub/docs/manual-caml-light/node4.9.html\">官方文档在这里</a>,虽然这个<a href=\"https://haifengl.wordpress.com/2014/07/02/ocaml-functions/\">博客</a>有一个更容易理解的解释。</p>\n<h2>内置中缀运算符</h2>\n<p>内置运算符在 <a href=\"https://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html\">Pervasives</a> 中定义:</p>\n<p>请参阅文档,了解涉及多种类型(=,<>,<,>等)的函数所涉及的魔力。</p>\n<p>操作符 | 描述\n------------ | -------------\n=\t| 结构相等 <a href=\"https://stackoverflow.com/a/13596236/212555\">1</a>\n<>\t| 结果不等 <a href=\"https://stackoverflow.com/a/13596236/212555\">1</a>\n<\t| 小于</p>\n<blockquote>\n<p>| 大于\n<=\t| 小于等于\n=\t| 大于等于\n==\t| 物理相等(同一对象)<a href=\"https://stackoverflow.com/a/13596236/212555\">1</a>\n!=\t| 物理不相等(不是同一个对象)<a href=\"https://stackoverflow.com/a/13596236/212555\">1</a>\n&&\t| 布尔和\n&\t| (已弃用) 布尔和\n||\t| 布尔或\n|\t| (已弃用)布尔或\n|>\t| 反函数应用(x |> f 与 f x 相同)。也称管道运算符\n@@\t| 功能应用(f @@ x与 f x 相同)\n~-\t| 整数否定(与一元相同 - )\n~+\t| 被描述为 “一元加法” 但似乎没有做任何事情。</p>\n</blockquote>\n<ul>\n<li>| 整数加法</li>\n</ul>\n<ul>\n<li>| 整数减法</li>\n</ul>\n<ul>\n<li>| 整数乘法\n/\t| 整数除法\n~-.\t| 浮动否定(与一元相同 -.)\n~+.\t| 被描述为 “一元加法” 但似乎没有做任何事情。\n+.\t| 浮点数加法\n-.\t| 浮点数减法\n*.\t| 浮点数乘法\n/.\t| 浮点数除法\n**\t| 浮点数幂\n^\t| 字符串连接\n@\t| 列表连接\n!\t| 获取 ref 的值\n:=\t| 设置(修改) ref 的值\n^^\t| 格式化字符串连接</li>\n</ul>\n<h2>Jane Street</h2>\n<h3>Numbers</h3>\n<p>Jane Street 通常在模块中定义有意义的算术运算符,因此您可以执行以下操作:</p>\n<pre><code class=\"language-ocaml\">Bigint.(of_int 1 + of_int 3 / of_int 5)\n</code></pre>\n<p>此接口的文档位于 <a href=\"https://ocaml.janestreet.com/ocaml-core/latest/doc/base/Base/Int_intf/module-type-S_common/index.html\">Int_intf.S_common</a> 下,尽管它们中的大多数也是针对浮点数定义的。</p>\n<p>操作符 | 描述\n------------ | -------------</p>\n<ul>\n<li>| 特定模块的添加(即 float.(+) 是浮动添加)</li>\n</ul>\n<ul>\n<li>| 特定模块的减法</li>\n</ul>\n<ul>\n<li>| 特定模块的乘法\n/\t| 特定模块的除法\n//\t| 整数除法返回浮点数\n%\t| 中缀 <a href=\"https://en.wikipedia.org/wiki/Modulo_operation\">mod</a> (结果总是整数)\n/%\t| 中缀 <a href=\"https://en.wikipedia.org/wiki/Modulo_operation\">mod</a> (如果输入为负,则结果为负)</li>\n</ul>\n<h3>Monads</h3>\n<p>Jane Street 的库( Core,Async,Base 等)在 <a href=\"https://ocaml.janestreet.com/ocaml-core/latest/doc/base/Base/List/Monad_infix/index.html\">Monad_infix 模块</a>下一致地定义了中缀运算符。</p>\n<p>操作符 | 描述\n------------ | -------------</p>\n<blockquote>\n<blockquote>\n<p>=\t| 中缀版本的 <a href=\"https://en.wikipedia.org/wiki/Monad_(functional_programming)#Overview\">bind</a>。打开 Async 将此设置为 <a href=\"https://ocaml.janestreet.com/ocaml-core/latest/doc/async_kernel/Async_kernel/Deferred/index.html#val-bind\">Deferred.bind</a>\n|\t| 中缀版本的 map. 打开 Async 将此设置为 <a href=\"https://ocaml.janestreet.com/ocaml-core/latest/doc/async_kernel/Async_kernel/Deferred/index.html#val-map\">Deferred.map</a>\n=? |\t与 Or_error 混合的 bind。打开 Async 将此设置为 <a href=\"https://ocaml.janestreet.com/ocaml-core/latest/doc/async_kernel/Async_kernel__/Deferred_or_error/index.html#val-bind\">Deferred.Or_error.bind</a>\n>>|? |\t与 Or_error 混合的 map。打开异步将此设置为 <a href=\"https://ocaml.janestreet.com/ocaml-core/latest/doc/async_kernel/Async_kernel__/Deferred_or_error/index.html#val-map\">Deferred.Or_error.map</a></p>\n</blockquote>\n</blockquote>\n<p>假设您熟悉 monad,可以记录 map 和 bind ,如果您需要更多信息,可能会发现此 <a href=\"https://stackoverflow.com/questions/29851449/what-is-the-use-of-monads-in-ocaml/29852213#29852213\">StackOverflow 答案</a>很有用。</p>\n<blockquote>\n<blockquote>\n<p>= 和 >>| 最常出现在 Async 中,但它们也可以与 Option,List,Result 等一起使用。</p>\n</blockquote>\n</blockquote>\n<h3>Lwt</h3>\n<p>请参阅 <a href=\"http://ocsigen.org/lwt/3.1.0/api/Lwt\">Lwt文档</a>。</p>\n<p>操作符 | 描述\n------------ | -------------</p>\n<blockquote>\n<blockquote>\n<p>=\t| 中缀版本的 <a href=\"\">bind</a>\n=<<\t| 与反转的参数 bind\n>|=\t| 中缀版 map。与 Jane Street 代码中的 >>| 相同\n=|<\t| 与反转的参数 map</p>\n</blockquote>\n</blockquote>\n<p>Lwt 没有 Async 的>>=? 或 >>|? 因为 Lwt.t 可以包含错误而没有单独的 Or_error 模块。</p>\n<p>如果您需要有关 map 和 bind 的信息,请参阅上面的 Jane Street Monad 部分。</p>\n","author":"lanqy"},{"title":"OCaml -ppx 语言扩展教程","created":"2018/09/17","link":"2018/09/17/ppx-tutorial","description":"OCaml -ppx 语言扩展教程","content":"<h1>OCaml -ppx 语言扩展教程</h1>\n<p>译自:https://victor.darvariu.me/jekyll/update/2018/06/19/ppx-tutorial.html</p>\n<blockquote>\n<p>简要介绍 OCaml 中的 ppx 扩展机制,并附有示例和进一步的指示 - 我希望在准备硕士论文时我知道的事情。</p>\n</blockquote>\n<p>你如何扩展编程语言?老式的方法是编写自己的预处理器,它接受用扩展语法编写的程序,并将它们转换为普通的编程语言(这是早期 c++ 编译器的工作原理)。稍微不那么苛刻的选择是依赖编程语言生态系统提供的工具;OCaml 是为数不多的提供开箱即用功能的d语言之一。在这篇文章中,我尝试从程序员的角度补充现有的集体智慧,了解 OCaml ppx 语言扩展机制的工作原理。</p>\n<h2>ppx 基础知识</h2>\n<p>OCaml 语法支持 4.02 版本中的扩展节点,如 <a href=\"https://caml.inria.fr/pub/docs/manual-ocaml/extn.html#sec261\">OCaml 手册</a>中所述。扩展节点允许以任意复杂的方式扩展语言 - 它们可以表示表达式,类型表达式,模式等。让我们看一个扩展节点的一个愚蠢的例子:一个将 “+ 1” 项附加到任何代数表达式的节点。它可能看起来如下:</p>\n<pre><code class=\"language-ocaml\">[%addone 1 + 2]\n</code></pre>\n<p>扩展节点由两部分组成:属性 id(上面的 addone )和有效负载(表达式 1 + 2)。属性 id 标识它所代表的扩展节点的类型,以便由适当的重写器处理,而有效负载是需要根据语言扩展的逻辑重写的表达式的主体。在我们的例子中,在作者展开之后,上面的术语应该被阅读</p>\n<pre><code class=\"language-ocaml\">(1 + 2) + 1\n</code></pre>\n<p>扩展节点是语法树中的通用占位符,类型检查器拒绝它,并打算由 ppx 重写器扩展它。ppx rewriter 是一种二进制文件,它接收解析器生成的 AST,执行一些转换,并输出经过修改的 AST。</p>\n<h2>OCaml AST</h2>\n<p>那么这个 AST 是什么样的?通过解析生成的 AST 数据类型是 compiler-libs 包的一部分。您可以在 <a href=\"https://caml.inria.fr/pub/docs/manual-ocaml/libref/Parsetree.html\">Parsetree</a> 和 <a href=\"https://caml.inria.fr/pub/docs/manual-ocaml/libref/Asttypes.html\">Asttypes</a> 模块中找到类型的定义。要检查解析器在特定表达式上生成的 AST,可以通过运行以下命令来使用 <a href=\"https://github.com/ocaml-ppx/ppx_tools\">ppx_tools</a> 包中的 dumpast 工具:</p>\n<pre><code class=\"language-text\">ocamlfind ppx_tools/dumpast -e "[%addone 1 + 2]"\n</code></pre>\n<p>它生成了下面的语法树片段,其中使用了 OCaml 版本4.05中的 Parsetree/Asttypes 模块中的数据类型。我们可以直观地看到,解析树包含一个扩展节点,带有 addone 属性 id 和一个包含表达式的有效负载。这个表达式是对两个子表达式的加法函数的应用,这两个子表达式仅仅是常量。尝试按原样编译这个小程序将导致由于扩展节点未解释而引起错误——这是 ppx 重写程序的工作。</p>\n<pre><code class=\"language-ocaml\">{pexp_desc =\n Pexp_extension\n ({txt = "addone"},\n PStr\n [{pstr_desc =\n Pstr_eval\n ({pexp_desc =\n Pexp_apply ({pexp_desc = Pexp_ident {txt = Lident "+"}},\n [(Nolabel,\n {pexp_desc = Pexp_constant (Pconst_integer ("1", None))});\n (Nolabel,\n {pexp_desc = Pexp_constant (Pconst_integer ("2", None))})])},\n ...)}])}\n</code></pre>\n<h2>AST Helpers</h2>\n<p>ppx 重写器需要与 AST 片段(例如上面的片段)进行模式匹配并执行转换。这些转换还需要生成有效的 OCaml AST 片段。手工构建这些非常麻烦。为此,<a href=\"https://caml.inria.fr/pub/docs/manual-ocaml/libref/Ast_helper.html\">Ast_helper 模块</a>提供了用于构造片段的辅助函数。为了构造表达式1 + 2,我们可以使用 Exp.apply 和 Exp.constant 助手 (helpers),如下所示:</p>\n<pre><code class=\"language-ocaml\">Exp.apply (Exp.ident {txt = Lident "+"; loc=(!default_loc)}) \n [(Nolabel, Exp.constant (Pconst_integer ("1", None)));\n (Nolabel, Exp.constant (Pconst_integer ("2", None)))];\n</code></pre>\n<h2>AST Mapper</h2>\n<p>我们任务的 ppx 重写器将使用 <a href=\"https://caml.inria.fr/pub/docs/manual-ocaml/libref/Ast_mapper.html\">Ast_mapper API</a>,它提供编译器和 ppx 重写器之间的标准接口。它还提供了一个默认的映射器,它只不过是一个深度的身份映射器 - 因此我们只能修改我们感兴趣的语法树部分。使用必要的管道,我们的 addone 重写器将如下所示:</p>\n<pre><code class=\"language-ocaml\">open Ast_mapper\nopen Ast_helper\nopen Asttypes\nopen Parsetree\nopen Longident\n\nlet expr_mapper mapper expr = \n begin match expr with\n | { pexp_desc =\n Pexp_extension ({ txt = "addone"; loc }, pstr)} ->\n begin match pstr with\n | PStr [{ pstr_desc =\n Pstr_eval (expression, _)}] -> \n Exp.apply (Exp.ident {txt = Lident "+"; loc=(!default_loc)})\n [(Nolabel, expression);\n (Nolabel, Exp.constant (Pconst_integer ("1", None)))]\n | _ -> raise (Location.Error (Location.error ~loc "Syntax error")) \n end\n (* Delegate to the default mapper. *)\n | x -> default_mapper.expr mapper x;\n end\n\nlet addone_mapper argv =\n { \n default_mapper with\n expr = expr_mapper;\n }\n \nlet () = register "addone" addone_mapper\n</code></pre>\n<p>让我们详细研究这个片段。在第 23 行,我们定义了自定义映射器,它使用我们自己的 expr_mapper 替换默认映射器中的 expr 字段。这意味着我们自己的 expr_mapper 只处理表达式;模式和其他 AST 类型将保持不变。第 7 行的 expr_mapper 的定义将表达式与具有标识符 addone 的扩展节点匹配,其他标识符不应由此映射器处理。然后我们对第 13 行的表达式进行模式匹配,并使用 AST 助手添加另一个函数应用程序 - + 应用于原始表达式和常量1。</p>\n<p>为了构建重写器,我们可以使用标准的 ocamlbuild 工具,指定对 compiler-libs 的依赖,其中必要的模块位于:</p>\n<pre><code class=\"language-text\">ocamlbuild -package compiler-libs.common addone_ppx.native\n</code></pre>\n<p>要检查重写器是否符合我们的要求,我们可以使用ppx_tools包中的重写器工具。假设 [%addone 1 + 2] OCaml 代码在文件 addone.ml 中:</p>\n<pre><code class=\"language-text\">ocamlfind ppx_tools/rewriter ./addone_ppx.native addone.ml\n</code></pre>\n<p>输出 (1 + 2) + 1,这正是我们想要的。该工具还允许将重写的源代码输出到文件而不是stdout。</p>\n<h2>递归呢?</h2>\n<p>上面给出的重写器不会递归,因此我们不能有嵌套的 addone 节点,例如 [%addone 1 + [%addone 2]]。支持递归是任何有意义的语言添加的关键要求,正是使扩展功能强大的原因。以下将让我们首先在AST的外部节点上应用+1加法;注意第16行的映射器的递归调用。然后重写[%addone 1 + [%addone 2]]表达式将给出(1 +(2 + 1))+ 1。</p>\n<pre><code class=\"language-ocaml\">open Ast_mapper\nopen Ast_helper\nopen Asttypes\nopen Parsetree\nopen Longident\n\nlet rec expr_mapper mapper expr = \n begin match expr with\n | { pexp_desc =\n Pexp_extension ({ txt = "addone"; loc }, pstr)} ->\n begin match pstr with\n | PStr [{ pstr_desc =\n Pstr_eval (expression, _)}] -> \n Exp.apply (Exp.ident {txt = Lident "+"; loc=(!default_loc)})\n [(Nolabel, (expr_mapper mapper expression));\n (Nolabel, Exp.constant (Pconst_integer ("1", None)))]\n | _ -> raise (Location.Error (Location.error ~loc "Syntax error in expression mapper")) \n end\n (* Delegate to the default mapper. *)\n | x -> default_mapper.expr mapper x;\n end\n\nlet addone_mapper argv =\n { \n default_mapper with\n expr = expr_mapper;\n }\n \nlet () = register "addone" addone_mapper\n</code></pre>\n<h2>构建和打包</h2>\n<p>假设您对 ppx 重写器感到满意。 到目前为止的直观工作流程(使用 ppx 重写器重写文件,编译重写文件)对于玩具问题是可接受的,但对于较大的项目是不可行的。 理想情况下,我们希望能够使用扩展语言编写源文件,而不必担心调用预处理步骤。</p>\n<p>这就是打包的地方:将重写器作为包安装(例如,addone.ppx),您只需将其指定为依赖项,编译器将为您处理中间步骤:</p>\n<pre><code class=\"language-text\">ocamlfind ocamlc -package addone.ppx -package decml -linkpkg addone.ml\n</code></pre>\n<p>我个人发现 <a href=\"http://oasis.forge.ocamlcore.org/\">oasis</a> 工具最容易使用,以便在更复杂的场景下构建扩展。whitequark 的教程提供了一个很好的示例配置;然后,您可以使用 <a href=\"https://github.com/ocaml/oasis2opam\">oasis2opam</a> 等工具将其转换为 <a href=\"http://opam.ocaml.org/\">opam</a> 格式,在本地固定,甚至发布!您可能还希望查看更复杂的项目(例如 <a href=\"http://akabe.github.io/slap/\">SLAP</a> )以查看示例 oasis 配置和项目布局,因为 oasis 文档简短且不完整。值得一提的是,OCaml 社区似乎正在向 <a href=\"https://github.com/ocaml/dune\">dune</a> (jbuilder)转变为事实上的构建工具,尽管我认为它更难以作为一个完整的初学者使用,特别是对于 ppx 重写器。</p>\n<h2>Parsetree 版本</h2>\n<p>使用 ppx 重写器时的一个警告是 AST 数据类型在不同版本之间略有不同;因此,为 OCaml 版本 4.02 编写的扩展名与例如版本 4.05 的 OCaml 编译器不兼容。社区已经提出了一种自动方式,用于在 <a href=\"https://github.com/ocaml-ppx/ocaml-migrate-parsetree\">ocaml-migrate-parsetree</a> 库中的不同 AST 版本之间转换扩展。对于本博客中提供的示例,您需要使用 OCaml 4.05 版。如果您只打算支持有限数量的版本,则可能需要避免开销并手动将重写器转换为适当的版本。</p>\n<pre><code class=\"language-ocaml\">{pexp_desc =\n Pexp_apply ({pexp_desc = Pexp_ident {txt = Lident "+"}},\n [(Nolabel, {pexp_desc = Pexp_constant (Pconst_integer ("1", None))});\n (Nolabel, {pexp_desc = Pexp_constant (Pconst_integer ("2", None))})])}\n</code></pre>\n<p>AST for 1 + 2 in OCaml version 4.05.</p>\n<pre><code class=\"language-ocaml\">{pexp_desc =\n Pexp_apply ({pexp_desc = Pexp_ident {txt = Lident "+"}},\n [("", {pexp_desc = Pexp_constant (Const_int 1)});\n ("", {pexp_desc = Pexp_constant (Const_int 2)})])}\n</code></pre>\n<p>AST for 1 + 2 in OCaml version 4.02.</p>\n<h2>更多资源</h2>\n<ul>\n<li><a href=\"https://github.com/ocaml-ppx/ppx_tools\">ppx_tools</a> 库在编写 ppx 重写器时提供了有用的功能。 本教程提到了重写器和 dumpast; 作者还提供了 metaquot,它为您提供了一种简单的方法来获取重写器代码中特定表达式的 OCaml 语法树。 例如,使用它,您可以通过编写 [%expr 1 + 2] 来获取 1 + 2 的 AST,而不是使用详细的 parsetree s数据类型来构造它。 这在编写测试时很方便。</li>\n<li><a href=\"https://whitequark.org/blog/2014/04/16/a-guide-to-extension-points-in-ocaml/\">whitequark 的教程</a>是关于 ppx 重写器的原始教程,是我的初始起点。</li>\n<li>Shayne Fletcher 编写了一个<a href=\"http://blog.shaynefletcher.org/2017/05/preprocessor-extensions-for-code.html\">优秀的教程</a>,它使用了多种类型的映射器(不是表达式,而是结构和类型/构造函数声明)。您可能会发现它可用作重写器的另一个用例。</li>\n</ul>\n<p>编辑26/06/2018</p>\n<p><a href=\"https://www.reddit.com/r/ocaml/comments/8sus7f/a_tutorial_to_ocaml_ppx_language_extensions/\">这里</a>有关于 OCaml reddit 的帖子的讨论。 OCaml 社区有帮助指出包装部分已经过时。 您可以在 <a href=\"http://rgrinberg.com/posts/extension-points-3-years-later/\">Rudi Grinberg 的教程</a>中查看如何使用 dune / jbuilder(较新的构建工具)设置配置,包括运行测试以将语法树与 diff 工具进行比较的巧妙方法。 他提供了一个可以在<a href=\"https://github.com/rgrinberg/ppx_getenv2\">这里</a>克隆的初学者项目。 有些库已经重新组织,因此您必须更改导入才能使其开箱即用。</p>\n","author":"lanqy"},{"title":"使用 Rebar3 构建您的第一个 Erlang 应用程序","created":"2018/08/10","link":"2018/08/10/building-your-first-erlang-app-using-rebar3","description":"使用 Rebar3 构建您的第一个 Erlang 应用程序","content":"<h1>使用 Rebar3 构建您的第一个 Erlang 应用程序</h1>\n<p>Rebar3 是 Erlang 的构建工具和包管理工具。由于 Rebar3 带有 <a href=\"https://hex.pm/\">Hex</a> 插件,因此创建和发布 Erlang 软件包非常简单。让我们制作一个简单的 “hello world” 包,随意在家玩!</p>\n<h2>下载 Rebar3</h2>\n<p>在此下载最新版本:<a href=\"http://www.rebar3.org/\">http://www.rebar3.org/</a>。</p>\n<pre><code class=\"language-text\">curl -O https://s3.amazonaws.com/rebar3/rebar3\n</code></pre>\n<p>使用 chmod 使其可执行,然后将其添加到环境变量 PATH。</p>\n<pre><code class=\"language-text\">chmod +x rebar3\nexport PATH=$PATH:your-current-directory\n</code></pre>\n<h2>你的第一个 Erlang 应用程序</h2>\n<p>从命令 rebar3 new 开始,从名为 app 的内置模板生成一个新项目。在这个例子中,我们正在创建一个名为 myapp 的项目。其他可用的模板有:release,lib,plugin,escript,cmake。</p>\n<pre><code class=\"language-text\">$ rebar3 new app myapp\n===> Writing myapp/src/myapp_app.erl\n===> Writing myapp/src/myapp_sup.erl\n===> Writing myapp/src/myapp.app.src\n===> Writing myapp/rebar.config\n===> Writing myapp/.gitignore\n===> Writing myapp/LICENSE\n===> Writing myapp/README.md\n</code></pre>\n<p>包的代码放在 src 目录中。</p>\n<pre><code class=\"language-text\">$ cd myapp\n$ tree\n.\n├── LICENSE\n├── README.md\n├── rebar.config\n└── src\n ├── myapp.app.src\n ├── myapp_app.erl\n └── myapp_sup.erl\n</code></pre>\n<p>惯例是有一个 .app.src 文件将您的应用程序定义为 OTP 应用程序,因为 Rebar3 只处理 OTP <a href=\"http://www.erlang.org/doc/design_principles/applications.html\">结构化项目</a>。看起来很熟悉?该文件也是 Erlang 。查看完整<a href=\"http://www.erlang.org/doc/design_principles/applications.html#id73836\">参考</a>,看看它可以包含什么。</p>\n<pre><code class=\"language-erlang\">$ cat src/myapp.app.src \n{application, 'myapp',\n [{description, "An OTP application"},\n {vsn, "0.1.0"},\n {registered, []},\n {mod, {'myapp_app', []}},\n {applications,\n [kernel,\n stdlib\n ]},\n {env,[]},\n {modules, []}\n ]}.\n</code></pre>\n<p>src / myapp_app.erl 中的代码非常简单。它只是确保您可以启动和停止您的 Erlang 应用程序:</p>\n<pre><code class=\"language-erlang\">$ cat src/myapp_app.erl\n-module('myapp_app').\n-behaviour(application).\n-export([start/2, stop/1]).\n\nstart(_StartType, _StartArgs) ->\n 'myapp_sup': start_link().\n\nstop(_State) ->\n ok.\n</code></pre>\n<p>Rebar3 使用名为 rebar.config 的文件来指定附加元数据,例如<a href=\"https://github.com/rebar/rebar/wiki/Dependency-management\">依赖项</a>。rebar.config 可以包含很多字段。要查看它们,请查看<a href=\"https://github.com/rebar/rebar/blob/master/rebar.config.sample\">完整的样本</a>。</p>\n<pre><code class=\"language-text\">$ cat rebar.config \n{erl_opts, [debug_info]}.\n{deps, []}.\n</code></pre>\n<p>现在让我们使用 Rebar3 启动一个 Erlang shell ,其中包含您的应用程序和路径中的依赖项。运行应用程序:启动(myapp)。验证您的应用是否已正确加载。</p>\n<pre><code class=\"language-erlang\">$ rebar3 shell\n===> Verifying dependencies...\n===> Compiling myapp\nErlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:8:8] [async-threads:0] [hipe] [kernel-poll:false]\nEshell V5.10.4 (abort with ^G)\n1> application:start(myapp).\nok\n2> application:stop(myapp). \nok\n3> \n=INFO REPORT==== 29-Jun-2015::16:14:10 ===\n application: myapp\n exited: stopped\n type: temporary\n</code></pre>\n<p>要了解命令 rebar3 shell,Fred Hebert(<a href=\"http://learnyousomeerlang.com/\">Learn You Some Erlang</a> 的作者)在<a href=\"http://ferd.ca/rebar3-shell.html\">这里</a>写了一篇很好的帖子。</p>\n<h2>Erlang 包</h2>\n<h3>在你开始之前</h3>\n<p>我们需要安装一个名为 <a href=\"https://github.com/hexpm/rebar3_hex\">rebar3_hex</a> 的插件,以便使用来自 <a href=\"https://hex.pm/\">Hex.pm</a>(Erlang / Elixir 包管理器)的获取和安装 Erlang 包。只需将以下行添加到 rebar.config 文件中即可。 (你需要 Erlang 版 OTP 17.4 及以上版本)</p>\n<pre><code class=\"language-text\">{plugins, [rebar3_hex]}.\n</code></pre>\n<p>然后通过:rebar3 update 命令运行,以启用插件。</p>\n<pre><code class=\"language-text\">$ rebar3 update\n===> Fetching jsx ({pkg,<<"jsx">>,<<"2.6.1">>})\n===> Fetching ssl_verify_hostname ({pkg,<<"ssl_verify_hostname">>,\n <<"1.0.5">>})\n===> Fetching rebar3_hex ({pkg,<<"rebar3_hex">>,<<"0.6.0">>})\n===> Compiling ssl_verify_hostname\n===> Compiling jsx\n===> Compiling rebar3_hex\n===> Updating package index...\n</code></pre>\n<p>如果您想在每次创建新的 Erlang 应用程序时避免此步骤,请将该条目添加到全局 rebar.config 并将其放在:</p>\n<pre><code class=\"language-text\">~/.config/rebar3/rebar.config\n</code></pre>\n<h2>寻找 Erlang 包</h2>\n<p>使用 search 命令可以找到在 <a href=\"https://hex.pm/\">Hex.pm</a> 上发布的远程 Erlang 包。您可以在查询中使用正则表达式字符:</p>\n<pre><code class=\"language-text\">$ rebar3 hex search cowboy\ncloudi_service_http_cowboy\ncowboy\n</code></pre>\n<h2>安装包</h2>\n<p>Rebar3 可以下载并安装 Erlang 包和任何必要的依赖项。将应用程序名称添加到 rebar.config 文件中的 deps 条目,然后运行命令:rebar3 compile。在这个例子中,我们尝试使用两个名为 cowboy 和 meck 的Erlang 包。</p>\n<pre><code class=\"language-text\">{deps, [cowboy, meck]}.\n$ rebar3 compile\n===> Verifying dependencies...\n===> Fetching ranch ({pkg,<<"ranch">>,<<"1.0.0">>})\n===> Fetching meck ({pkg,<<"meck">>,<<"0.8.2">>})\n===> Fetching cowlib ({pkg,<<"cowlib">>,<<"1.0.1">>})\n===> Fetching cowboy ({pkg,<<"cowboy">>,<<"1.0.0">>})\n===> Compiling cowlib\n===> Compiling ranch\n===> Compiling meck\n===> Compiling cowboy\n===> Compiling myapp\n</code></pre>\n<p>想要安装特定版本的 Erlang 包吗?将应用程序名称和版本写入元组中。您可以在 <a href=\"https://hex.pm/\">Hex 主页</a>上浏览包的可用版本。</p>\n<pre><code class=\"language-text\">{deps, [{cowboy, “1.0.2”}, {meck, "0.8.3"}]}.\n</code></pre>\n<h2>列出已安装的包</h2>\n<p>rebar3 deps 命令显示本地安装的软件包:</p>\n<pre><code class=\"language-text\">$ rebar3 deps\ncowboy (locked package 1.0.0)\nmeck (locked package 0.8.2)\n</code></pre>\n<h2>卸载包</h2>\n<p>要卸载软件包,首先必须将其从 rebar.config 文件中删除,然后使用命令:rebar3 unlock。在这里,我们从列表中删除了包 meck。</p>\n<pre><code class=\"language-text\">$ rebar3 unlock\n$ rebar3 deps\ncowboy (locked package 1.0.0)\n</code></pre>\n<h2>进一步阅读</h2>\n<p>http://www.rebar3.org/</p>\n","author":"lanqy"},{"title":"Erlang maps 函数 “简单” 的解释","created":"2018/08/01","link":"2018/08/01/erlang-maps-function-simple-explanation","description":"Erlang maps 函数 “简单” 的解释","content":"<h1>Erlang maps 函数 “简单” 的解释</h1>\n<p>译自:https://qiita.com/kayama0fa/items/b0e60644ed40b318a513</p>\n<h2>Erlang maps 函数 “简单” 的解释</h2>\n<p>描述 maps 函数,一行的简要说明和示例代码。 有关更多信息,请参阅官方参考。</p>\n<p>原始转发(官方参考)http://erlang.org/doc/man/maps.html</p>\n<p>描述,以便您可以立即找到该功能,并能够大致掌握它的移动方式。 评述的顺序是根据基于主观性的重要性来安排的。</p>\n<p>本页的TODO:</p>\n<ul>\n<li>由于它几乎完全写入,检查是否有错误。</li>\n<li>如果我能负担得起的话,我会做得更好一点。但是如果你写了别的东西,你想增加另一个库。</li>\n<li>示例代码已改进</li>\n</ul>\n<h2>创建一个空 maps - maps:new/0</h2>\n<pre><code class=\"language-erlang\">M0 = maps:new(),\n</code></pre>\n<h2>向 maps 添加值 - maps:put/3</h2>\n<pre><code class=\"language-erlang\">M1 = maps:put(a, 1, M0),\nM2 = maps:put(b, 2, M1),\nM3 = maps:put(c, 3, M2),\nM4 = maps:put(d, "abc",M3),\nM5 = maps:put(e, <<"アイウエオ"/utf8>>, M4), \n</code></pre>\n<h2>从 maps 中取值 - maps:get/2, maps:get/3</h2>\n<pre><code class=\"language-erlang\">1 = maps:get(a, M5),\nnot_found = maps:get(g, M5, not_found),\n</code></pre>\n<h2>从 maps 中删除值 - maps:remove/2</h2>\n<pre><code class=\"language-erlang\">M4 = maps:remove(e, M5),\nM5 = maps:remove(d, M4),\n</code></pre>\n<h2>从 maps 中查找和检索值 - maps:find/2</h2>\n<pre><code class=\"language-erlang\">{ok, _} = maps:find(e, M5),\nerror = maps:find(g, M5),\n</code></pre>\n<h2>检查 maps 是否具有指定的 key - maps:is_key/2</h2>\n<pre><code class=\"language-erlang\">true = maps:is_key(a, M5),\nfalse = maps:is_key(x, M5),\n</code></pre>\n<h2>获取在 maps 中注册的数据数量 - maps:size/1</h2>\n<pre><code class=\"language-erlang\">5 = maps:size(M5),\n1 = maps:size(M1),\n0 = maps:size(M0),\n</code></pre>\n<h2>获取在 maps 中注册的 keys 列表 - maps:keys/1</h2>\n<pre><code class=\"language-erlang\">[a, b, c] = lists:sort(maps:keys(M3)),\n</code></pre>\n<h2>获取在 maps 中注册的值列表 - maps:valus/1</h2>\n<pre><code class=\"language-erlang\">[1,2,3] = lists:sort(maps:values(M3)),\n</code></pre>\n<h2>maps 一个列表的 keys 和 值的元组 - maps:to_list/1</h2>\n<pre><code class=\"language-erlang\">[{a, 1},{b, 2}] = maps:to_list(M2),\n</code></pre>\n<h2>从 keys 和值元组列表中获取 maps - maps:from_list/1</h2>\n<pre><code class=\"language-erlang\">M2 = maps:from_list([{a, 1}, {b, 2}]),\n</code></pre>\n<h2>将任意筛选应用于 maps 以创建新 maps - maps:filter/2</h2>\n<pre><code class=\"language-erlang\">FilterM = maps:from_list([{a, 1},{b, 2}]),\nFilterM = maps:filter(fun(_K, V) -> (V rem 3) =:= 1 end, M3),\n</code></pre>\n<h2>在 maps 上执行卷积操作 - maps:fold/3</h2>\n<pre><code class=\"language-erlang\">6 = maps:fold(fun(_, V, Acc) when is_integer(V) -> V + Acc; (_, _, Acc) -> Acc end, 0, M5),\n</code></pre>\n<h2>返回 maps 的每个值的任意处理结果 - maps:map/2</h2>\n<pre><code class=\"language-erlang\">NewMap = maps:from_list([{a, 10}, {b, 20}, {c, 30}]),\nNewMap = maps:map(fun(_K, V) -> V * 10 end, M3),\n</code></pre>\n<h2>合并两个 maps - maps:merge/2</h2>\n<p>%如果存在相同的键,则优先考虑第二个 maps 的值</p>\n<pre><code class=\"language-erlang\">MapA = maps:from_list([{a, 10}, {b, 20}]),\nMapB = maps:from_list([{a, 1}, {c, 30}]),\nMapAB = maps:from_list([{a, 1}, {b, 20}, {c, 30}]),\nMapAB = maps:merge(MapA, MapB),\n</code></pre>\n<h2>更新 maps 的任何键的值 - maps:update/3</h2>\n<pre><code class=\"language-erlang\">UpdateMap1 = maps:from_list([{a, 1}, {b, 2}]),\nUpdateMap2 = maps:from_list([{a, 10}, {b, 2}]),\nUpdateMap2 = maps:update(a, 10, UpdateMap1),\n</code></pre>\n<h2>用函数更新 maps 任意键的值 - maps:update_with/3</h2>\n<pre><code class=\"language-erlang\">UpdateMap2 = maps:update_with(a, fun(V) -> V * 10 end, UpdateMap1),\n</code></pre>\n<h2>来自 update_with/3,一个可以在没有 key 时指定默认值的函数 - maps:update_with/4</h2>\n<pre><code class=\"language-erlang\">UpdateMap3 = maps:from_list([{a, 1}, {b, 2}, {c, 3}]),\nUpdateMap3 = maps:update_with(c, fun(V) -> V * 10 end, 3, UpdateMap1),\n</code></pre>\n<h2>仅使用指定的键从 maps 中重新创建 maps - maps:with/2</h2>\n<pre><code class=\"language-erlang\">WithKeys = [a, c],\nWithMap1 = maps:from_list([{a, 1},{b, 2}, {c, 3}]),\nWithMap2 = maps:from_list([{a, 1}, {c, 3}]),\nWithMap2 = maps:with(WithKeys, WithMap1),\n</code></pre>\n<h2>使用非指定的键重建 maps,使用 with/2 相反的键 - maps:without/2</h2>\n<pre><code class=\"language-erlang\">WithMap3 = maps:from_list([{b, 2}]),\nWithMap3 = maps:without(WithKeys, WithMap1),\n</code></pre>\n<h2>从已删除该键的 maps 和 maps 中返回任意键的值 - take/2</h2>\n<pre><code class=\"language-erlang\">{2, M1} = maps:take(b, M2)\n</code></pre>\n","author":"lanqy"},{"title":"Erlang 入门","created":"2018/07/31","link":"2018/07/31/getting-start-erlang","description":"Erlang 入门","content":"<h1>Erlang 入门</h1>\n<p>译自:https://qiita.com/oskimura/items/87910c2e76cac075ee7f</p>\n<h2>Erlang 入门</h2>\n<p>Erlang 是一种支持语言和 VM 并行处理的函数式语言。此外,Erlang 受到 Prolog 及其并行逻辑语言的强烈影响,后者是其继承者,其效果可见于 VM 的语法和结构。</p>\n<h3>安装</h3>\n<h4>Windows</h4>\n<p>从 http://www.erlang.org/downloads 下载安装程序并安装它。</p>\n<h4>Mac</h4>\n<pre><code class=\"language-text\">brew install erlang\n</code></pre>\n<p>你可以安装</p>\n<h4>Linux</h4>\n<p>如果是 Debian</p>\n<pre><code class=\"language-text\">apt install erlang\n</code></pre>\n<h3>对话环境</h3>\n<h4>Windows</h4>\n<pre><code class=\"language-text\">"Start" -> "All Programs" -> "Erlang OTP" -> "Erlang"\n</code></pre>\n<h4>Mac & Linux</h4>\n<p>通过从终端输入来建立对话环境。我们来试试吧。</p>\n<pre><code class=\"language-text\">$ erl\n</code></pre>\n<pre><code class=\"language-erlang\">$ erl\nErlang R16B03 (erts-5.10.4) [source] [64-bit] [async-threads:10] [kernel-poll:false]\n\nEshell V5.10.4 (abort with ^G)\n1> 1+2+3.\n6\n2>\n</code></pre>\n<p>6 它将显示。这是 1 + 2 + 3. 计算公式的结果。Erlang 使用 . 代替 ;。</p>\n<h3>编译</h3>\n<p>用 c 命令编译 。</p>\n<p>由于 Erlang 在名为 BEAM 的 VM 上运行,因此它被编译为在 VM 上运行的二进制文件。</p>\n<p>我们将此文件称为梁文件。</p>\n<p>创建的文件是</p>\n<pre><code class=\"language-shell\">$ erl - pz. - noshell - noinput - s module - name function - s init stop\n</code></pre>\n<p>您可以在终端上键入并运行。</p>\n<p>但是,如果您想轻松运行它,它会更方便使用。</p>\n<h3>escript</h3>\n<p>在escript中,Erlang的程序可以像Perl脚本一样执行。</p>\n<pre><code class=\"language-erlang\">#!/usr/bin/env escript\n%% -*- erlang -*-\n%%! -smp enable -sname factorial -mnesia debug verbose\nmain([String]) ->\n try\n N = list_to_integer(String),\n F = fac(N),\n io:format("factorial ~w = ~w\\n", [N,F])\n catch\n _:_ ->\n usage()\n end;\nmain(_) ->\n usage().\nusage() ->\n io:format("usage: factorial integer\\n"),\n halt(1).\n\nfac(0) -> 1;\nfac(N) -> N * fac(N - 1).\n</code></pre>\n<pre><code class=\"language-text\">$ chmod u+x factorial\n</code></pre>\n<pre><code class=\"language-text\">$ ./factorial 5\nfactorial 5 = 120\n</code></pre>\n<pre><code class=\"language-text\">$ ./factorial\nusage: factorial integer\n</code></pre>\n<pre><code class=\"language-text\">$ ./factorial\nusage: factorial integer\n</code></pre>\n<pre><code class=\"language-text\">$ ./factorial five\nusage: factorial integer\n</code></pre>\n<pre><code class=\"language-text\">$ escript factorial 5\n</code></pre>\n<p>等等。</p>\n<p>有关详细信息,请参阅 <a href=\"http://erlang.org/doc/man/escript.html\">escript manual</a>。</p>\n<h3>整数和实数</h3>\n<pre><code class=\"language-erlang\">4> 2#0101.\n5\n5> 16#deadbeaf.\n3735928495\n</code></pre>\n<p>作为 # 你可以通过在前面放置一个数字来将 N-ary 符号提高到 36 位小数。</p>\n<p>Erlang 是一个多精度整数。</p>\n<p>IEEE 754格式的浮点数在 1.0e−323 到 1.0e+308 范围内</p>\n<h3>文字列</h3>\n<p>字符前面是 ASCII 字符 $ 。用双引号括起字符串。这是字母列表的糖涂层语法。列表将描述以下内容。</p>\n<p>它需要以后面描述的二进制表示。</p>\n<pre><code class=\"language-erlang\">7> $a.\n97\n8> "a".\n"a"\n9> [$a,$b].\n"ab"\n</code></pre>\n<h3>变量</h3>\n<p>Erlang 变量名以大写字母开头。</p>\n<pre><code class=\"language-erlang\">10> X = 1.\n1\n11> X.\n1\n</code></pre>\n<p>这是变量 X 在 1 中的值,通过代入 X 的值。</p>\n<p>我们将用后面描述的模式匹配来解释赋值。</p>\n<h3>操作符</h3>\n<h4>算术运算符</h4>\n<ul>\n<li><code>+</code>\n加</li>\n<li><code>*</code>\n乘</li>\n<li><code>-</code>\n减</li>\n<li><code>/</code>\n除</li>\n<li><code>div</code>\n整数除法</li>\n<li><code>rem</code>\n整数除法的余数</li>\n</ul>\n<p>它基本上与C语言和Java相同。div和rem分别是除数时的商和余数。</p>\n<h4>比较运算符</h4>\n<ul>\n<li><code>></code>\n大于</li>\n<li><code><</code>\n小于</li>\n<li><code>>=</code>\n大于等于</li>\n<li><code>=<</code>\n小于等于</li>\n<li><code>==</code>\n等于</li>\n<li><code>/=</code>\n不等于</li>\n<li><code>=:=</code>\n相等(带类型检查)</li>\n<li><code>=/=</code>\n不等于(带类型检查)</li>\n</ul>\n<h4>算术运算符</h4>\n<ul>\n<li><code>not</code>\n否定</li>\n<li><code>and</code>\n和</li>\n<li><code>andalso</code>\n和顺便(短路)</li>\n<li><code>or</code>\n或</li>\n<li><code>orelse</code>\n或者其他(短路)</li>\n<li><code>xor</code>\n独家理论和</li>\n</ul>\n<h3>条件分支</h3>\n<h4>if</h4>\n<pre><code class=\"language-erlang\">if\n <conditional expression> -> <form 1>;\n <Conditional> -> <Formula 2>\nend\n</code></pre>\n<p>因为 Erlang 有一个模式匹配,所以 if 使用不多。</p>\n<p>对于条件分支,我们经常使用模式匹配,如稍后所述。</p>\n<h3>原子</h3>\n<p>原子代表一个标识符。小写字母如果包含以字母或符号开头的字母数字字符,则必须将它们括在反引号中。</p>\n<pre><code class=\"language-erlang\">1> abc.\nabc\n</code></pre>\n<p>当然,您可以将其分配给变量。</p>\n<pre><code class=\"language-erlang\">2> X = abc.\nabc\n3> X.\nabc\n</code></pre>\n<p>Atom 经常用于 Erlang 编程,因为它便于后面描述的元组和消息的模式匹配。</p>\n<h3>输出</h3>\n<p>在 Erlang 中,输出到控制台 io:format 是使用通常调用的函数完成的。</p>\n<h4>io:format</h4>\n<p>Erlang 的格式化输出是通过 io:format完成。printf它对应于C语言,但格式化的输出格式类似于 Common Lisp 或者来自 C 的 Common Lisp。</p>\n<pre><code class=\"language-erlang\">io:format("~p~n", [Term]).\n</code></pre>\n<ul>\n<li><code>~p</code> 漂亮打印</li>\n<li><code>~s</code> 文本列</li>\n<li><code>~n</code> 更改行数</li>\n<li><code>~d</code> 整数(十进制数)</li>\n</ul>\n<p>Erlang 使用列表,因为没有像 C 语言这样的可变参数。</p>\n<h3>模式匹配</h3>\n<h4>案例部分</h4>\n<pre><code class=\"language-erlang\">case <type> of\n <表达式1> -> <式1>;\n <表达式2> -> <式2>\nend\n</code></pre>\n<p>case 子句是最简单的模式匹配形式。</p>\n<pre><code class=\"language-erlang\">2> A = 3.\n3\n3> case A of 1 -> a; 2 -> b; 3 -> c end.\nc\n</code></pre>\n<p>将返回 c ,它也可以像这样的C语言 switch 句子。</p>\n<p>如果我们从此表达式中排除最后一次模式匹配的处理,会发生什么?</p>\n<pre><code class=\"language-erlang\">4> case A of 1 -> a; 2 -> b end.\n** 异常错误:没有 case 子句匹配3\n5>\n</code></pre>\n<p>像这样发生异常。这是一个例外,意味着没有条件匹配模式匹配。</p>\n<h4>通配符</h4>\n<p>然后如何更改它如下?它是通配符,并且在每种情况下都匹配。</p>\n<pre><code class=\"language-erlang\">5> case A of 1 -> a; 2 -> b; _ -> c end.\nc\n</code></pre>\n<h4>单赋值</h4>\n<pre><code class=\"language-erlang\">6> X = 1.\n</code></pre>\n<p>实际上,替换实际上是一种模式匹配。</p>\n<p><code>=</code> 操作符在左侧表达式的右侧执行模式匹配。如果左边的变量尚未绑定,则计算右边表达式的值将被绑定。<code>=</code> 当然,如果表达式左侧的模式匹配失败,则会引发异常。</p>\n<p>在这个例子中,因为它只是一个像数字类型的简单类型,我认为它的用处很难理解。通过结合后面描述的列表,记录,元组等,您可以以非常多样化的方式使用它。</p>\n<p>模式匹配是 Erlang 的核心功能。</p>\n<h3>函数</h3>\n<pre><code class=\"language-erlang\"><function name> (参数...) -> \n函数主体<body>.\n</code></pre>\n<p>例如,下面是一个函数,用于递增指定为参数的变量。函数定义是这样的。</p>\n<pre><code class=\"language-erlang\">inc(X) -> X + 1.\n</code></pre>\n<h3>函数和模式匹配</h3>\n<p>Erlang 还可以对函数参数执行模式匹配。</p>\n<pre><code class=\"language-erlang\">signal(red) -> stop;\nsignal(blue) -> do;\nsignal(yellow) -> carefull;\nsignal(_) -> error.\n</code></pre>\n<h4>递归</h4>\n<p>由于 Erlang 没有循环控制语法(如 C 语言中的语句),因此循环处理是递归进行的。</p>\n<p>我将编写一个函数来查找阶乘。</p>\n<pre><code class=\"language-erlang\">pow(1) -> 1;\npow(X) -> X * pow(X - 1).\n</code></pre>\n<h4>尾递归</h4>\n<p>可以通过使用尾递归的形式来优化循环。</p>\n<pre><code class=\"language-erlang\">pow_tail(1, Ret) -> Ret;\npow_tail(X, Ret) -> pow_tail(X - 1, X * Ret).\n</code></pre>\n<p>简单地说,尾递归形式不计算递归函数的返回值,因此不需要每次都将函数的返回点堆栈在堆栈帧上,因此可以像循环一样在内部处理它</p>\n<p>有兴趣的人请自己研究。</p>\n<h3>记录</h3>\n<p>这是一个所谓的结构。</p>\n<pre><code class=\"language-erlang\">-record(<记录名>, {<元素名1>, <元素名2>, ...}).\n</code></pre>\n<p>还提供了一种称为定义记录的函数的 rd 语法糖。</p>\n<pre><code class=\"language-erlang\">rd(item, {id, name, price}).\n</code></pre>\n<pre><code class=\"language-erlang\">7> rd(player, {name, hp}).\nplayer\n</code></pre>\n<h4>初始化</h4>\n<pre><code class=\"language-erlang\"># <记录名> {<元素名1> = 初始值1, <元素名1> = 初始值2, ...}\n</code></pre>\n<p>可以以这种形式进行记录初始化。</p>\n<pre><code class=\"language-erlang\">9> P = #player{name="hoge", hp=3}.\n</code></pre>\n<h4>引用</h4>\n<pre><code class=\"language-erlang\">Variable#<record name>.<Element>\n</code></pre>\n<p>上面提到它。</p>\n<pre><code class=\"language-erlang\">10>P#player.name\n"hoge"\n</code></pre>\n<h4>更改</h4>\n<pre><code class=\"language-erlang\">Variable#<record name>{<element> = "hage"}.\n</code></pre>\n<p>通过这样做,它返回更改记录元素的记录。</p>\n<pre><code class=\"language-erlang\">11> P#player{name="hage"}.\n#player{name = "hage", hp = 2}\n</code></pre>\n<h4>记录模式匹配</h4>\n<p>在左侧</p>\n<pre><code class=\"language-erlang\"># <Record name> {<element name 1> = value, <element name 2> = variable name, ..}\n</code></pre>\n<p>你可以用搭配的形式进行模式匹配。</p>\n<pre><code class=\"language-erlang\">2> P = #item{id = 1, name="test", price=100}.\n3> #item{id=1} = P.\n#item{id = 1, name = "test", price = 100}.\n4> #item{id = 1, name = Name} = P.\n#item{id = 1, name = "test", price = 100}\n5> Name.\n"test"\n</code></pre>\n<h3>列表</h3>\n<pre><code class=\"language-erlang\">[<element 1>, <element 2>...]\n</code></pre>\n<pre><code class=\"language-erlang\">1> [1,2,3].\n[1,2,3]\n</code></pre>\n<p>这与 Lisp 等列表相同。内部缺点,最后一个是递归结构,单元格类似于Lisp nil。这将在后面描述。</p>\n<h4>列表处理</h4>\n<p>该列表可以通过模式匹配来处理。</p>\n<p>接下来,我们以一个计算列表长度的函数为例。</p>\n<pre><code class=\"language-erlang\">len([]) ->\n 0;\nlen([Head|Tail]) ->\n 1 + len(Tail).\n</code></pre>\n<p>[] 匹配空列表。</p>\n<p>[Head|Tail] Head 标识列表头, 例如, [1,2,3] Head 匹配 1, Tail 匹配 [2,3].</p>\n<p>处理列表的函数是组织成称为标准库的模块的列表。</p>\n<h3>元组</h3>\n<pre><code class=\"language-erlang\">{<Element 1>, <element 2>...}\n</code></pre>\n<pre><code class=\"language-erlang\">1> {1,2,3}.\n{1,2,3}\n</code></pre>\n<p>在 Erlang 中经常使用元组而不是 Lisp 的 S 表达式。</p>\n<h3>二进制</h3>\n<pre><code class=\"language-erlang\"><< <Value 1>: <Size 1> / <Type>, <Value 2>: <Size 2> / <Type> ... >>\n</code></pre>\n<p>您可以为每个尺寸指定一个大小的值。如果省略大小规范,则默认为 8 位。按此大小的大小划分的块称为段。</p>\n<p>可以指定类型,类型,代码,字节序,多种类型 - 可以连接。 可能的类型如下所列。</p>\n<p>类型</p>\n<ul>\n<li><code>integer</code> 整型</li>\n<li><code>float</code> 浮点数</li>\n<li><code>binary</code> 二进制类型</li>\n<li><code>bytes</code> 字节型</li>\n<li><code>bitstring</code> 位串</li>\n<li><code>bits</code> 位类型</li>\n<li><code>utf8</code> UTF-8 位字符串</li>\n<li><code>utf16</code> UTF16 的位串</li>\n<li><code>utf32</code> UTF32 位字符串</li>\n</ul>\n<p>符号</p>\n<ul>\n<li><code>signed</code> 有符号的</li>\n<li><code>unsigned</code> 无符号</li>\n</ul>\n<p>尾数法</p>\n<ul>\n<li><code>big</code> 大端字节序</li>\n<li><code>little</code> 小端字节</li>\n<li><code>native</code> CPU 原生端</li>\n<li><code>unit</code> 段的大小</li>\n</ul>\n<h4>二元运算符</h4>\n<ul>\n<li><code>bsl</code> 左移位</li>\n<li><code>bsr</code> 右移位</li>\n<li><code>band</code> 比特理论</li>\n<li><code>bor</code> 比特理论和</li>\n<li><code>bxor</code> 比特独家论证和</li>\n<li><code>bnot</code> 比特论证否定</li>\n</ul>\n<h4>二进制模式匹配</h4>\n<p>作为二进制模式匹配的示例,我将展示 RGB 颜色的示例。</p>\n<pre><code class=\"language-erlang\">1> Color = <<16#FF00FF:(8*3)>>.\n<<255,0,255>>\n</code></pre>\n<p><code>,</code> 您可以将每个 8 位 RGB 定义为相隔。</p>\n<pre><code class=\"language-erlang\">2> <<R,G,B>> = Color.\n<<255,0,255>>\n</code></pre>\n<p>这样,它可以通过模式匹配每 8 位进行分解。</p>\n<p>如果只想要第一个 R ,可以使用通配符通过以下方式编写来获得它。</p>\n<pre><code class=\"language-erlang\">2> <<R:8,_/binary>> = Color.\n<<255,0,255>>\n3> R.\n255\n</code></pre>\n<p>顺便说一句,在 Erlang 中处理像日语这样的多字节字符</p>\n<pre><code class=\"language-erlang\">io:format("~ts~n",[<<"お"/utf8>>]).\n</code></pre>\n<p>必须以二进制格式指定UTF-8,如下所示 有关详细信息,请参阅 <a href=\"http://erlang.org/doc/programming_examples/bit_syntax.html\">Erlang 文档</a>。</p>\n<h3>理解符号</h3>\n<p>有两种类型的理解符号:列表理解符号和二进制理解符号。</p>\n<p>最初,理解符号是表示一组与集合论中的条件 P 一致的元素的符号。</p>\n<p>试图表达这一点的符号被介绍给米兰达。这种表示法后来被合并到 Haskell 和 Python 中。</p>\n<h4>列表理解符号</h4>\n<p>进入 <code>erl</code></p>\n<pre><code class=\"language-erlang\">Erlang R16B03 (erts-5.10.4) [source] [64-bit] [async-threads:10] [kernel-poll:false]\n\nEshell V5.10.4 (abort with ^G)\n1> [ X * 2 || X <- [1,2,3,4,5], X < 4 ].\n[2,4,6]\n</code></pre>\n<p>这个 X < - [1,2,3,4,5] 产生一个列表。</p>\n<p>接下来,X < 4 它匹配条件 1 2 3 但被过滤,</p>\n<p>最后列表 X * 2 由 1 * 2 , 2 * 2 , 3 * 2 创建公式。</p>\n<p>概括如下:</p>\n<pre><code class=\"language-erlang\">[Expression || pattern <- list, condition]\n</code></pre>\n<p>二元理解是理解符号的二进制版本。</p>\n<pre><code class=\"language-erlang\"><< expression || pattern <= binary, condition >>\n</code></pre>\n<h3>并行</h3>\n<p>Erlang 中的并行处理是通过轻量级进程(进程)和消息实现的。</p>\n<p>Erlang 的过程类似于 OS 过程,但它由 Erlang 的 VM 执行,因此操作系统内的上下文切换没有完成。</p>\n<p>Erlang 的进程通过消息队列进行通信,而不共享内存区域。</p>\n<p>切换速度很快,因为 Erlang 的进程不涉及复杂的上下文切换,例如临时保存寄存器和切换到内核模式。</p>\n<h4>Actor 模型</h4>\n<p>我将简要介绍作为 Erlang 并行模型的 Actor 模型。</p>\n<p>一个叫做 actor 的对象,能够发送和接收消息 通过异步发送和接收消息并行执行计算处理的计算模型。在 Erlang 中,该过程相当于一个 actor ,我们正在相互发送和接收消息。 除了 Erlang 之外,这个 actor 模型的概念被采用为 Scala 的框架等</p>\n<h4>spawn</h4>\n<p>spawn 通过以下方式调用它来生成轻量级进程。</p>\n<pre><code class=\"language-erlang\">Pid = spawn (<module name>, <function name>, <list of arguments>)\n</code></pre>\n<p>spawn 从参数函数创建一个进程,并为每个进程返回一个唯一的数值,称为进程 ID。</p>\n<p>进程 ID 和进程是链接的,此进程 ID 用于在进程之间发送和接收消息。</p>\n<h4>发送消息</h4>\n<p>要发送消息,请将消息以消息的形式发送到您想要发送的流程的进程 ID ,如下所示。</p>\n<pre><code class=\"language-erlang\">Pid! Message\n</code></pre>\n<h4>接收消息</h4>\n<p>通过以下方式接收消息并通过模式匹配执行与消息相对应的处理是常见的。</p>\n<pre><code class=\"language-erlang\">receive\n <Pattern 1> ->\n <Process 1>;\n <Pattern 2> ->\n <Process 2>;\n \n .........\n\n <Pattern N> ->\n <Process N>\n after n ->\n < Timeout processing>\nend\n</code></pre>\n<p>after 将进程在 n 毫秒后超时。</p>\n<h4>进程注册</h4>\n<p>spawn 对程序员来说每次都要保存变量生成的进程 ID 很麻烦。特别是当多个程序创建程序来发送消息时,管理变得非常麻烦。</p>\n<p>Erlang 有一种通过指定原子而不是进程 ID 来发送消息的方法。</p>\n<p>这是流程注册。 register 使用该函数注册进程。</p>\n<p>由于它超出了本文档的范围,因此不对其进行详细描述,但是进程注册功能通常用于分布式环境中。</p>\n<pre><code class=\"language-erlang\">register(Atom,Pid)\n</code></pre>\n<p>通过 Atom ! the message 您可以在进程中发送已经以所述形式向 atom 注册的消息。</p>\n<h3>spawn_link</h3>\n<p>spawn_link 基本上是 spawn 相同,但是当发生异常时行为会有所不同。 该进程的父进程会在生成的子进程中创建一个异常。 spawn,父进程不会收到异常。</p>\n<h3>异常</h3>\n<p>我这样写异常。</p>\n<pre><code class=\"language-erlang\">try <evaluated expression> of\n <pattern 1> guard 1 -> <process 1>;\n <pattern 2> guard 2 -> <process 2>;\ncatch\n <exception type 1>: <pattern 1> guard 1 -> <Exception processing 1>;\n <exception type 2>: <pattern 2> guard 2 -> <Exception processing 2>\nafter\n <Processing executed even if an exception occurs but not occurring>\nend\n</code></pre>\n<p>try ... of 当在封闭的表达式中出现异常时,将在其后执行一个捕捉模式匹配,并在执行异常处理后执行处理。</p>\n<p>以下是引发异常的函数。</p>\n<h4>抛出异常</h4>\n<p>您可以按如下方式引发异常。</p>\n<pre><code class=\"language-erlang\">throw(<表达式>)\n</code></pre>\n<h4>退出</h4>\n<p>它引发了如下异常并终止了该过程。</p>\n<pre><code class=\"language-erlang\">exit(<表达式>)\n</code></pre>\n<h4>错误</h4>\n<p>我们将提出一个严重的例外,并终止如下过程。</p>\n<p>错误主要用于 Eralng 的 VM 生成的运行时错误。</p>\n<pre><code class=\"language-erlang\">error(<表达式>)\n</code></pre>\n<h3>模块</h3>\n<p>当以描述源的文件为单位进行编译时,模块是最小的单元。除非属于某个模块,否则无法编译源代码。</p>\n<p>要创建模块,您需要在.erl 文件中描述模块属性,例如模块声明 。</p>\n<h4>模块属性</h4>\n<pre><code class=\"language-erlang\">-module(<module name>).\n</code></pre>\n<h4>导出声明</h4>\n<p>声明导出声明函数。请连接 函数名称的参数个数如下 <code>/</code>。您可以按如下方式导出模块创建的所有功能。</p>\n<pre><code class=\"language-erlang\">-export([<函数名1>/<参数1>>, <函数名2>/<参数2>>])\n\n-export_all\n</code></pre>\n<h4>编译选项声明</h4>\n<p>为编译器指定<a href=\"http://erlang.org/doc/man/compile.html\">此选项</a>。</p>\n<h4>宏定义</h4>\n<p>宏可以定义如下。</p>\n<pre><code class=\"language-erlang\">-define{<macro>,<expression>}\n</code></pre>\n<h4>记录声明</h4>\n<p>记录可以声明如下。</p>\n<pre><code class=\"language-erlang\">-record(<记录名>, {<元素名1>, <元素名2>,...}).\n</code></pre>\n<h4>include</h4>\n<p>头文件可以包括如下。</p>\n<pre><code class=\"language-erlang\">-include("<header file>").\n</code></pre>\n<p>头文件扩展名为.hrl。</p>\n","author":"lanqy"},{"title":"理解JS:事件循环","created":"2018/07/26","link":"2018/07/26/understanding-js-the-event-loop","description":"理解JS:事件循环","content":"<h1>理解JS:事件循环</h1>\n<p>译自:https://hackernoon.com/understanding-js-the-event-loop-959beae3ac40</p>\n<p>由于大量的库,工具和各种使您的开发变得更容易的东西,许多程序员开始构建应用程序而不必深入了解某些内容的工作原理。JavaScript 是这种确切行为的典型代表。虽然它是最复杂的语言之一并且传播最广泛,但许多开发人员都被使用更高级别的工具和抽象语言的“坏部分”所吸引。</p>\n<p>虽然您仍然可以构建令人惊叹的应用程序,但进入 JavaScript 漩涡对您来说非常有益。理解“怪异部分”是将普通编码者与高级开发人员分开的原因,而 JS 生态系统不断变化,其基础是构建所有其他工具的基础。了解这些可以让您更广泛地了解并改变您对开发过程的看法。</p>\n<h2>什么是事件循环?</h2>\n<p>你可能听说 JavaScript 是一种单线程语言。您甚至可能听说过 Call Stack 和 Event Queue 这两个术语。大多数人都知道事件循环允许 JavaScript 使用回调和承诺( promises ),但还有更多内容。但还有很多东西。在不深入细节的情况下,我们将高度了解 JavaScript 代码的实际执行方式。</p>\n<h2>调用堆栈</h2>\n<p>JavaScript 有一个单独的调用堆栈,它跟踪我们当前正在执行的函数以及之后要执行的函数。首先,什么是堆栈?堆栈是一种类似数组的数据结构,但是有一些限制——您只能在后面添加项,并且只删除最后一个项。另一个例子是一堆盘子——你把它们放在一起,在任何时候你只能把最上面的拿走。</p>\n<p>当您要执行某个函数时,它会被添加到调用堆栈中。然后,如果该函数调用另一个函数 - 另一个函数将位于调用堆栈中的第一个函数之上。当您在控制台中收到错误时,您将得到一条长长的消息,该消息显示了执行路径——这正是栈在那个时刻所看到的。但是,如果我们发出请求或对某件事超时怎么办?理论上,应该冻结整个浏览器直到它被执行,这样调用堆栈才能继续?但是在实践中,您知道这不会发生——因为事件表和事件队列。</p>\n<h2>事件表和事件队列</h2>\n<p>每次调用 setTimeout 函数或执行异步操作时,都会将其添加到事件表中。这是一种数据结构,它知道在某个事件之后应该触发某个功能。一旦发生该事件(超时,单击,鼠标移动),它就会发送通知。请记住,事件表不执行函数,也不会将它们添加到调用堆栈中。它的唯一目的是跟踪事件并将其发送到事件队列。</p>\n<p>事件队列是一个类似于堆栈的数据结构 - 再次向后面添加项目但只能从前面删除它们。它存储了执行函数的正确顺序。它接收来自事件表的函数调用,但它需要以某种方式将它们发送到调用堆栈?这就是Event Loop的用武之地。</p>\n<h2>事件循环</h2>\n<p>我们终于到达了臭名昭着的 Event Loop。这是一个持续运行的进程,用于检查调用堆栈是否为空。想象它就像一个时钟,每次滴答时都会查看调用堆栈,如果它是空的,它会查看事件队列。如果事件队列中有某些东西正在等待,则会将其移动到调用堆栈。如果没有,那么没有任何反应。</p>\n<p>这里有几个有趣的案例。您认为以下代码将以什么顺序运行?</p>\n<pre><code class=\"language-javascript\">setTimeout(() => console.log('first'), 0)\nconsole.log('second')\n</code></pre>\n<p>有些人认为因为设置超时被调用0(零),它应该立即运行。事实上,在这个具体的例子中,你会看到 “second” 在 “first” 之前打印出来。JavaScript 看到了 setTimeout 并说“好吧,我应该将它添加到我的事件表并继续执行”。然后它将通过事件表,事件队列并等待事件循环勾选以便运行。</p>\n<h2>应用</h2>\n<p>事件循环行为的另一个有趣的例子是递归。您是否见过堆栈溢出错误消息?当你进行无限递归时,你有时会得到这个结果但有时你会有大量的递归调用。这里有一个简单而简单的解决方案,它将允许您保留代码结构并仍然进行大量调用——在 setTimeout 中包装递归调用。与直接调用 recursion() (设想这是您的方法的名称)相反,您可以调用 setTimeout(() => recursion(), 0).这将避免堆栈溢出,因为调用将经过事件表和队列,而不是直接堆积在堆栈上。尽量避免使用这种方法,但这是 JavaScript 行为的一个很好的例子。</p>\n<h2>结束</h2>\n<p>还有更多的事情在发生,这只是循环和它周围的一切的基本解释。尽管我希望尽可能地保持简单,但如果不深入整个过程,就无法解释事件循环做了什么。需要注意的是,这个解释是在 V8 JavaScript 引擎的上下文中进行的。它是 Chrome 背后的引擎,也用于 Node。</p>\n","author":"lanqy"},{"title":"F# 备忘单","created":"2018/07/19","link":"2018/07/19/fsharp-cheatsheet","description":"F# 备忘单","content":"<h1>F# 备忘单</h1>\n<p>译自:https://dungpa.github.io/fsharp-cheatsheet/</p>\n<p>这张备忘单浏览了一些 F# 3.0 的常用语法。如果您有任何意见,更正或建议添加内容,请打开问题或发送拉取请求到https://github.com/dungpa/fsharp-cheatsheet。</p>\n<h2>注释</h2>\n<p>块注释放在 (* 和 *) 之间。行注释从 // 开始并一直持续到行结束。</p>\n<pre><code class=\"language-text\">(* 这是块注释 *)\n\n// 这是行注释\n</code></pre>\n<p>在 /// 允许我们使用 XML 标记生成文档后,XML 文档注释就会出现。</p>\n<pre><code class=\"language-fsharp\">/// `let`关键字定义了一个(不可变的)值\nlet result = 1 + 1 = 2\n</code></pre>\n<h2>字符串</h2>\n<p>F# 字符串类型是 System.String 类型的别名。</p>\n<pre><code class=\"language-fsharp\">/// 使用字符串连接创建字符串\nlet hello = "Hello" + "World"\n</code></pre>\n<p>使用以 @ 符号开头的逐字字符串以避免转义控制字符(除了通过 "" 转义 ")。</p>\n<pre><code class=\"language-fsharp\">let verbatimXml = @"<book title="" Paradise Lost "">"\n</code></pre>\n<p>我们甚至不必转义 " 使用三引号字符串。</p>\n<pre><code class=\"language-fsharp\">let tripleXml = """<book title="Paradise Lost">"""\n</code></pre>\n<p>反斜杠字符串通过去除前导空格来缩进字符串内容。</p>\n<pre><code class=\"language-fsharp\">let poem = \n "The lesser world was daubed\\n\\\n By a colorist of modest skill\\n\\\n A master limned you in the finest inks\\n\\\n And with a fresh-cut quill."\n</code></pre>\n<h2>基本类型和文字</h2>\n<p>大多数数字类型都有相关的后缀,例如,uy 表示无符号 8 位整数,L 表示有符号 64 位整数。</p>\n<pre><code class=\"language-fsharp\">let b, i, l = 86uy, 86, 86L\n\nval b : byte = 86uy\nval i : int = 86\nval l : int64 = 86L\n</code></pre>\n<p>其他常见的例子是 32 位浮点数的 F 或 f ,小数的 M 或 m ,大整数的 I。</p>\n<pre><code class=\"language-fsharp\">let s, f, d, bi = 4.14F, 4.14, 0.7833M, 9999I\n\nval s : float32 = 4.14f\nval f : float = 4.14\nval d : decimal = 0.7833M\nval bi : System.Numerics.BigInteger = 9999\n</code></pre>\n<p>有关完整参考,请<a href=\"http://msdn.microsoft.com/en-us/library/dd233193.aspx\">参阅文字(MSDN)</a>。</p>\n<h2>函数</h2>\n<p>let 关键字还定义了命名函数。</p>\n<pre><code class=\"language-fsharp\">let negate x = x * -1\nlet square x = x * x\nlet print x = printfn "The number is: %d" x\n\nlet squareNegateThenPrint x = \n print (negate (square x))\n</code></pre>\n<h3>管道和组合操作符</h3>\n<p>管道运算符 |> 用于将函数和参数链接在一起。双重反引号标识符便于提高可读性,尤其是在单元测试中:</p>\n<pre><code class=\"language-fsharp\">let ``square, negate, then print`` x = \n x |> square |> negate |> print\n</code></pre>\n<p>此运算符在使用前通过提供类型信息来协助 F# 类型检查器是必不可少的:</p>\n<pre><code class=\"language-fsharp\">let sumOfLengths (xs : string []) =\n xs\n |> Array.map (fun s -> s.Length)\n |> Array.sum\n</code></pre>\n<p>组合运算符 >> 用于组合函数:</p>\n<pre><code class=\"language-fsharp\">let squareNegateThenPrint' = \n square >> negate >> print\n</code></pre>\n<h3>递归函数</h3>\n<p>rec 关键字与 let 关键字一起用于定义递归函数:</p>\n<pre><code class=\"language-fsharp\">let rec fact x = \n if x < 1 then 1\n else x * fact (x - 1)\n</code></pre>\n<p>相互递归函数(那些相互调用的函数)由 and 关键字表示:</p>\n<pre><code class=\"language-fsharp\">let rec even x = \n if x = 0 then true\n else odd (x - 1)\nend odd x = \n if x = 1 then true\n else even (x - 1)\n</code></pre>\n<h2>模式匹配</h2>\n<p>通常通过 match 关键字来促进模式匹配。</p>\n<pre><code class=\"language-fsharp\">let rec fib n = \n match n with\n | 0 -> 0\n | 1 -> 1\n | _ -> fib (n - 1) + fib (n - 2)\n</code></pre>\n<p>为了匹配复杂的输入,可以使用 when 在模式上创建过滤器或防护:</p>\n<pre><code class=\"language-fsharp\">let sign x = \n match x with\n | 0 -> 0\n | x when x < 0 -> -1\n | x -> 1\n</code></pre>\n<p>模式匹配可以直接在参数上完成:</p>\n<pre><code class=\"language-fsharp\">let fst' (x, _) = x\n</code></pre>\n<p>或通过 function 关键字隐式:</p>\n<pre><code class=\"language-fsharp\">/// 类似于 `fib`;使用 `function` 进行模式匹配\nlet rec fib' = function\n | 0 -> 0\n | 1 -> 1\n | n -> fib' (n - 1) + fib' (n - 2)\n</code></pre>\n<p>有关更完整的参考,请访问<a href=\"http://msdn.microsoft.com/en-us/library/dd547125.aspx\">模式匹配(MSDN)</a>。</p>\n<h2>集合</h2>\n<h3>列表</h3>\n<p>列表是相同类型的元素的不可变集合。</p>\n<pre><code class=\"language-fsharp\">// 列表使用方括号和`;`分隔符\nlet list1 = ["a";"b"]\n// :: 用于将元素加在列表开头\nlet list2 = "c" :: list1\n// @ 用于连接列表\nlet list3 = list1 @ list2\n\n// 使用( :: )运算符在列表上递归\nlet rec sum list = \n match list with\n | [] -> 0\n | x :: xs -> x + sum xs\n</code></pre>\n<h3>数组</h3>\n<p>数组是连续数据元素的固定大小,从零开始,可变的集合。</p>\n<pre><code class=\"language-fsharp\">// 数组使用方括号和条形\nlet array1 = [|"a";"c"|]\n// 使用点进行索引访问\nlet first = array1.[0]\n</code></pre>\n<h3>序列</h3>\n<p>序列是相同类型的逻辑系列元素。仅根据需要计算各个序列元素,因此在不使用所有元素的情况下,序列可以提供比列表更好的性能。</p>\n<pre><code class=\"language-fsharp\">// 序列可以使用 yield 并包含子序列\nlet seq1 = \n seq {\n // “yield”增加一个元素\n yield 1\n yield 2\n\n // "yield!" 添加一个完整的子序列\n yield! [5..10]\n }\n\n</code></pre>\n<h3>集合上的高阶函数</h3>\n<p>同样的清单 [1; 3; 5; 7; 9] 或数组 [| 1; 3; 5; 7; 9 |] 可以以各种方式生成。</p>\n<ul>\n<li>使用范围运算符..</li>\n</ul>\n<pre><code class=\"language-fsharp\">let xs = [1..2..9]\n</code></pre>\n<ul>\n<li>使用列表或数组理解</li>\n</ul>\n<pre><code class=\"language-fsharp\">let yx = [| for i in 0..4 -> 2 * i + 1 |]\n</code></pre>\n<ul>\n<li>使用 init 函数</li>\n</ul>\n<pre><code class=\"language-fsharp\">let zs = List.init 5 (fun i -> 2 * i + 1)\n</code></pre>\n<p>列表和数组具有用于操作的全面的高阶函数集。</p>\n<ul>\n<li>fold 从列表(或数组)的左侧开始,foldBack 的方向相反</li>\n</ul>\n<pre><code class=\"language-fsharp\">let xs' = Array.fold (fun str n -> sprintf "%s,%i" str n) "" [|0..9|]\n</code></pre>\n<ul>\n<li>reduce 不需要初始累加器</li>\n</ul>\n<pre><code class=\"language-fsharp\">let last xs = List.reduce (fun acc x -> x) xs\n</code></pre>\n<ul>\n<li>map 转换列表(或数组)的每个元素</li>\n</ul>\n<pre><code class=\"language-fsharp\">let ys' = Array.map (fun x -> x * x) [|0..9|]\n</code></pre>\n<ul>\n<li>iter 列表并产生副作用</li>\n</ul>\n<pre><code class=\"language-fsharp\">let _ = List.iter (printfn "%i") [0..9]\n</code></pre>\n<p>所有这些操作也可用于序列。序列的附加好处是对实现 IEnumerable<'T> 的所有集合的懒惰和统一处理。</p>\n<pre><code class=\"language-fsharp\">let zs' = \n seq {\n for i in 0..9 do\n printfn "Adding %d" i\n yield i\n }\n</code></pre>\n<h2>元组和记录</h2>\n<p>元组是一组未命名但有序的值,可能是不同类型的:</p>\n<pre><code class=\"language-fsharp\">// 元组结构\nlet x = (1, "Hello")\n\n// 三重\nlet y = ("one", "two", "three")\n\n// 元组解构/模式\nlet (a', b') = x\n</code></pre>\n<p>可以使用 fst,snd 或模式匹配获得元组的第一个和第二个元素:</p>\n<pre><code class=\"language-fsharp\">let c' = fst (1, 2)\nlet d' = snd (1, 2)\n\nlet print' tuple = \n match tuple with\n | (a, b) -> printfn "Pair %A %A" a b\n</code></pre>\n<p>记录表示命名值的简单聚合,可选择包含成员:</p>\n<pre><code class=\"language-fsharp\">// 声明记录类型\ntype Person = { Name: string; Age: int }\n\n// 通过记录表达式创建值\nlet paul = { Name = "Paul"; Age = 28}\n\n// '复制并更新'记录表达式\nlet paulsTwin = {paul with Name = "Jim"}\n\n</code></pre>\n<p>记录可以使用属性和方法进行扩充:</p>\n<pre><code class=\"language-fsharp\">type Person with\n member x.Info = (x.Name, x.Age)\n</code></pre>\n<p>记录本质上是带有额外顶部的密封类:默认不变性,结构相等和模式匹配支持。</p>\n<pre><code class=\"language-fsharp\">let isPaul person = \n match person with\n | { Name = "Paul" } -> true\n | _ -> false\n</code></pre>\n<h2>识别联合</h2>\n<p>识别联合(DU)为可以是多个命名案例之一的值提供支持,每个案例可能具有不同的值和类型。</p>\n<pre><code class=\"language-fsharp\">type Tree<'T> =\n | Node of Tree<'T> * 'T * Tree<'T>\n | Leaf\n\nlet rec depth = function \n | Node(l, _, r) -> 1 + max (depth l) (depth r)\n | Leaf -> 0\n</code></pre>\n<p>F# Core 有一些用于错误处理的内置区分联合,例如 <a href=\"http://msdn.microsoft.com/en-us/library/dd233245.aspx\">Option</a> 和 <a href=\"http://msdn.microsoft.com/en-us/library/ee353439.aspx\">Choice</a> 。</p>\n<pre><code class=\"language-fsharp\">let optionPatternMatch input = \n match input with\n | Some i -> printfn "input is an int=%d" i\n | None -> printfn "input is missing"\n</code></pre>\n<p>单例区分联合通常用于创建具有模式匹配支持的类型安全抽象:</p>\n<pre><code class=\"language-fsharp\">type OrderId = Order of string\n\n// 创建DU值\nlet orderId = Order "12"\n\n// 使用模式匹配来解构单个案例 DU\nlet (Order id) = orderId\n</code></pre>\n<h2>异常</h2>\n<p>failwith 函数抛出异常类型 Exception 。</p>\n<pre><code class=\"language-fsharp\">let divideFailwith x y = \n if y = 0 then\n failwith "Divisor cannot be zero"\n else x / y\n</code></pre>\n<p>异常处理通过 try / with 表达式完成。</p>\n<pre><code class=\"language-fsharp\">let divide x y =\n try\n Some (x / y)\n with :? System.DivideByZeroException ->\n printfn "Division by zero!"\n None\n</code></pre>\n<p>try / finally 表达式使您可以执行清理代码,即使代码块引发异常也是如此。这是一个定义自定义异常的示例。</p>\n<pre><code class=\"language-fsharp\">exception InnerError of string\nexception OuterError of string\n\nlet handleErrors x y =\n try\n try\n if x = y then raise (InnerError("inner"))\n else raise (OuterError("outer"))\n with\n printfn "Error1 %s" str\n finally\n printfn "Always print this."\n</code></pre>\n<h2>类和继承</h2>\n<p>此示例是一个基本类,包含(1)本地 let 绑定,(2)属性,(3)方法和(4)静态成员。</p>\n<pre><code class=\"language-fsharp\">type Vector(x: float, y: float) =\n let mag = sqrt(x * x + y * y) // (1)\n member this.X = x // (2)\n member this.Y = y\n member this.Mag = mag\n member this.Scale(s) = // (3)\n Vector(x * s, y * s)\n static member (+) (a: Vector, b: Vector) = // (4)\n Vector(x.X + b.X, a.Y + b.Y)\n</code></pre>\n<p>从派生类调用基类。</p>\n<pre><code class=\"language-fsharp\">type Animal() =\n member __.Reset() = ()\n\ntype Dog() = \n inherit Animal()\n member __.Run() =\n base.Reset()\n</code></pre>\n<p>上传表示为 :> 运算符。</p>\n<pre><code class=\"language-fsharp\">let dog = Dog()\nlet animal = dog :> Animal\n</code></pre>\n<p>如果转换在运行时未成功,则动态向下转换( :?> )可能会抛出 InvalidCastException。</p>\n<pre><code class=\"language-fsharp\">let shouldBeDog = animal :?> Dog\n</code></pre>\n<h2>接口和对象表达式</h2>\n<p>声明 IVector 接口并在 Vector' 中实现它。</p>\n<pre><code class=\"language-fsharp\">type IVector\n abstract Scale : float -> IVector\n\ntype Vector'(x, y) = \n interface IVector with\n member __.Scale(s) =\n Vector'(x * s, y * s) :> IVector\n member __.X = x\n member __.Y = y\n</code></pre>\n<p>实现接口的另一种方法是使用对象表达式。</p>\n<pre><code class=\"language-fsharp\">type ICustomer =\n abstract Name : string\n abstract Age : int\n\nlet createCustomer name age = \n { new ICustomer with\n member __.Name = name\n member __.Age = age }\n</code></pre>\n<h2>活动模式</h2>\n<p>完整的活动模式:</p>\n<pre><code class=\"language-fsharp\">let (|Even|Odd|) i = \n if i % 2 = 0 then Even else Odd\n\nlet testNumber i = \n match i with\n | Even -> printfn "%d is even" i\n | Odd -> printfn "%d is odd" i\n</code></pre>\n<p>参数化活动模式:</p>\n<pre><code class=\"language-fsharp\">let (|DivisibleBy|_|) by n =\n if n % by = 0 then Some DivisibleBy else None\n\nlet fizzBuzz = function\n | DivisibleBy 3 & DivisibleBy 5 -> "FizzBuzz"\n | DivisibleBy 3 -> "Fizz"\n | DivisibleBy 5 -> "Buzz"\n | i -> string i\n</code></pre>\n<p>部分活动模式共享参数化模式的语法,但它们的主动识别器只接受一个参数。</p>\n<h2>编译器指令</h2>\n<p>将另一个 F# 源文件加载到 FSI 中。</p>\n<pre><code class=\"language-fsharp\">#load "../lib/StringParsing.fs"\n</code></pre>\n<p>引用.NET程序集(建议使用/符号以实现Mono兼容性)。</p>\n<pre><code class=\"language-fsharp\">#r "../lib/FSharp.Markdown.dll"\n</code></pre>\n<p>在程序集搜索路径中包含目录。</p>\n<pre><code class=\"language-fsharp\">#I "../lib"\n#r "FSharp.Markdown.dll"\n</code></pre>\n<p>其他重要指令是 FSI(INTERACTIVE)中的条件执行和查询当前目录( <strong>SOURCE_DIRECTORY</strong> )。</p>\n<pre><code class=\"language-fsharp\">#if INTERACTIVE\nlet path = __SOURCE_DIRECTORY__ + "../lib"\n#else\nlet path = "../../../lib"\n#endif\n</code></pre>\n","author":"lanqy"},{"title":"使用 Erlang / OTP 构建您的第一个多用户聊天室应用程序","created":"2018/07/18","link":"2018/07/18/build-your-first-multi-user-chat-room-app-with-erlang-otp","description":"使用 Erlang / OTP 构建您的第一个多用户聊天室应用程序","content":"<h1>使用 Erlang / OTP 构建您的第一个多用户聊天室应用程序</h1>\n<p>译自:https://medium.com/@kansi/chatbus-build-your-first-multi-user-chat-room-app-with-erlang-otp-b55f72064901</p>\n<blockquote>\n<p>在本教程中,我将使用 Erlang / OTP和 ErlBus 构建另一个消息传递系统(稍后我们将讨论它),并在此过程中探索 Erlang 的强大功能!</p>\n</blockquote>\n<p>注意:以下假设假定您对 Erlang / OTP 和 Web 开发有基本的了解。</p>\n<p>本文的目的是构建一个多用户聊天室应用程序,但这个想法听起来太无聊了!所以让我们添加一些创造力并称之为 ChatBus。现在您可能想知道这个名字是如何帮助的。等一下 !让我解释 ChatBus 的内容。我们将聊天室的概念抽象为公共汽车,聊天室中的所有用户都被称为搭便车,因为他们不需要为乘坐这辆公共汽车支付任何费用!</p>\n<p>在我们潜入并开始编码像疯狗之前!我们应该决定 ChatBus 要具备哪些功能。这通常称为软件开发中的需求规范。在实施之前或者很快项目将开始分崩离析时,始终要根据经验确切知道要实施的内容!</p>\n<p>对于我们的多用户聊天系统,我们将具有以下功能:</p>\n<ul>\n<li>允许用户创建新的总线(也称为聊天室)!</li>\n<li>允许用户从一个总线切换到另一个总线! (这里有一些不可能的任务)</li>\n<li>动态更新乘坐所选巴士的可用巴士和搭便车的列表。</li>\n</ul>\n<p>这完成了基本规范。现在我们继续决定实现这个项目所需的工具和库。</p>\n<h2>工具和库</h2>\n<ul>\n<li><a href=\"https://www.polymer-project.org/1.0/\">Polymer</a>:对于这个应用程序,我使用 <a href=\"https://www.polymer-project.org/1.0/\">Polymer</a> 来开发客户端,但是你可以选择在Bootstrap中实现它或者你喜欢什么,因为讨论客户端不在讨论的范围内。</li>\n<li><a href=\"https://github.com/cabol/erlbus\">ErlBus</a>:在 Erlang 中传递消息非常简单,但是这个项目更进一步,使它更容易。它允许用户创建频道并为其订阅侦听器进程。这就是我们实现 ChatBus 所需要的一切!下面是一个快速示例,以显示它的力量:</li>\n</ul>\n<pre><code class=\"language-erlang\">\n% 创建匿名函数以执行监听频道的操作\n% 请注意,该函数有两个参数,第一个是self\n% 第一个是自解释的,第二个是上下文\n% (ctx) 在生成这个函数时可以设置。\n\nF = fun({Channel, Msg}, Ctx) ->\n io:format("[Pid: ~p] [Channel: ~p] [Msg: ~p] [Ctx: ~p]~n", [self(), Channel, Msg, Ctx])\n end.\n% 将此函数派生为进程。\nMH2 = ebus_handler:new(F, {my_ctx, <<"MH2">>}).\n\n% 将生成的进程订阅到通道(ch1)\n% 注意:如果通道不存在,则创建该通道。\nebus:sub(ch1, [MH2]).\n\n% 让我们发一条消息给 'ch1'\nebus:pub(ch1, "Hello!").\n[Pid: <0.52.0>] [Channel: ch1] [Msg: "Hello!"] [Ctx: {my_ctx, <<MH2>>}]\n\n\n</code></pre>\n<p>我希望这足以说明如何使用 ChatBus,但如果不能,你可以在他们的 <a href=\"https://github.com/cabol/erlbus\">GitHub 项目</a> 页面看一些例子。</p>\n<ul>\n<li><a href=\"https://github.com/ninenines/cowboy\">Cowboy</a>:我们将使用 Cowboy 来实现我们的网络服务器。</li>\n<li><a href=\"https://www.rebar3.org/docs/getting-started\">Rebar3</a>:我们将使用rebar3作为此项目的构建和发布工具。</li>\n</ul>\n<p>...</p>\n<h2>实现</h2>\n<p>ChatBus 的项目源码位于<a href=\"https://github.com/beamX/chatBus/tree/feat-chatbus-client\">此处</a>。</p>\n<p>首先,我们将使用rebar3生成模板项目:</p>\n<pre><code class=\"language-text\">$ rebar3 new release chatbus\n</code></pre>\n<p>这将生成一个模板项目。执行以确保项目正常工作:</p>\n<pre><code class=\"language-text\">$ rebar3 compile\n$ rebar3 release\n$ ./_build/default/rel/chatbus/bin/chatbus console\n</code></pre>\n<p>最后一个命令应该打开 erlang shell。该项目应如下所示</p>\n<pre><code class=\"language-text\">├── apps\n│ └── chatbus\n│ └── src\n│ ├── chatbus_app.erl\n│ ├── chatbus.app.src\n│ └── chatbus_sup.erl\n├── _build\n│ └── default\n│ ├── lib\n│ └── rel\n├── config\n│ ├── sys.config\n│ └── vm.args\n├── LICENSE\n├── Makefile\n├── README.md\n├── rebar.config\n└── rebar.loc\n</code></pre>\n<p>首先,我们添加项目所需的依赖项,将 rebar.config 更改为像<a href=\"https://github.com/beamX/chatBus/blob/feat-chatbus-client/rebar.config\">这样</a>。我们在那里有以下依赖:</p>\n<ul>\n<li>cowboy:用于创建允许客户端连接的 Web 服务器。</li>\n<li>lager:记录错误和东西。</li>\n<li>mochiweb:解析 json。</li>\n<li>erlbus:用于创建频道和传递消息。</li>\n<li>sync:用于开发目的。您可以选择排除它,但您必须相应地更改 chatbus.app.src。</li>\n</ul>\n<p>现在我们使用 cowboy 创建我们的Web服务器。在你的chatbus_app.erl文件中,启动函数如下所示:</p>\n<pre><code class=\"language-erlang\">start(_StartType, _StartArgs) ->\n\n ok = application:ensure_started(ebus),\n \n Dispatch = cowboy_router:compile(\n [{'_', [\n {"/", cowboy_static, {priv_file, chatbus, "index.html"}},\n {"/ws", ws_handler, []},\n {"/[...]", cowboy_static, {priv_dir, chatbus, "./"}}]}]),\n {ok, _} = cowboy:start_http(http, 100, [{port, 9090}], [{env, [{dispatch, Dispatch}]}]),\n 'chatbus_sup':start_link().\n</code></pre>\n<p>这里我们添加了一些路由。我们的应用程序将是一个单页面的 Web 应用程序,因此我们定义静态文件 “index.html” 的位置,任何点击 “/” 的人都将获得此 “index.html” 和其他静态文件。接下来我们指定一个路由 “/ws”,我们将用它来建立一个 websocket 连接,对该端点的任何请求都将由 ws_handler.erl 模块处理。加载静态文件后,将使用 javascript 调用此 websocket 端点。最后,我们使用 “[...]” 定义所有静态文件的位置。</p>\n<p>现在我们将创建一个 bus_listener.erl 模块,它将订阅一个频道,收听和广播消息,</p>\n<pre><code class=\"language-erlang\">-module(bus_listener).\n-behaviour(ebus_handler).\n\n%% API\n-export([handle_msg/2]).\n\nhandle_msg({_Channel, {Sender, Type, Msg}}, User) ->\n if\n Sender =:= User -> ok;\n true -> User ! {Type, Msg}\n end\n</code></pre>\n<p>如果你通过<a href=\"https://github.com/cabol/erlbus#my_handlererl\">这里</a>的文档,上面的模块应该很容易理解,但不过我将解释这段代码,但稍后。</p>\n<p>现在我们将创建一个 websocket 处理程序,它将在调用端点 “/ws” 时创建 websocket 连接。回想一下,我们在路由中将处理程序名称指定为 “ws_handler”,因此我们使用以下内容创建名为 “<a href=\"https://github.com/beamX/chatBus/blob/feat-chatbus-client/apps/chatbus/src/ws_handler.erl\">ws_handler.erl</a>” 的模块,</p>\n<pre><code class=\"language-erlang\">\n-module(ws_handler).\n-export([init/2]).\n-export([websocket_handle/3]).\n-export([websocket_info/3]).\n-export([websocket_terminate/3]).\n-export([send_active_channels/1]).\ninit(Req, _Opts) ->\n io:format("connected !~n"),\n \n %% subscribe to default bus\n BusFd = ebus_handler:new(bus_listener, self()),\n ok = ebus:sub(default, [BusFd]),\n %% send subscribes bus name\n auto_send(<<"bus_subscribed">>, default),\n \n {cowboy_websocket, Req, #{bus => default\n ,bus_fd => BusFd\n ,hitchhicker => false}}.\nwebsocket_handle({text, Msg}, Req, #{bus := BusName\n ,bus_fd := BusFd\n ,hitchhicker := Hitchhicker} = \n State) ->\n {ok, {Type, Msg1}} = parse_message(Msg),\n case Type of\n <<"chat">> ->\n ok = ebus:pub(BusName, {self(), Type, Msg1}),\n {ok, Req, State};\n <<"bus_list">> ->\n {ok, List} = bus_manager:bus_list(),\n {ok, Reply} = encode_message(<<"bus_list">>, List),\n {reply, {text, Reply}, Req, State};\n <<"hitchhicker_list">> ->\n {ok, List} = bus_manager:get_hitchhickers(BusName),\n {ok, Reply} = encode_message(<<"hitchhicker_list">>, List),\n {reply, {text, Reply}, Req, State};\n <<"bus_subscribed">> ->\n BusName2 = erlang:binary_to_atom(Msg1, utf8),\n ok = ebus:unsub(BusName, BusFd),\n ok = ebus:sub(BusName2, [BusFd]),\n {ok, Reply} = encode_message(<<"bus_subscribed">>, BusName2),\n {reply, {text, Reply}, Req, State#{bus => BusName2}};\n <<"add_bus">> ->\n BusNewName = erlang:binary_to_atom(Msg1, utf8),\n ok = ebus:unsub(BusName, BusFd),\n ok = ebus:sub(BusNewName, [BusFd]),\n %% signal bus_manager to send all client list of \n %% active buses\n bus_manager:check_bus(BusName),\n %% send message to client updating his bus\n {ok, Reply} = encode_message(<<"bus_subscribed">>, \n BusNewName),\n {reply, {text, Reply}, Req, State#{bus => BusNewName}};\n <<"username">> ->\n %% check if username is assignable\n case bus_manager:store_username(BusName, Msg1) of\n {ok, error} ->\n {ok, Reply} = encode_message( <<"username_error">>, error),\n {reply, {text, Reply}, Req, State};\n _ ->\n {ok, List} = bus_manager:get_hitchhickers(BusName),\n ok = ebus:pub(BusName, {none,\n <<"hitchhicker_list">>, List}),\n {ok, Reply} = encode_message(<<"username">>, \n Msg1),\n {reply, {text, Reply}, Req, \n State#{hitchhicker => Msg1}}\n end;\n <<"terminate">> ->\n bus_manager:remove_hitchhicker(Hitchhicker),\n ebus:unsub(BusName, BusFd),\n ebus_handler:delete(BusFd),\n {ok, List} = bus_manager:get_hitchhickers(BusName),\n ok = ebus:pub(BusName, {none, <<"hitchhicker_list">>, \n List}),\n {shutdown, Req, State};\n _ ->\n io:format("unknown message type ~p~n", [Type]),\n {ok, Req, State}\n end;\nwebsocket_handle(Data, Req, State) ->\n io:format("received ~p~n", [Data]),\n {ok, Req, State}.\n%% handle erlang messages\nwebsocket_info({Type, Msg}, Req, State) ->\n {ok, Reply} = encode_message(Type, Msg),\n {reply, {text, Reply}, Req, State};\nwebsocket_info(Info, Req, State) ->\n io:format("[ws_info]: unknown message ~p~n", [Info]),\n {ok, Req, State}.\nwebsocket_terminate(_Reason, _Req, _State) ->\n io:format("[ws_info]: terminating websocket ~n"),\n ok.\n%% ===============================================================\n%% other exports\n%% ===============================================================\nsend_active_channels(Channels) ->\n lists:map(fun(Bus) ->\n ok = ebus:pub(Bus, {none, <<"bus_list">>, Channels})\n end, Channels).\n%% ===============================================================\n%% internal functions\n%% ===============================================================\nauto_send(Mtype, Msg) ->\n %% send subscribes bus name\n timer:send_after(10, self(), {Mtype, Msg}).\nparse_message(Msg) ->\n {struct, Msg1} = mochijson2:decode(Msg),\n {<<"type">>, Type} = lists:keyfind(<<"type">>, 1, Msg1),\n {<<"msg">>, Content} = lists:keyfind(<<"msg">>, 1, Msg1),\n {ok, {Type, Content}}.\nencode_message(Type, Msg) ->\n Reply = {[{type, Type}, {msg, Msg}]},\n {ok, iolist_to_binary(mochijson2:encode(Reply))}.\n\n</code></pre>\n<p>当客户端调用 “/ws” 端点时,将通过调用 init/2 函数创建 websocket 。在这个功能中发生了两件重要的事情</p>\n<pre><code class=\"language-erlang\">\nBusFd = ebus_handler:new(bus_listener, self()),\nok = ebus:sub(default, [BusFd]),\n\n</code></pre>\n<p>在第一行中,我们使用 bus_listener 模块生成一个新的 ebus 处理程序进程。然后,此处理程序进程订阅名为 “default” 的通道。</p>\n<pre><code class=\"language-erlang\">handle_msg({_Channel, {Sender, Type, Msg}}, User) ->\n if\n Sender =:= User -> ok;\n true -> User ! {Type, Msg}\n end\n</code></pre>\n<p>handle_msg/2 的第一个参数是包含通道名称和消息的元组,第二个参数是我们在创建此过程时传递给 ebus_handler:new/2 的参数,即 self()。接下来,websocket_handle/3 函数处理来自客户端的数据。人们可以很容易地注意到客户端发送包含消息类型和消息的 json 对象。根据消息类型,我们执行不同的操作,例如。消息类型 “chat” 用于在通道上发送消息,该消息使用 ebus:pub/2 完成,还有其他消息类型执行不同的功能,如更改用户名,添加新聊天室,发送连接用户列表等。</p>\n<p>...</p>\n<p>通过上面的讨论,我试图介绍处理消息传递部分的 ChatBus 的基本代码库。我鼓励读者探索代码库并尝试使用它。</p>\n<p>总之,使用 Erlang 构建聊天系统非常容易 :)</p>\n","author":"lanqy"},{"title":"OCaml简介第6部分","created":"2018/07/17","link":"2018/07/17/getting_started6","description":"OCaml简介第6部分","content":"<h2>OCaml简介第6部分</h2>\n<p>译自:https://qiita.com/zenwerk/items/97d370d457008d8f01de</p>\n<h3>标记的参数</h3>\n<h4>〜标签名称:</h4>\n<ul>\n<li>你可以命名这个参数。</li>\n<li>如果你给一个标签名称,你可以改变你喜欢的参数的顺序。</li>\n</ul>\n<pre><code class=\"language-ocaml\">(* 使用带标签的参数定义函数 *)\n\n# let rec range ~first: a ~last: b = \n if a > b then []\n else a :: range ~first: (a + 1) ~last: b;;\n \n(* 函数类型上的标签类型 *)\nval range : first:int -> last:int -> int list = <fun>\n\n(* 指定标签名称函数应用程序 *)\n\n# range ~first: 1 ~last: 10;;\n\n- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] \n\n# range ~last: 10 ~first: 1;;\n\n- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] \n\n(* 除非指定了标签名称,否则按标签名称定义应用 *) \n\n# range 1 10;;\n- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]\n\n# range 10 1;;\n- : int list = []\n</code></pre>\n<ul>\n<li>〜hoge:○○ ○○ 是可选的</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let rec range ~first ~last = \n if first > last then []\n else first :: range (first + 1) last;;\nval range : first:int -> last:int -> int list = <fun>\n</code></pre>\n<h4>可选参数</h4>\n<p>?标签名称:(pattern =表达式)</p>\n<p>与Python的家伙相同的功能。</p>\n<pre><code class=\"language-ocaml\">(* 默认值1给出步骤值 *)\n\n# let rec range ?(step = 1) a b = \n if a > b then []\n else a :: range ~step (a + step) b;;\nval range : ?step:int -> int -> int -> int list = <fun>\n\n(* 函数应用 *)\n# range 1 10;;\n\n- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]\n\n(* 因为它在调用时指定了标签 *)\n\n# range 1 10 ~step:2;;\n- : int list = [1; 3; 5; 7; 9]\n# range 1 ~step:3 10;;\n- : int list = [1; 4; 7; 10]\n\n# range 2 1 10;;\nError: The function applied to this argument has type ?step:int -> int list\nThis argument cannot be applied without label\n</code></pre>\n<h4>关于可选参数的说明</h4>\n<h5>在选项参数后面准备一个无标签的参数</h5>\n<p>如果选项参数在函数的结尾,则它变成了不能省略的选项参数,所以没有任何意义。</p>\n<pre><code class=\"language-ocaml\">(**\n * 我想定义一个函数,返回常量1 ....。\n * 如果选项参数在最后,我会收到警告\n *)\n# let f ?(x=1) = x;;\nWarning 16: this optional argument cannot be erased.\nval f : ?x:int -> int = <fun>\n(* 1函数返回我以为它返回一个函数,接收*选项参数返回正在currying\n *)\n# f;;\n- : ?x:int -> int = <fun>\n\n(* 最后这么糟糕,不要添加无标签的参数 *)\n# let f ?(x=1) () = x;;\nval f : ?x:int -> unit -> int = <fun>\n# f;;\n- : ?x:int -> unit -> int = <fun>\n# f();;\n- : int = 1\n</code></pre>\n<h4>选项参数实体</h4>\n<p>可选参数是用 'a option 实现的</p>\n<p>如果你没有指定默认值,并写入它,尝试执行,你会得到一个错误,如下所示</p>\n<pre><code class=\"language-ocaml\"># let rec range ?step a b =\n if a > b then []\n else a :: range ~step (a+step) b;;\n(* 'a option type error is occurring *)\nError: This expression has type 'a option\n but an expression was expected of type 'a\n The type variable 'a occurs inside 'a option\n</code></pre>\n<p>因此,在这种情况下,None 加 'a Some 来模式匹配</p>\n<pre><code class=\"language-ocaml\">\n(* 与选项类型匹配的模式 *)\n\n# let rec range ?step a b =\n let s = match step with None -> 1 | Some s -> s in\n if a > b then [] else a :: range (a + s) b;;\nval range : ?step:int -> int -> int -> int list = <fun>\n\n# range 1 10;;\n- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]\n</code></pre>\n<h4>多态变种</h4>\n<h5>`Constructor</h5>\n<p>一种机制,可以为多种变体类型使用通用的构造函数。</p>\n<p>消除 “1构造函数<=> 1变体” 的限制。</p>\n<pre><code class=\"language-ocaml\"># `Hoge;;\n- : [> `Hoge ] = `Hoge\n# `Hoge 2;;\n- : [> `Hoge of int ] = `Hoge 2\n# `Hoge `Fuga;;\n- : [> `Hoge of [> `Fuga ] ] = `Hoge `Fuga\n</code></pre>\n<h4>函数返回多态变种</h4>\n<pre><code class=\"language-ocaml\"># let f b = if b then `Hoge else `Fuga;;\nval f : bool -> [> `Fuga | `Hoge ] = <fun>\n</code></pre>\n<pre><code class=\"language-ocaml\"># let hoge = function\n | `Hoge -> "hoge"\n | `Fuga -> "fuga"\n | `Piyo -> "piyo";;\nval hoge : [< `Fuga | `Hoge | `Piyo ] -> string = <fun>\n</code></pre>\n<h4>多态变种的类型方案</h4>\n<p>[> ...] , [<...] 是类型方案 (同 `a)</p>\n<p>[> ..] 和 [< ..] 它被视为有限制的类型变量。</p>\n<p>在 [> ...] 的情况下\n[> 可以解释为 “包含多态变种” 或更高。</p>\n<pre><code class=\"language-ocaml\">(* \n * 可以接受任何东西的多相变体列表\n *)\n# let a : [>] list = [`Fuga; `Piyo];;\nval a : [> `Fuga | `Piyo ] list = [`Fuga; `Piyo]\n\n# a @ [`Asdf];;\n- : [> `Asdf | `Fuga | `Piyo ] list = [`Fuga; `Piyo; `Asdf]\n</code></pre>\n<p>在 [<...] 中</p>\n<p>[< 可以被解释为“在所包含的多相变体类型中”。</p>\n<pre><code class=\"language-ocaml\">(* `Hoge,`within Fuga *)\n# let f = function\n | `Hoge -> "hoge"\n | `Fuga -> "fuga";;\nval f : [< `Fuga | `Hoge ] -> string = <fun>\n\n(* `Hoge, type within Fuga *)\n# type type_A = [`Hoge | `Fuga];;\ntype type_A = [ `Fuga | `Hoge ]\n# let a:type_A = `Hoge;;\nval a : type_A = `Hoge\n# f a;;\n- : string = "hoge"\n\n(* `Hoge,`Fuga or more types *)\n# type type_B = [`Hoge | `Fuga | `Piyo];;\ntype type_B = [ `Fuga | `Hoge | `Piyo ]\n\n(* `I'm Hoge type_B is `Hoge, `Fuga or more types *)\n# let b:type_B = `Hoge;;\nval b : type_B = `Hoge\n# f b;; (* type error *)\nError: This expression has type type_B but an expression was expected of type\n [< `Fuga | `Hoge ]\n The second variant type does not allow tag(s) `Piyo\n</code></pre>\n<h4>多态变体类型的定义</h4>\n<p>[> ..] [<..] 无法使用的模式</p>\n<p>type hoge = [<code>Hoge |</code>Fuga |...]</p>\n<p>声明一个类型变量 [> ..] 和 [<..] 定义一个模式</p>\n<p>type 'a hoge = [> `Hoge ...] as' a</p>\n<pre><code class=\"language-ocaml\"># type hoge = [`Hoge | `Fuga];;\ntype hoge = [ `Fuga | `Hoge ]\n\n# type 'a hoge = [> `Hoge] as 'a;;\ntype 'a hoge = 'a constraint 'a = [> `Hoge ]\n\n# type 'a hoge = [< `Hoge] as 'a;;\ntype 'a hoge = 'a constraint 'a = [< `Hoge ]\n\n(* 类型定义可以重用 *)\n# type hoge' = [hoge | `Piyo];;\ntype hoge' = [ `Fuga | `Hoge | `Piyo ]\n</code></pre>\n<h4>递归定义多相变量</h4>\n<h5>type 'a mylist = Nil | Cons of 'a * 'a mylist 我们将其定义为多相变量</h5>\n<ul>\n<li>首先按顺序定义它,并检查Valiant类型发生了什么</li>\n</ul>\n<pre><code class=\"language-ocaml\">(*通过定义来查看价格类型的变化*)\n# let l1 = `Nil;;\nval l1 : [> `Nil ] = `Nil\n# let l2 = `Cons(1, `Nil);;\nval l2 : [> `Cons of int * [> `Nil ] ] = `Cons (1, `Nil)\n\n(*你不能获得泛型类型定义*)\n# let l3 = `Cons(2, `Cons(1, `Nil));;\nval l3 : [> `Cons of int * [> `Cons of int * [> `Nil ] ] ] =\n `Cons (2, `Cons (1, `Nil))\n</code></pre>\n<ul>\n<li>如果你写一个执行任意列表处理的函数,你应该能够获得列表的通用术语类型定义...</li>\n</ul>\n<pre><code class=\"language-ocaml\">(* 递归Variant类型的类型定义是...? *)\n# let rec length = function\n | `Nil -> 0\n | `Cons (a, l) -> 1 + length l;;\n(*\n* [...]因为'a已被赋予变体类型定义的别名\n*这个别名'a出现在[...]中,原来是一个递归的定义\n*'由b给出的类型变量是一个也可以看出哪个是固定的类型\n*)\n\nval length : ([< `Cons of 'b * 'a | `Nil ] as 'a) -> int = <fun>\n</code></pre>\n<pre><code class=\"language-ocaml\">(* 函数在列表中选择*的最大值 *)\n# let rec max_list = function\n | `Cons(x, `Nil) -> x\n | `Cons(x, `Cons(y, l)) ->\n if x < y then max_list (`Cons(y, l)) else max_list (`Cons(x, l));;\n\n(*\n* [.. [..]] 是因为它的形式,可以看出长度大于或等于1\n*)\nval max_list : [ `Cons of 'a * ([< `Cons of 'a * 'b | `Nil ] as 'b) ] -> 'a = <fun>\n</code></pre>\n<h4>注意多相变异型</h4>\n<h5>注意 "&"</h5>\n<p>如果多态变体&出来,它表明,它输入失败</p>\n<p>例如,int&float指示表示int和float,不能实现</p>\n<p>那么,在这种情况下,您应该查看函数定义等</p>\n<pre><code class=\"language-ocaml\"># let f = function `A x -> x+1 | `B -> 2;;\nval f : [< `A of int | `B ] -> int = <fun>\n# let g = function `A x -> int_of_float x+1 | `B -> 2;;\nval g : [< `A of float | `B ] -> int = <fun>\n\n(*\n*返回不正确的类型[int&float]\n* hoge&fuga是一个不可行的类型,所以下面的类型定义是真实的[<`B] - > Int\n*)\n\n# let f_or_g b = if b then f else g;;\nval f_or_g : bool -> [< `A of int & float | `B ] -> int = <fun>\n</code></pre>\n<h4>使用点</h4>\n<p>练习多相变量的正确使用所必需的。</p>\n<p>在很多情况下,应该使用正常的变体来完成。</p>\n<h4>奖励:你有多态记录吗?</h4>\n<p>如果有多相变量,是否还有多态记录(如``hoge:int')? 我想,但没有这样的事情。</p>\n<p>相反,OCaml对象可以提供等效的功能。</p>\n<p>然而,对象没有模式匹配,所以说它用得不多。</p>\n","author":"lanqy"},{"title":"OCaml简介第5部分","created":"2018/07/16","link":"2018/07/16/getting_started5","description":"OCaml简介第5部分","content":"<h2>OCaml简介第5部分</h2>\n<p>译自:https://qiita.com/zenwerk/items/3844df72c5f4afb1782f</p>\n<h3>面向对象的功能</h3>\n<h4>类声明</h4>\n<p>object ... end</p>\n<p>OCaml实例变量的所有成员都是私有的</p>\n<pre><code class=\"language-ocaml\"># class point ini_x ini_y =\n object (self) (* 自身の名前をつける, 自由なので this とかでも良い *)\n val mutable x = 0 (* 实例变量不能从外部访问 *)\n val mutable y = 0\n\n (*\n * 实例方法\n * method 方法名称 参数... = 表达式\n *)\n method set new_x new_y = begin x <- new_x; y <- new_y end\n method private print_x = print_int x (* 私有方法 *)\n\n (* 构造函数 *)\n initializer begin\n x <- ini_x; y <- ini_y\n end\n end;;\n(* 签名 *) \nclass point :\n int ->\n int ->\n object\n val mutable x : int\n val mutable y : int\n method private print_x : unit\n method set : int -> int -> unit\n end\n</code></pre>\n<h4>实例生成</h4>\n<p>new 类名称</p>\n<pre><code class=\"language-ocaml\"># let p = new point;;\nval p : point = <obj>\n</code></pre>\n<h4>调用实例方法</h4>\n<p>实例#方法名称</p>\n<pre><code class=\"language-ocaml\"># p#set 1 2;;\n- : unit = ()\n</code></pre>\n<h4>継承</h4>\n<p>inherit 类名</p>\n<p>以下类可以由 self 本身通过 super 访问父类。</p>\n<pre><code class=\"language-ocaml\">(* 打印坐标 *)\n# class point_with_print x y =\n object (self)\n inherit point x y as super (* 访问父类的名称 *)\n method print = Printf.printf "(%d, %d)\\n" x y\n end;;\nclass point_with_print :\n int ->\n int ->\n object\n val mutable x : int\n val mutable y : int\n method print : unit\n method private print_x : unit\n method set : int -> int -> unit\n end\n\n(* 生成继承类的实例 *)\n# let p = new point_with_print 1 1;;\nval p : point_with_print = <obj>\n# p#print;;\n(1, 1)\n- : unit = ()\n</code></pre>\n<h4>类类型</h4>\n<h5><方法名称:类型 ...></h5>\n<ul>\n<li>对象的类型是方法类型的顺序</li>\n<li>如果方法名称和类型的组合匹配,则认为是相同的对象类型</li>\n<li>要检查一个类的类型,直接使用 let 定义对象而不使用 class。</li>\n</ul>\n<pre><code class=\"language-ocaml\">(* 直接定义对象 *)\nlet obj = \n object (self)\n val mutable x = 0\n val mutable y = 0\n method set new_x new_y = begin x <- new_x; y <- new_y end\n end;;\n\n(* 显示类型 *)\nval obj : < set : int -> int -> unit > = <obj>\n(* 尝试调用实例方法 *)\n# obj#set 1 2;;\n- : unit = ()\n</code></pre>\n<h4>上面的 obj 类型是 < set : int -> int -> unit ></h4>\n<p>换句话说,这意味着有一个方法设置int - > int - > unit。</p>\n<p>具有满足这个定义的方法的类被认为是相同的对象类型。</p>\n<pre><code class=\"language-ocaml\">(* 定义上面与obj无关的类 *)\n# class unrelated_class =\n object\n (* 定义一个显示x,y的方法集 *)\n method set x y = Printf.printf "(%d, %d)\\n" x y\n end;;\nclass unrelated_class : object method set : int -> int -> unit end\n\n(* 由于对象类型匹配,它们被放在同一个列表中 *)\n# let obj2 = new unrelated_class;;\nval obj2 : unrelated_class = <obj>\n# [obj; obj2];;\n- : unrelated_class list = [<obj>; <obj>]\n\n(* 由于对象类型匹配,可以将其作为相同的返回值类型进行处理 *)\n# let hoge x = if x then obj else new unrelated_class;;\nval hoge : bool -> unrelated_class = <fun>\n# (hoge true)#set 1 2;;\n- : unit = ()\n# (hoge false)#set 1 2;;\n(1, 2)\n- : unit = ()\n</code></pre>\n<p>我个人觉得Java和Go的界面正在扮演OCaml的对象类型的角色。</p>\n<h4>部分类型</h4>\n<h5>部分类型=>如果定义了部分对象类型的方法,则将其视为部分类型</h5>\n<ul>\n<li>示例:类型2是类型1的部分类型</li>\n<li>类型1 => <方法1:int - > int,方法2:unit></li>\n<li>类型2 => <method1:int - > int></li>\n</ul>\n<h4>CORATION(类型转换)</h4>\n<p>(表达式:类型1:>类型2)</p>\n<p>将部分类型1转换为类型2。 它接近于Java中的上传。</p>\n<pre><code class=\"language-ocaml\">(* print_class1 是 print_class2 的部分类型 *)\n# class print_class1 = object\n method print_1 = print_int 1\n end;;\nclass print_class1 : object method print_1 : unit end\n# class print_class2 = object\n method print_1 = print_int 1\n method print_2 = print_int 2\n end;;\nclass print_class2 : object method print_1 : unit method print_2 : unit end\n\n(* 由于对象类型不同,print_class 1和print_class 2不在同一个列表中 *)\n# let obj_list = [new print_class1; new print_class2];;\nError: This expression has type print_class2\n but an expression was expected of type print_class1\n The second object type has no method print_2\n\n(* 通过指导(类型转换)把它们放在同一个列表中 *)\n# let obj_list = [new print_class1; (new print_class2 :> print_class1)];;\nval obj_list : print_class1 list = [<obj>; <obj>]\n\n(* コアーションによって削ぎ落とされた情報は呼び出せない *)\n# let [obj1; obj2] = obj_list;;\nval obj1 : print_class1 = <obj>\nval obj2 : print_class1 = <obj>\n\n# obj1#print_1;; (* 可以调用 *)\n1- : unit = ()\n# obj2#print_2;; (* 不能被调用 *)\nError: This expression has type print_class1\n It has no method print_2\n</code></pre>\n<h4>多层对象类型</h4>\n<p>表示满足部分类型的任意类型</p>\n<ul>\n<li><方法名称1:类型1; ...,方法名称n:类型n; ..></li>\n<li>最后一个“..”是重要的</li>\n<li>换句话说,它表示“各种其他”</li>\n</ul>\n<pre><code class=\"language-ocaml\">\n(* 定义接受多层对象类型的函数 *)\n# let print1 print_obj = print_obj#print_1;;\nval print1 : < print_1 : 'a; .. > -> 'a = <fun>\n\n(* 您可以接收满足部分类型的对象类型 *)\n# print1 obj1;;\n1- : unit = ()\n# print1 obj2;;\n1- : unit = ()\n</code></pre>\n<p>以上 < print_1 : 'a; .. > -> 'a = <fun> 中的 -> 'a 部分 'a 是一个类型变量 < print_1 : 'a; .. > 的别名</p>\n<h4>关于多层次对象类型的参数类型的变化</h4>\n<p>如果你给一个多层次的类型的参数</p>\n<pre><code class=\"language-ocaml\">(* 定义多层函数 *)\n# let k1 a b = a and k2 a b = b;;\nval k1 : 'a -> 'b -> 'a = <fun>\nval k2 : 'a -> 'b -> 'b = <fun>\n\n(* 请求相同类型的函数的返回值会更改该类型。 *)\n# let f b = if b then k1 else k2;;\n(*'a - >'b - >'a /'b 的类型是 'a - >'a - >'a *)\nval f : bool -> 'a -> 'a -> 'a = <fun>\n</code></pre>\n<p>当给出一个不是多层的类型的参数时</p>\n<pre><code class=\"language-ocaml\">(* 不同类型的函数 *)\n# let k1' (a:int) (b:string) = a and k2' (a:int) (b:string) = b;;\nval k1' : int -> string -> int = <fun>\nval k2' : int -> string -> string = <fun>\n\n(*\n *由于返回值的函数类型不同,因此不能作为返回值使用\n *统一的类型不能完成\n *)\n# let f' b = if b then k1' else k2';;\nError: This expression has type int -> string -> string\n but an expression was expected of type int -> string -> int\n Type string is not compatible with type int\n</code></pre>\n<p>给定一个多层对象类型</p>\n<pre><code class=\"language-ocaml\">(* 多定义分层对象类型的函数 *)\n# let print1' obj = obj#print_1 and print2' obj = obj#print_2;;\nval print1' : < print_1 : 'a; .. > -> 'a = <fun>\nval print2' : < print_2 : 'a; .. > -> 'a = <fun>\n\n(* 当组合两个分层对象类型函数时 *)\n# let f b = if b then print1' else print2';;\n(*\n * 函数的返回值的类型为 < print_1 : 'a; print_2 : 'a; .. >\n * 换句话说,诸如合成每个多层对象类型的类型变成了\n *)\nval f : bool -> < print_1 : 'a; print_2 : 'a; .. > -> 'a = <fun>\n\n(* 函数调用 *)\n# let a = f true;;\nval a : < print_1 : '_a; print_2 : '_a; _.. > -> '_a = <fun>\n(* print_2 因为它没有类型错误 *)\n# a new print_class1;;\nError: This expression has type print_class1\n but an expression was expected of type print_class2\n The first object type has no method print_2\n(* OK, 因为它满足对象类型 *)\n# a new print_class2;;\n1- : unit = ()\n</code></pre>\n<h4>抽象方法·抽象类</h4>\n<ul>\n<li>定义假定稍后继承</li>\n<li>抽象方法=>具有空定义的方法</li>\n<li>method virtual 方法 : 类型</li>\n<li>抽象类=>抽象方法的类定义</li>\n</ul>\n<pre><code class=\"language-ocaml\">\n(* 抽象类的定义 *)\n# class virtual abstruct_print =\n object (self)\n method virtual print : unit (* 抽象方法 *)\n end;;\nclass virtual abstruct_print : object method virtual print : unit end\n\n(* 继承抽象类 *)\n# class print_hoge =\n object (self)\n inherit abstruct_print\n method print = Printf.printf "hoge!\\n"\n end;;\nclass print_hoge : object method print : unit end\n</code></pre>\n<p>class virtual</p>\n<h4>多阶段类(泛型)</h4>\n<ul>\n<li>定义用类型参数化的对象类。</li>\n<li>类定义=>定义一个同名的对象类型,而不仅仅是类</li>\n<li>类型变量的声明是定义多层定义中的类类型所必需的</li>\n<li>显式声明类型变量</li>\n<li>请参阅中定义的类型变量</li>\n</ul>\n<h4>class [类型变量, ...] 类名称 object ... end</h4>\n<p>定义一个可以填充任何类型值的堆栈(从官方网站引用)</p>\n<pre><code class=\"language-ocaml\"># class ['a] stack =\n object (self)\n val mutable list = ( [] : 'a list ) (* 实例变量 *)\n method push x = list <- x :: list (* 推入堆栈 *)\n method pop = (* 从堆栈中移除(pop) *)\n let result = List.hd list in\n list <- List.tl list;\n result\n method peek = List.hd list (* 堆栈峰值 *)\n method size = List.length list (* 堆栈的大小 *)\n end;;\nclass ['a] stack :\n object\n val mutable list : 'a list\n method peek : 'a\n method pop : 'a\n method push : 'a -> unit\n method size : int\n end\n</code></pre>\n<p>使用多阶段类</p>\n<pre><code class=\"language-ocaml\">(* 生成堆栈的一个实例 *)\n# let s = new stack;;\n(* 用非约束状态的类型变量生成实例 *)\nval s : '_a stack = <obj>\n(* 添加一个浮点数 *)\n# s#push 1.0;;\n- : unit = ()\n(* 类型变量 '_a 被绑定为浮点数 *)\n# s;;\n- : float stack = <obj>\n</code></pre>\n","author":"lanqy"},{"title":"OCaml简介第4部分","created":"2018/07/15","link":"2018/07/15/getting_started4","description":"OCaml简介第4部分","content":"<h2>OCaml简介第4部分</h2>\n<p>译自:https://qiita.com/zenwerk/items/244b84bee48bf61d2a51</p>\n<h3>模块</h3>\n<p>它是程序的一部分,但在OCaml中被称为结构。</p>\n<p>所有OCaml库都作为模块(结构)提供。</p>\n<h4>文件名称即是模块名称</h4>\n<p>文件名 example.ml => 模块名称为 Example</p>\n<h4>标准模块</h4>\n<p>OCaml 内置模块</p>\n<pre><code class=\"language-ocaml\">(* 列表 *)\n# List.length [1; 2; 3];;\n- : int = 3\n# let q = Queue.create ();;\nval q : '_a Queue.t = <abstr>\n\n(* 队列 *)\n# Queue.add "first" q;;\n- : unit = ()\n# Queue.take q;;\n- : string = "first"\n# Queue.take q;;\nException: Queue.Empty.\n\n(* 数组 *)\n# Array.make 3 'a';;\n- : char array = [|'a'; 'a'; 'a'|]\n\n(* 标准输出 *)\n# Printf.printf "%d, %x, %s\\n" 10 255 "hoge";;\n10, ff, hoge\n- : unit = ()\n</code></pre>\n<h4>open 模块</h4>\n<p>通过 open 打开模块可以省略模块名称</p>\n<p>与 python 中 from hoge import * 类似</p>\n<p>由于打开到当前源的模块的名称空间已扩展,因此只有在不困惑时才能打开它。</p>\n<p>由于容器名称经常与函数名称重叠,因此不打开它们是正常的。</p>\n<pre><code class=\"language-ocaml\">(* 打开模块 *)\n# open List;;\n# length [1; 2; 3];;\n- : int = 3\n\n(* 覆盖函数名称 *)\n# let length () = "overload!";;\nval length : unit -> string = <fun>\n# length ();;\n- : string = "overload!"\n\n(* 您可以通过指定模块名称来调用它 *)\n# List.length [1; 2; 3];;\n- : int = 3\n</code></pre>\n<p>顺便说一下,OCaml有一个名为Pervasives的模块,在启动时打开。</p>\n<p>像abs和open_in这样的函数属于这个模块。</p>\n<h4>模块定义</h4>\n<p>模块名称以大写字母开头</p>\n<h5>module 模块名 = struct 各种定义... end</h5>\n<pre><code class=\"language-ocaml\">(* 模块定义 *)\n# module Hello = struct\n let message = "Hello"\n let hello () = print_endline message\n end;;\nmodule Hello : sig val message : string val hello : unit -> unit end\n(* 调用 *)\n# Hello.hello ();;\nHello\n- : unit = ()\n</code></pre>\n<h4>签名</h4>\n<h5>签名</h5>\n<ul>\n<li>sig ... end 周围的部分</li>\n<li>整个模块的类型(类似)</li>\n<li>表示模块的I / F(可以定义可访问模块的元素)</li>\n</ul>\n<h4>mli文件</h4>\n<p>Hoge 模块的签名可以在 hoge.mli 文件中定义。</p>\n<p>在文件中写一个签名</p>\n<h5>签名定义</h5>\n<ul>\n<li>定义 => module type 签名名 = sig ... end</li>\n<li>应用 => module 模块名 : 签名名 = 模块名或 struct ... end</li>\n</ul>\n<pre><code class=\"language-ocaml\">(* message 元素可访问 *)\n# Hello.message;;\n- : string = "Hello"\n\n(* message 定义未定义的签名 *)\n# module type Hello_Sig =\n sig\n val hello: unit -> unit\n end;;\nmodule type Hello_Sig = sig val hello : unit -> unit end\n\n(* 给一个模块签名 *)\n# module Hello2 : Hello_Sig = Hello;;\nmodule Hello2 : Hello_Sig\n\n(* 因为它不是签名, message 元素不可访问 *)\n# Hello2.hello ();;\nHello\n- : unit = ()\n# Hello2.message;;\nError: Unbound value Hello2.message\n\n(* 直接定义模块 *)\n# module Hello3 : Hello_Sig = struct\n let hello () = print_endline "Hello3!"\n end;;\nmodule Hello3 : Hello_Sig\n# Hello3.hello ();;\nHello3!\n- : unit = ()\n# Hello3.message;;\nError: Unbound value Hello3.message\n</code></pre>\n<h4>抽象数据类型</h4>\n<p>在签名定义中,省略 = ...用于类型定义(typ t = ...)</p>\n<p>您可以隐藏定义的详细信息。</p>\n<p>通过隐藏类型信息和实现,可以将该类型的操作限制为通过模块进行的操作。</p>\n<p>防止意外操作。</p>\n<pre><code class=\"language-ocaml\">(* 签名定义 *)\n# module type AbstTypeSig = sig\n type t (* 抽象数据类型 *)\n val get_t : int -> t\n val print : t -> unit\n end;;\nmodule type AbstTypeSig =\n sig type t val get_t : int -> t val print : t -> unit end\n\n(* 模块定义 *)\n# module AbstTypeInt : AbstTypeSig = struct\n type t = int\n let get_t i = i\n let print t = print_int t\n end;;\nmodule AbstTypeInt : AbstTypeSig\n\n(* 如果返回值是一个抽象数据类型 <abstr> *)\n# let t = AbstTypeInt.get_t 0;;\nval t : AbstTypeInt.t = <abstr>\n# AbstTypeInt.print t;;\n0- : unit = ()\n\n(* \n 抽象数据类型不能在外部处理\n AbstTypeInt.t 是一个真正的int,但是因为它隐藏着一个抽象的数据类型\n print_int 即使是作为参数引用\n*)\n# let () = print_int t;;\nError: This expression has type AbstTypeInt.t\n but an expression was expected of type int\n</code></pre>\n<h3>Functor</h3>\n<p>通过应用参数动态生成参数的函数。</p>\n<p>消除了多次定义不同模块的麻烦。</p>\n<h4>使用 Functor</h4>\n<h5>Functor应用程序 => Functor名称(模块化)</h5>\n<h5>※模块化功能应用程序本身</h5>\n<h4>处理集合 Set 模块示例</h4>\n<p>标准模块的 Set 和 Queue 使用 functor</p>\n<p>对于要处理的集合的元素,定义以下模块</p>\n<ul>\n<li>定义集合元素的模块</li>\n<li>一个类型 t 表示一个集合的元素</li>\n<li>比较元素类型t的大小的函数:compare: t -> t -> int</li>\n</ul>\n<p>将上述“元素类型模块”应用于 functor ,生成“该类型为元素的模块”。</p>\n<pre><code class=\"language-ocaml\">(* functor 应用 *)\n# module IntSet = Set.Make (struct\n type t = int\n let compare i j = i - j\n end);;\nmodule IntSet :\n sig\n type elt = int (* 元素我想作为元素类型对待 elt = int *)\n type t (* 代表一个集合的类型是 IntSet.t 是一种抽象数据类型 *)\n val empty : t\n val is_empty : t -> bool\n val mem : elt -> t -> bool\n val add : elt -> t -> t\n val singleton : elt -> t\n val remove : elt -> t -> t\n val union : t -> t -> t\n val inter : t -> t -> t\n val diff : t -> t -> t\n val compare : t -> t -> int\n val equal : t -> t -> bool\n val subset : t -> t -> bool\n val iter : (elt -> unit) -> t -> unit\n val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a\n val for_all : (elt -> bool) -> t -> bool\n val exists : (elt -> bool) -> t -> bool\n val filter : (elt -> bool) -> t -> t\n val partition : (elt -> bool) -> t -> t * t\n val cardinal : t -> int\n val elements : t -> elt list\n val min_elt : t -> elt\n val max_elt : t -> elt\n val choose : t -> elt\n val split : elt -> t -> t * bool * t\n val find : elt -> t -> elt\n val of_list : elt list -> t\n end\n\n(* 使用由 functor 生成的模块 *)\n# open IntSet;;\n# let s1 = add 2 (add 1 empty)\n and s2 = add 1 (add 3 empty);;\nval s1 : IntSet.t = <abstr>\nval s2 : IntSet.t = <abstr>\n# mem 1 s1;;\n- : bool = true\n</code></pre>\n<h4>Functor 的定义</h4>\n<h5>module functor名称(参数名称:签名表达式)= 模块化表达式</h5>\n<p>以下糖衣语法</p>\n<h5>module functor名称 = functor(参数名称:签名表达式) -> 模块化表达式</h5>\n<p>定义 Set.Make 的一个简单 functor 的例子</p>\n<pre><code class=\"language-ocaml\">(* 签名定义 *)\nmodule type ELEMENT = sig\n type t\n val compare: t -> t -> int\nend\n\n(* functor 定义 *)\nmodule MakeSet (Element : ELEMENT) =\n struct\n type elt = Element.t\n type t = elt list\n\n let empty = []\n\n let mem x set = List.exists (fun y -> Element.compare x y = 0) set\n\n let rec add elt = function\n | [] -> [elt]\n | (x :: rest as s) ->\n match Element.compare elt x with\n | 0 -> s\n | r when r < 0 -> elt :: s\n | _ -> x :: (add elt rest)\n\n let rec elements s = s\n end;;\n</code></pre>\n<h4>依赖类型</h4>\n<ul>\n<li>上述 functor 的返回值的签名如下</li>\n<li>Functor(Element:ELEMENT) -> 转换的描述在 sig ... end 中查看</li>\n<li>type elt = Element.t 被写入正式参数的值包含在返回类型中</li>\n<li>换句话说,返回值的类型根据给定的参数值(!= Type)而变化,</li>\n<li>这被称为依赖类型</li>\n</ul>\n<pre><code class=\"language-ocaml\">(* 返回顶层的值 *)\nmodule type ELEMENT = sig type t val compare : t -> t -> int end\nmodule MakeSet :\n functor (Element : ELEMENT) ->\n sig\n type elt = Element.t\n type t = elt list\n val empty : 'a list\n val mem : Element.t -> Element.t list -> bool\n val add : Element.t -> Element.t list -> Element.t list\n val elements : 'a -> 'a\n end\n</code></pre>\n<h4>functor 的信息隐藏</h4>\n<p>像普通模块一样,您可以限制函数返回值的签名并隐藏内部实现。</p>\n<h5>module functor名称(参数名称:输入签名表达式):签名表达式返回 = 模块表达式</h5>\n<p>通过指定要返回的签名表达式可以隐藏信息</p>\n<pre><code class=\"language-ocaml\">module MakeSet (Element : ELEMENT) :\n (* 签名表达式返回 *)\n sig\n type elt = Element.t\n type t (* 抽象数据类型 *)\n val mem : elt -> t -> bool\n val add : elt -> t -> t\n val elements : t -> elt list\n end\n =\n (* 模块化 *)\n struct\n type elt = Element.t\n type t = elt list\n\n let empty = []\n\n let mem x set = List.exists (fun y -> Element.compare x y = 0) set\n\n let rec add elt = function\n | [] -> [elt]\n | (x :: rest as s) ->\n match Element.compare elt x with\n | 0 -> s\n | r when r < 0 -> elt :: s\n | _ -> x :: (add elt rest)\n\n let rec elements s = s\n end;;\n\n(* functor 表达式的返回值 *)\nmodule MakeSet :\n functor (Element : ELEMENT) ->\n sig\n type elt = Element.t\n type t\n val empty : t\n val mem : elt -> t -> bool\n val add : elt -> t -> t\n val elements : t -> elt list\n end\n</code></pre>\n<p>比较上述函数应用方程的返回值签名公式</p>\n<ul>\n<li>before</li>\n</ul>\n<pre><code class=\"language-ocaml\"># module StringSet = MakeSet(String);;\nmodule StringSet :\n sig\n type elt = String.t\n type t = elt list\n val empty : 'a list\n val mem : String.t -> String.t list -> bool\n val add : String.t -> String.t list -> String.t list\n val elements : 'a -> 'a\n end\n</code></pre>\n<ul>\n<li>After</li>\n</ul>\n<pre><code class=\"language-ocaml\"># module IntSet = MakeSet(struct\n type t = int\n let compare i j = i - j\n end);;\nmodule IntSet :\n sig\n type elt = int\n type t (* 抽象数据类型 *)\n val empty : t\n val mem : elt -> t -> bool\n val add : elt -> t -> t\n val elements : t -> elt list\n end\n\n# Open IntSet;;\n# let s1 = add 1 (add 2 empty)\n and s2 = add 3 (add 4 empty);;\nval s1 : IntSet.t = <abstr> (* 抽象数据类型 *)\nval s2 : IntSet.t = <abstr>\n\n(* 当它是一个字符串 *)\n# module StringSet = MakeSet(String);;\nmodule StringSet :\n sig\n type elt = String.t\n type t = MakeSet(String).t (* 有隐藏的实现吗? *)\n val empty : t\n val mem : elt -> t -> bool\n val add : elt -> t -> t\n val elements : t -> elt list\n end\n\n# open StringSet;;\n# let s1 = add "a" (add "b" empty)\n and s2 = add "c" (add "d" empty);;\nval s1 : StringSet.t = <abstr> (* 这是一个抽象的数据类型 *)\nval s2 : StringSet.t = <abstr>\n</code></pre>\n<h4>签名的分解和定义( with type )</h4>\n<p>签名表达式 with type 类型名 = 类型定义 and type ...</p>\n<p>不要指定你希望 functor 直接用 sig ... end 返回的签名定义,而是要给它定义名称。</p>\n<p>type elt = Element.t 部分</p>\n<p>签名不能被定义为别名,因为它取决于伪参数名称</p>\n<pre><code class=\"language-ocaml\">module MakeSet (Element : ELEMENT) :\n (* 签名表达式返回 *)\n sig\n type elt = Element.t\n type t (* 抽象数据类型 *)\n end\n =\n struct ... end;;\n</code></pre>\n<h4>with type 语法使用</h4>\n<pre><code class=\"language-ocaml\">(* with type 类型定义替换 elt *)\n# module type ElementWithType =\n sig\n type elt\n type t\n val empty : t\n val mem : elt -> t -> bool\n val add : elt -> t -> t\n val elements : t -> elt list\n end;;\n\n(* with type 定义 *)\n# module type E2 = ElementWithType with type elt = int;;\nmodule type E2 =\n sig\n type elt = int\n type t\n val empty : t\n val mem : elt -> t -> bool\n val add : elt -> t -> t\n val elements : t -> elt list\n end\n</code></pre>\n<h3>批处理编译器和拆分编译</h3>\n<p>编译器通过file => 批量编译器导出</p>\n<p>以文件为单位创建一个目标文件Link => 分割编译</p>\n<p>ocaml 的批处理编译器 => ocamlc(bytecode),ocamlopt(本地代码)</p>\n<h4>统一编译</h4>\n<pre><code class=\"language-ocaml\">$ ocamlc -o output.name src1.ml src2.ml ...\n</code></pre>\n<h4>拆分编译</h4>\n<ul>\n<li>需要安排最后一个链接的 cmo 文件,以便名称解析成为可能</li>\n<li>非标准库</li>\n<li>链接时有必要明确指出文件名</li>\n<li>nums.cma 等</li>\n<li>-c 选项输出目标文件而不链接</li>\n<li>-I 允许您指定包含 cmi,cmo 的目录</li>\n</ul>\n<pre><code class=\"language-ocaml\">$ ocamlc -c mod1.ml\n$ ocamlc -c mod2.ml\n$ ocamlc -o output.name nums.cma mod1.cmo mod2.cmo\n</code></pre>\n<h4>mli文件</h4>\n<p>mli 文件描述了相应的 .ml 签名</p>\n<p>在编译时使用 ocamlc</p>\n<p>用 ocamlc 编译 mli 文件</p>\n<p>用 ocamlc -c编译ml文件</p>\n<p>链接 cmo 与 ocamlc</p>\n<pre><code class=\"language-ocaml\">$ ocamlc mod.mli # -c 没有选项要求\n$ ocamlc -c mod.ml\n$ ocamlc -c mod2.ml\n$ ocamlc -o a.out mod.cmo mod2.cmo\n</code></pre>\n","author":"lanqy"},{"title":"OCaml简介第3部分","created":"2018/07/14","link":"2018/07/14/getting_started3","description":"OCaml简介第3部分","content":"<h2>OCaml简介第3部分</h2>\n<p>译自:https://qiita.com/zenwerk/items/bfc1978718b5da3f463b</p>\n<h3>异常处理</h3>\n<p>它在您除以零或指定一个不存在的文件时发生。</p>\n<pre><code class=\"language-ocaml\"># 1/0;;\nException: Division_by_zero.\n# open_in "";;\nException: Sys_error ": No such file or directory".\n</code></pre>\n<h4>抛出异常( raise 表达式 )</h4>\n<h5>raise 异常</h5>\n<h5>raise( 异常参数 )</h5>\n<p>如果异常需要参数,则()是必需的。</p>\n<pre><code class=\"language-ocaml\"># raise Not_found;;\nException: Not_found.\n# raise (Sys_error ": No such file or directory");;\nException: Sys_error ": No such file or directory".\n# raise (Sys_error ": 我会抛出异常!");;\nException: Sys_error ": ?\\136\\145?\\154?\\138\\155?\\135??\\130常?\\129".\n</code></pre>\n<pre><code class=\"language-ocaml\"># let rec fact n =\n if n < 0 then raise (Invalid_argument ": negative argument")\n else if n = 0 then 1 else n * fact (n-1);;\nval fact : int -> int = <fun>\n# fact 5;;\n- : int = 120\n# fact (-1);;\nException: Invalid_argument ": negative argument".\n</code></pre>\n<h4>异常处理( try with )</h4>\n<h5>try 表达式 with 异常1 -> 表达式1 | ...</h5>\n<pre><code class=\"language-ocaml\"># try raise Not_found with\n | Not_found -> "not found !"\n | _ -> "unknown !";;\n- : string = "not found !"\n\n(* 前面定义的 fact 函数例子 *)\n# try fact (-1) with\n | Invalid_argument _ -> 0\n | _ -> 9999;;\n- : int = 0\n</code></pre>\n<h4>异常定义</h4>\n<p>异常类型的构造函数中称为异常构造函数。\n该变种是 exn 类型</p>\n<pre><code class=\"language-ocaml\">(* 确认异常的变体类型 *)\n# Not_found;;\n- : exn = Not_found\n# raise;;\n- : exn -> 'a = <fun>\n</code></pre>\n<p>异常定义是为exn类型添加一个新的构造函数。</p>\n<p>exn类型是特殊的,你可以稍后添加一个构造函数。</p>\n<h5>exception 异常名</h5>\n<pre><code class=\"language-ocaml\">(* 异常定义 *)\n# exception Hoge;;\nexception Hoge\n# exception Fuga of string;;\nexception Fuga of string\n# raise Hoge;;\nException: Hoge.\n# raise (Fuga "fuga!");;\nException: Fuga "fuga!".\n</code></pre>\n<h4>关于exn类型</h4>\n<p>既然exn也是一个变体类型,它也可以作为一个参数传递。</p>\n<pre><code class=\"language-ocaml\"># exception Hoge;;\nexception Hoge\n\n(* exn 类型列表 *)\n# let exnlist = [Not_found; Hoge; (Invalid_argument "fuga")];;\nval exnlist : exn list = [Not_found; Hoge; Invalid_argument "fuga"]\n\n(* 接收exn类型的函数 *)\n# let f = function\n | Hoge -> "hoge!"\n | x -> raise x;;\nval f : exn -> string = <fun>\n# f Hoge;;\n- : string = "hoge!"\n# f Not_found;;\nException: Not_found.\n</code></pre>\n<h3>unit 类型</h3>\n<p>输出字符串的程序。</p>\n<pre><code class=\"language-ocaml\"># print_string "hoge\\n";;\nhoge\n- : unit = ()\n</code></pre>\n<p>返回类型是 unit 类型</p>\n<p>unit 类型的值只是一个名为()的常量,称为 unit 值。</p>\n<h4>unit 类型的用法</h4>\n<ul>\n<li>()上没有可以执行的操作</li>\n<li>用作返回值本身没有意义的函数的返回值</li>\n<li>在定义不需要有意义的参数的函数时用作参数</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let const () = 777;;\nval const : unit -> int = <fun>\n# const ();;\n- : int = 777\n</code></pre>\n<h5>用作判断操作是否成功的返回值</h5>\n<pre><code class=\"language-ocaml\">(*\n () 将匹配模式,如果操作成功,将返回单位类型。\n 也就是说,如果匹配成功,则表示操作成功。\n*)\n# let () = Bytes.set "Test" 1 'C';;\n</code></pre>\n<h3>可变的数据结构</h3>\n<h4>修改字符串</h4>\n<h5>"string".[index] <- 'char'</h5>\n<pre><code class=\"language-ocaml\"># let s = "life";;\nval s : string = "life"\n# s.[2] <- 'v';;\n- : unit = ()\n# s;;\n- : string = "live"\n\n(* String.set 弃用 *)\n# let f2 = "hoge";;\nval f2 : string = "hoge"\n# Bytes.set f2 0 'H';;\n- : unit = ()\n# f2;;\n- : string = "Hoge"\n</code></pre>\n<h5>该操作操作参考目的地</h5>\n<pre><code class=\"language-ocaml\"># let s = "hoge";;\nval s : string = "hoge"\n# let a = (s, s);;\nval a : string * string = ("hoge", "hoge")\n# Bytes.set s 3 'E';;\n- : unit = ()\n\n(* 两者都被改变,因为参考目标是相同的 *)\n# a;;\n- : string * string = ("hogE", "hogE")\n</code></pre>\n<h4>物理相等</h4>\n<h5>物理相等 => 比较数据地址时的平等性</h5>\n<ul>\n<li>使用 ==, !=</li>\n</ul>\n<h5>结构相等 => 平等作为价值进行比较</h5>\n<ul>\n<li>使用=,<></li>\n</ul>\n<pre><code class=\"language-ocaml\"># let s1 = "hoge" and s2 = "hoge";;\nval s1 : string = "hoge"\nval s2 : string = "hoge"\n(* 结构相等 *)\n# s1 = s2;;\n- : bool = true\n(* 物理相等 *)\n# s1 == s2;;\n- : bool = false\n# s1 != s2;;\n- : bool = true\n</code></pre>\n<h4>可修改的记录</h4>\n<ul>\n<li>修改记录 => 使用 mutable 关键字</li>\n<li>记录修改 => record.field <- 值</li>\n</ul>\n<pre><code class=\"language-ocaml\"># type account = {name:string;mutable amount:int};;\ntype account = { name : string; mutable amount : int; }\n# let ac = {name = "bob"; amount = 1000};;\nval ac : account = {name = "bob"; amount = 1000}\n# ac.amount <- 999;;\n- : unit = ()\n# ac;;\n- : account = {name = "bob"; amount = 999}\n(* 不可改变 *)\n# let () = ac.name <- "Hoge";;\nError: The record field name is not mutable\n(* 这样是可以的 *)\n# ac.name.[0] <- 'B';;\n- : unit = ()\n# ac;;\n- : account = {name = "Bob"; amount = 999}\n</code></pre>\n<h4>创建引用( ref )</h4>\n<ul>\n<li>创建引用 => ref函数</li>\n<li>引用获取 => !引用</li>\n<li>引用目标重写 => 引用 := 值</li>\n</ul>\n<pre><code class=\"language-ocaml\">(* 创建引用 *)\n# let h = ref "Hoge" and f = ref "Fuga";;\nval h : string ref = {contents = "Hoge"}\nval f : string ref = {contents = "Fuga"}\n\n(* 引用获取 *)\n# let () = print_string (!h ^ !f ^ "\\n");;\nHogeFuga\n\n(* 引用重写 *)\n# h := !f;;\n- : unit = ()\n# let () = print_string (!h ^ !f ^ "\\n");;\nFugaFuga\n</code></pre>\n<h4>引用的引用 ( ref ref )</h4>\n<p>它也可以像C语言中的双指针一样</p>\n<pre><code class=\"language-ocaml\">(* 创建引用 *)\n# let r1 = ref 5 and r2 = ref 2;;\nval r1 : int ref = {contents = 5}\nval r2 : int ref = {contents = 2}\n(* 引用的引用创建 *)\n# let rr1 = ref r1 and rr2 = ref r2;;\nval rr1 : int ref ref = {contents = {contents = 5}}\nval rr2 : int ref ref = {contents = {contents = 2}}\n(* 引用操作 *)\n# let () = !rr1 := !(!rr2);;\n# (!r1, !r2);;\n- : int * int = (2, 2)\n</code></pre>\n<h4>数组</h4>\n<ul>\n<li>[|值1;值2; ... |]</li>\n<li>引用元素 => [| ... |].( 下标 )</li>\n<li>改变数组元素 => [| ... |].( 下标 ) <- value</li>\n</ul>\n<p>数组的长度是固定的。</p>\n<p>您可以通过下标直接获取任何元素。</p>\n<h3>程序控制结构</h3>\n<p>作为OCaml的规范,参数的评估顺序是未定义的。</p>\n<p>因此,准备了用于像过程性语言那样控制执行顺序的语法。</p>\n<h4>顺序执行</h4>\n<h5>表达式1; 表达式2; ... 表达式n</h5>\n<p>与如下如下代码一样</p>\n<pre><code class=\"language-ocaml\">let _ = 表达式1 in\nlet _ = 表达式2 in\n.\n.\n.\n表达式n\n</code></pre>\n<p>最后的表达式n是整个返回值。</p>\n<h4>begin 与 end</h4>\n<p>你可以写 begin 表达式1; ...表达式n end ,而不是(表达式; ...表达式n)</p>\n<p>这似乎是首选。</p>\n<h4>ignore</h4>\n<p>如果相当于一个句子的表达式返回非 unit 类型,则会出现警告</p>\n<p>忽略警告的函数</p>\n<pre><code class=\"language-ocaml\">(* 将返回值设置为0(int) *)\n# let print_hello () = print_string "Hello, "; 0;;\nval print_hello : unit -> int = <fun>\n\n(* 警告出现在 print_hello *)\n# print_hello (); print_string "World\\n";;\nWarning 10: this expression should have type unit.\nHello, World\n- : unit = ()\n\n(* 忽略不发出警告 *)\n# ignore (print_hello ()); print_string "World\\n";;\nHello, World\n- : unit = ()\n</code></pre>\n<p>;是一个分隔符</p>\n<p>;不是终止符,它被用作分隔符。</p>\n<p>因此,要小心如果在表达式n的末尾添加一个分号作为表达式n,经常会出现额外的错误。</p>\n<h4>条件语句</h4>\n<p>if then 的 then 子句的返回值是(),则else可以省略。</p>\n<p>if 表达式1 then 表达式2 => if 表达式1 then 表达式2 else()</p>\n<p>这意味着如果表达式1不成立,将会做任何事情。</p>\n<h4>循环语句</h4>\n<p>for 和 while 语句</p>\n<h5>for</h5>\n<p>for => for variable = start_value to end_value do 表达式 done</p>\n<p>或</p>\n<p>for variable = start_value downto end_value do 表达式 done</p>\n<pre><code class=\"language-ocaml\">(* for 循环 *)\n# for i = 1 to 10 do begin print_int i; () end done;;\n12345678910- : unit = ()\n# for i = 10 downto 1 do begin print_int i; () end done;;\n10987654321- : unit = ()\n</code></pre>\n<h5>while</h5>\n<p>while 条件 do 表达式 done</p>\n<p>while据说它用得不多。</p>\n<pre><code class=\"language-ocaml\">let quit_loop = ref false in\n while not !quit_loop do\n print_string "Have you had enough yet? (y/n) ";\n let str = read_line () in\n if str.[0] = 'y' then\n quit_loop := true\n done;;\nHave you had enough yet? (y/n) n\nHave you had enough yet? (y/n) y\n- : unit = ()\n</code></pre>\n","author":"lanqy"},{"title":"OCaml简介第2部分","created":"2018/07/13","link":"2018/07/13/getting_started2","description":"OCaml简介第2部分","content":"<h2>OCaml简介第 2 部分</h2>\n<p>译自:https://qiita.com/zenwerk/items/603bd383fe5c6b8cace3</p>\n<h3>递归多层数据结构。</h3>\n<p>列表类型</p>\n<pre><code class=\"language-ocaml\"># [1;2;3;4;5];;\n- : int list = [1; 2; 3; 4; 5]\n# ["a"; "b"; "c"];;\n- : string list = ["a"; "b"; "c"]\n(* 不同的类型不能存在于同一个列表 *)\n# [1; "a"];;\nError: This expression has type string but an expression was expected of type int\n</code></pre>\n<h3>将值添加到列表的开头</h3>\n<p>使用consus操作符(::)。</p>\n<p>右结合</p>\n<pre><code class=\"language-ocaml\"># 1 :: [2; 3; 4];;\n- : int list = [1; 2; 3; 4]\n\n(* 右結合 *)\n# 1 :: 2 :: [3; 4];;\n- : int list = [1; 2; 3; 4]\n</code></pre>\n<h3>列表合并</h3>\n<p>使用 @ 。</p>\n<pre><code class=\"language-ocaml\"># [] @ [];;\n- : 'a list = []\n# [1] @ [2; 3];;\n- : int list = [1; 2; 3]\n# ["asdf"; "hoge"] @ ["fuga"];;\n- : string list = ["asdf"; "hoge"; "fuga"]\n</code></pre>\n<h3>模式匹配</h3>\n<p>match 表达式</p>\n<p>match 表达式 with 模式1 - >表达式 | 模式2 - >表达式...</p>\n<p>找到整数列表的总和的例子:</p>\n<pre><code class=\"language-ocaml\"># let rec total l =\n match l with\n [] -> 0\n | h :: rest -> h + (total rest);;\nval total : int list -> int = <fun>\n# total [1; 2; 3; 4; 5];;\n- : int = 15\n</code></pre>\n<p>反转列表的函数示例:</p>\n<pre><code class=\"language-ocaml\"># let reverse l =\n let rec innerReverse l1 l2 =\n match l1 with\n | [] -> l2\n | h :: rest -> innerReverse rest (h :: l2)\n in\n innerReverse l [];;\nval reverse : 'a list -> 'a list = <fun>\n# reverse [1; 2; 3; 4];;\n- : int list = [4; 3; 2; 1]\n</code></pre>\n<h3>function 表达式</h3>\n<p>fun 和 match 通过组合定义一个匿名函数</p>\n<p>function 模式1 - > 表达式 | 模式2 - > 表达式...</p>\n<p>上面整数列表的总和的例子可以改写如下:</p>\n<p>当使用最后一个参数进行模式匹配时方便,并且该参数仅用于模式匹配</p>\n<pre><code class=\"language-ocaml\"># let rec total = function\n [] -> 0\n | h :: rest -> h + (total rest);;\nval total : int list -> int = <fun>\n# total [1; 2; 3; 4; 5];;\n- : int = 15\n</code></pre>\n<h3>map 函数的例子</h3>\n<pre><code class=\"language-ocaml\"># let rec map fn = function\n | [] -> []\n | h :: rest -> fn h :: map fn rest;;\nval map : ('a -> 'b) -> 'a list -> 'b list = <fun>\n# map (fun x -> x + 1) [1; 2; 3; 4];;\n- : int list = [2; 3; 4; 5]\n</code></pre>\n<h3>fold (折叠)函数例子</h3>\n<pre><code class=\"language-ocaml\">(* 左fold *)\n# let rec foldl fn acc l =\n match l with\n | [] -> acc\n | h :: rest -> foldl fn (fn acc h) rest;;\n\n(* 用于查找列表长度的 fold 示例 *)\n# foldl (fun acc x -> acc + 1) 0 [1; 2; 3];;\n- : int = 3\n\n(* 右fold *)\n# let rec foldr fn l acc =\n match l with\n | [] -> acc\n | h :: rest -> fn h (foldr fn rest acc);;\nval foldr : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun>\n</code></pre>\n<h3>模式匹配守卫子句</h3>\n<p>match 表达式 with 模式1 when 真/假表达式 - >表达式| ...</p>\n<p>使用 when。</p>\n<h3>注意:match,function 中没有关闭符号</h3>\n<p>在match with 和 function 上没有结束符号。</p>\n<p>因此,当模式匹配嵌套时,必须用()等括起来</p>\n<h3>数据结构</h3>\n<h4>记录(record)</h4>\n<p>C语言结构,数据结构等同于 Python 字典。</p>\n<p>命名元素名称。</p>\n<h4>记录定义</h4>\n<ul>\n<li>type name = {field name:type; ...}</li>\n<li>字段 - > 名称和值对</li>\n<li>请注意,字段名称不能与其他记录重复</li>\n</ul>\n<pre><code class=\"language-ocaml\"># type student = {name: string; id: int};;\ntype student = { name : string; id : int; }\n</code></pre>\n<h4>创建记录</h4>\n<ul>\n<li>{Field name = value; ...}</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let s = {name = "hoge"; id = 1};;\nval s : student = {name = "hoge"; id = 1}\n</code></pre>\n<h3>记录转移</h3>\n<p>创建一个新的记录值,而不是覆盖现有的值。</p>\n<p>{记录 with 字段名称 = 值; ...}</p>\n<pre><code class=\"language-ocaml\"># let s2 = {s with name = "fuga"};;\nval s2 : student = {name = "fuga"; id = 1}\n</code></pre>\n<h3>变体</h3>\n<p>数据结构代表案例分类。</p>\n<pre><code class=\"language-ocaml\">type name =\n | 构造函数 [ of <type> [* <type>]... ]\n | 构造函数 [ of <type> [* <type>]... ]\n | ...\n</code></pre>\n<ul>\n<li>构造函数以英文大写字母开头</li>\n<li>of 是构造函数所需的参数类型</li>\n<li>of int * int 参数不是一组 int ,两个 int</li>\n<li>of (int * int) 参数是一对 int</li>\n</ul>\n<p>以下是四种数字的变体类型:</p>\n<pre><code class=\"language-ocaml\"># type figure =\n | Point\n | Circle of int\n | Rectangle of int * int (* 两个 int 类型的参数,不是元组 *)\n | Square of int;;\ntype figure = Point | Circle of int | Rectangle of int * int | Square of int\n\n# let c = Circle 4;;\nval c : figure = Circle 4\n# let figs = [Point; Rectangle (1, 1); c];;\nval figs : figure list = [Point; Rectangle (1, 1); Circle 4]\n</code></pre>\n<h3>变体的模式匹配</h3>\n<h4>function | 构造函数的参数 -> | ...</h4>\n<p>省略参数部分意味着没有参数构造函数。</p>\n<pre><code class=\"language-ocaml\">(* 计算图形面积的例子 *)\n# let area = function\n | Point -> 0\n | Circle r -> r * r * 3\n | Rectangle (x, y) -> x * y\n | Square x -> x * x;;\nval area : figure -> int = <fun>\n# area c;;\n- : int = 48\n</code></pre>\n<h3>多态变体类型</h3>\n<ul>\n<li>可以使用类型变量( 'a 等)来定义变体类型,</li>\n<li>它也被称为带参数的参数。</li>\n<li>例如,'a list 可以使用多态变量来表示。</li>\n</ul>\n<pre><code class=\"language-ocaml\"># type 'a mylist =\n | Nil\n | Cons of 'a * 'a mylist;;\ntype 'a mylist = Nil | Cons of 'a * 'a mylist\n# Nil;;\n- : 'a mylist = Nil\n\n(* 表示一个整数列表 *)\n# Cons(1, Nil);;\n- : int mylist = Cons (1, Nil)\n\n(* 表示字符列表 *)\n# Cons('a', Cons('b', Nil));;\n- : char mylist = Cons ('a', Cons ('b', Nil))\n</code></pre>\n<h3>Optional</h3>\n<p>通常说可选,表明这些情况下没有值</p>\n<pre><code class=\"language-ocaml\"># type 'a option =\n | None\n | Some of 'a;;\ntype 'a option = None | Some of 'a\n</code></pre>\n<p>使用可选的示例</p>\n<pre><code class=\"language-ocaml\"># let fact n =\n let rec fact' n = if n = 0 then 1 else n * fact' (n - 1) in\n if n < 0 then None else Some (fact' n);;\nval fact : int -> int option = <fun>\n# fact 3;;\n- : int option = Some 6\n# fact (-10);;\n- : int option = None\n</code></pre>\n<h3>递归变体类型</h3>\n<p>在构造函数中 of 下面是它自己的类型出现的变体类型</p>\n<h4>二叉树的例子</h4>\n<pre><code class=\"language-ocaml\"># type 'a btree =\n | Leaf\n | Node of 'a * 'a btree * 'a btree;;\ntype 'a btree = Leaf | Tree of 'a * 'a btree * 'a btree\n\n# Node(1, Node(1, Leaf, Leaf), Node(1, Node(1, Leaf, Leaf), Leaf));;\n- : int btree =\nNode (1, Node (1, Leaf, Leaf), Node (1, Node (1, Leaf, Leaf), Leaf))\n</code></pre>\n<p>查找树的元素数量和高度的函数示例</p>\n<pre><code class=\"language-ocaml\"># let tr = Node(1, Node(1, Leaf, Leaf), Node(1, Node(1, Leaf, Leaf), Leaf));; \nval tr : int btree =\n Node (1, Node (1, Leaf, Leaf), Node (1, Node (1, Leaf, Leaf), Leaf))\n\n(* 用于查找高度的函数 *)\n# let rec height = function\n | Leaf -> 0\n | Node(_, left, right) -> 1 + max (height left) (height right);;\nval height : 'a btree -> int = <fun>\n# height tr;;\n- : int = 3\n\n(* 用于查找元素数目的函数 *)\n# let rec size = function\n | Leaf -> 0\n | Node (_, left, right) -> 1 + size left + size right;;\nval size : 'a btree -> int = <fun>\n# size tr;;\n- : int = 4\n</code></pre>\n<h4>二叉搜索树的例子</h4>\n<ul>\n<li>添加元素</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let rec insert x = function\n | Leaf -> Node(x, Leaf, Leaf)\n | Node(k, left, right) ->\n if x < k then Node(k, insert x left, right)\n else Node(k, left, insert x right);;\nval insert : 'a -> 'a btree -> 'a btree = <fun>\n</code></pre>\n<ul>\n<li>搜索元素</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let rec mem x = function\n | Leaf -> false\n | Node(k, left, right) ->\n if x < k then mem x left\n else if x = k then true\n else mem x right;;\nval mem : 'a -> 'a btree -> bool = <fun>\n</code></pre>\n<ul>\n<li>使用例子</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let tr = Leaf;;\nval tr : 'a btree = Leaf\n# tr;;\n- : 'a btree = Leaf\n# insert 10 tr;;\n- : int btree = Node (10, Leaf, Leaf)\n# let tr = insert 10 tr;;\nval tr : int btree = Node (10, Leaf, Leaf)\n# let tr = insert 5 tr;;\nval tr : int btree = Node (10, Node (5, Leaf, Leaf), Leaf)\n# let tr = insert 20 tr;;\nval tr : int btree = Node (10, Node (5, Leaf, Leaf), Node (20, Leaf, Leaf))\n# mem 5 tr;;\n- : bool = true\n# mem 15 tr;;\n- : bool = false\n</code></pre>\n<h3>玫瑰树的一个例子</h3>\n<p>玫瑰树是元素数量未知的树。</p>\n<p>它可以被认为是与 UNIX 相同的目录结构。</p>\n<ul>\n<li>类型定义</li>\n</ul>\n<pre><code class=\"language-ocaml\">(* 由于子元素的数量是不确定的,节点的元素是列表 *)\n# type 'a rosetree = RLeaf | RNode of 'a * 'a rosetree list;;\ntype 'a rosetree = RLeaf | RNode of 'a * 'a rosetree list\n</code></pre>\n<p>XML作为玫瑰树</p>\n<ul>\n<li>类型定义</li>\n</ul>\n<p>因为它是 XML ,叶子也没有价值 - >('b option)</p>\n<pre><code class=\"language-ocaml\">(* 'a 标记, 'b 值 *)\n# type ('a, 'b) xml = XLeaf of 'b option | XNode of 'a * ('a, 'b) xml list;;\ntype ('a, 'b) xml = XLeaf of 'b option | XNode of 'a * ('a, 'b) xml list\n</code></pre>\n<p>对XML数据结构进行字符串化的函数</p>\n<p>递归XML(Rose Tree)包含一个递归数据结构的列表</p>\n<p>它是相互递归的定义</p>\n<pre><code class=\"language-ocaml\"># let rec string_of_xml = function\n | XNode(tag, xml_list) -> "<" ^ tag ^ ">" ^ string_of_xmllist xml_list ^ "</" ^ tag ^ ">"\n | XLeaf None -> ""\n | XLeaf(Some s) -> s\n and\n string_of_xmllist = function\n | [] -> ""\n | xml :: rest -> string_of_xml xml ^ string_of_xmllist rest;;\nval string_of_xml : (string, string) xml -> string = <fun>\nval string_of_xmllist : (string, string) xml list -> string = <fun>\n</code></pre>\n<h3>无限的列</h3>\n<h4>整数的无限列的例子</h4>\n<pre><code class=\"language-ocaml\"># type intseq = Cons of int * (int -> intseq);;\ntype intseq = Cons of int * (int -> intseq)\n</code></pre>\n<ul>\n<li>无限列的示例递增</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let rec f x = Cons(x+1, f);;\nval f : int -> intseq = <fun>\n# f 0;;\n- : intseq = Cons (1, <fun>)\n# f 100;;\n- : intseq = Cons (101, <fun>)\n</code></pre>\n<ul>\n<li>\n<p>如果返回值的 x 是下一个元素</p>\n<p>通过给x作为参数,我们得到元素的顺序</p>\n</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let Cons(x, f) = f 0;;\nval x : int = 1\nval f : int -> intseq = <fun>\n# let Cons(x, f) = f x;;\nval x : int = 2\nval f : int -> intseq = <fun>\n# let Cons(x, f) = f x;;\nval x : int = 3\nval f : int -> intseq = <fun>\n</code></pre>\n<ul>\n<li>获取第 N 个元素函数</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let rec nthseq n (Cons(x, f)) =\n if n = 1 then x\n else nthseq (n-1) (f x);;\nval nthseq : int -> intseq -> int = <fun>\n# nthseq 10 (f 0);;\n- : int = 10\n</code></pre>\n","author":"lanqy"},{"title":"OCaml简介第1部分","created":"2018/07/12","link":"2018/07/12/getting_started1","description":"OCaml简介第1部分","content":"<h1>OCaml 简介第 1 部分</h1>\n<p>译自 : https://qiita.com/zenwerk/items/3bdf7eef6b7511e11b2c</p>\n<h2>函数式语言</h2>\n<p>函数式语言 => 执行程序 = 执行表达式</p>\n<p>执行表达式 => 计算表达式(eval)以获得值</p>\n<p><img src=\"/images/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f343432382f65373633373633322d393262342d623736342d623063652d6465366161653933396161342e706e67.png\" alt=\"函数式语言原理图\" /></p>\n<h2>基本类型</h2>\n<h3>整型(int)</h3>\n<h4>字首</h4>\n<ul>\n<li>二进制 0b</li>\n<li>八进制 Oo</li>\n<li>十六进制 0x</li>\n</ul>\n<pre><code class=\"language-ocaml\"># 351;;\n- : int = 351\n# 12345;;\n- : int = 12345\n# 0o523;;\n- : int = 339\n# 0xffff;;\n- : int = 65535\n</code></pre>\n<h3>浮点数(float)</h3>\n<pre><code class=\"language-ocaml\"># 3.141;;\n- : float = 3.1415\n# 1.04e10;;\n- : float = 10400000000.\n# 25e-15;;\n- : float = 2.5e-14\n</code></pre>\n<h3>字符(char)</h3>\n<ul>\n<li>将其用单引号括起来,用单字节字母数字字符(ascii 字符)</li>\n</ul>\n<pre><code class=\"language-ocaml\"># 'a';;\n- : char = 'a'\n# '\\101';;\n- : char = 'e'\n# '\\n';;\n- : char = '\\n'\n</code></pre>\n<h3>字符串(string)</h3>\n<ul>\n<li>用双引号括起来。</li>\n<li>一个字符串,一个字符可以用[下标]获取。</li>\n</ul>\n<pre><code class=\"language-ocaml\"># "string";;\n- : string = "string"\n# "string".[0];;\n- : char = 's'\n</code></pre>\n<h3>类型转换</h3>\n<ul>\n<li>从 X 类型转换为 Y 类型的函数的命名规则为 Y_of_X。</li>\n</ul>\n<pre><code class=\"language-ocaml\"># float_of_int 5;;\n- : float = 5.\n# int_of_float 5.;;\n- : int = 5\n- # string_of_int 123;;\n- : string = "123"\n# int_of_string "123";;\n- : int = 123\n</code></pre>\n<h3>元组</h3>\n<ul>\n<li>(类型名 * 类型名...)</li>\n</ul>\n<pre><code class=\"language-ocaml\"># (1, 2);;\n- : int * int = (1, 2)\n# ('a', 1, "str", 4.3);;\n- : char * int * string * float = ('a', 1, "str", 4.3)\n# ((1, 2), ('a', "str"));;\n- : (int * int) * (char * string) = ((1, 2), ('a', "str"))\n</code></pre>\n<h3>定义变量</h3>\n<h4>let 定义(let defenition)</h4>\n<ul>\n<li>let 变量名称 = 表达式</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let hoge = 1;;\nval hoge : int = 1\n</code></pre>\n<h4>可以同时定义</h4>\n<ul>\n<li>let 变量名称 = 表达式 1 and 表达式 2</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let a = 1 and b = 2;;\nval a : int = 1\nval b : int = 2\n</code></pre>\n<h3>定义函数</h3>\n<h4>let 函数名 参数 = 表达式</h4>\n<pre><code class=\"language-ocaml\"># let twice s = s ^ s;;\nval twice : string -> string = <fun>\n</code></pre>\n<p>包含参数()是可选的。</p>\n<h4>let 表达式(let expression)</h4>\n<ul>\n<li>与 let 定义不同。</li>\n<li>用于在函数中定义临时变量(局部变量)的表达式。</li>\n<li>let 变量名 = 表达式 1 in 表达式 2</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let four_times s =\n let twice = s ^ s in\n twice ^ twice;;\nval four_times : string -> string = <fun>\n</code></pre>\n<h3>递归定义</h3>\n<h4>用 let rec 定义。</h4>\n<ul>\n<li>使用 rec 可以引用函数定义中定义的函数名称。</li>\n</ul>\n<p>阶乘的例子:</p>\n<pre><code class=\"language-ocaml\"># let rec fact x =\n if x <= 1 then 1 else x * fact (x - 1);;\nval fact : int -> int = <fun>\n# fact 5;;\n- : int = 120\n</code></pre>\n<h3>相互递归</h3>\n<ul>\n<li>\n<p>两个或多个函数相互调用的样式的递归定义。</p>\n</li>\n<li>\n<p>let rec 函数名称 1 参数 1 = 表达式 1 and 函数名称 2 参数 2 = 表达式 2 and ...</p>\n</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let rec even n =\n match n with\n | 0 -> true\n | x -> odd (x-1)\n and odd n =\n match n with\n | 0 -> false\n | x -> even (x-1);;\nval even : int -> bool = <fun>\nval odd : int -> bool = <fun>\n\n# even 10;;\n- : bool = true\n# even 3;;\n- : bool = false\n# odd 3;;\n- : bool = true\n# odd 10;;\n- : bool = false\n</code></pre>\n<h3>匿名函数</h3>\n<h4>fun 参数名 = 表达式</h4>\n<ul>\n<li>let f 参数 = 表达式 是 let f = fun 参数 = 表达式 的语法糖</li>\n<li>因为 fun 是尽可能地认识到它是一个函数定义,所以最好在必要时用()分割它。</li>\n</ul>\n<h3>高阶函数</h3>\n<p>定义一个函数作为参数</p>\n<pre><code class=\"language-ocaml\"># let twice f x = f (f x);;\nval twice : ('a -> 'a) -> 'a -> 'a = <fun>\n# twice (fun x -> x * x) 3;;\n- : int = 81\n\n# let fourth x =\n let square y = y * y in\n twice square x;;\nval fourth : int -> int = <fun>\n# fourth 3;;\n- : int = 81\n</code></pre>\n<h3>柯里化函数</h3>\n<pre><code class=\"language-ocaml\"># let concat_curry s1 = fun s2 -> s1 ^ s2 ^ s1;;\nval concat_curry : string -> string -> string = <fun>\n# concat_curry "a";; (* 部分適用 *)\n- : string -> string = <fun>\n# (concat_curry "a") "b";;\n- : string = "aba"\n</code></pre>\n<h3>柯里化语法糖</h3>\n<p>一下这个定义</p>\n<pre><code class=\"language-ocaml\">let concat_curry s1 s2 = s1 ^ s2 ^ s1;;\n</code></pre>\n<p>与以下相同</p>\n<pre><code class=\"language-ocaml\">let concat_curry s1 = fun s2 -> s1 ^ s2 ^ s1;;\n</code></pre>\n<p>也就是说,当参数排序的时候,以下代码</p>\n<pre><code class=\"language-ocaml\"># let fuga x y z = x + y + z;;\nval fuga : int -> int -> int -> int = <fun>\n</code></pre>\n<p>实际上可以展开为以下代码</p>\n<pre><code class=\"language-ocaml\"># let hoge x = fun y -> fun z -> x + y + z;;\nval hoge : int -> int -> int -> int = <fun>\n</code></pre>\n<p>函数是左结合,所以可以扩展如下:</p>\n<pre><code class=\"language-ocaml\">f x y z => (((f x) y) z)\n</code></pre>\n<p>函数类型构造函数是右结合的</p>\n<pre><code class=\"language-ocaml\">int - > int - > int - > int = <fun>\n解释为\nint - >(int - >(int - > int))= <fun>\n</code></pre>\n<h3>运算符定义</h3>\n<h4>中缀运算符被定义为柯里化函数。</h4>\n<pre><code class=\"language-ocaml\"># (+);;\n- : int -> int -> int = <fun>\n# (+) 1 2;;\n- : int = 3\n</code></pre>\n<h4>自己定义操作符时</h4>\n<h4>前缀运算符</h4>\n<ul>\n<li>1、定义为一个参数函数</li>\n</ul>\n<h4>中缀运算符</h4>\n<ul>\n<li>2、定义为参数(currying)函数</li>\n<li>请注意,可以用来定义运算符的字符类型是有限的。</li>\n</ul>\n<h3>类型推断</h3>\n<ul>\n<li>比较运算符(=等)可以使用多种类型,如整数类型和实数类型=>多态(多态)。</li>\n<li>具有多态性的类型系统被称为多态类型系统。</li>\n</ul>\n<h4>类型方案·参数多态性</h4>\n<ul>\n<li>一种表示一种类型的外观模式的变量。</li>\n<li>它表示为'a 或'b。</li>\n</ul>\n<pre><code class=\"language-ocaml\"># let twice f x = f (f x);;\nval twice : ('a -> 'a) -> 'a -> 'a = <fun>\n# let first (x, y) = x;;\nval first : 'a * 'b -> 'a = <fun>\n</code></pre>\n<h4>类型变量的泛化</h4>\n<ul>\n<li>将类型变量(如'_a)(类型方案,如'a')泛化的条件之一如下。</li>\n<li>如果定义右侧的表达式是一个值,则可以概括类型变量</li>\n<li>作为值处理的事物=>不需要计算的表达式</li>\n<li>作为一个例子</li>\n<li>函数声明</li>\n<li>常量</li>\n<li>需要计算的表达式不被视为值,因为它们是有价值的。</li>\n</ul>\n<pre><code class=\"language-ocaml\">(*定义身份函数*)\n# let id x = x ;;\nval id:'a - >'a = <fun>\n\n(*'_a不是一个类型方案\n 因为id是一个值,一些实际的参数是必需的,计算是必要的。\n*)\n# let id'= id id ;;\nval id':'_a - >'_a = <fun>\n\n(*\n 这种类型的方案\n 通过添加参数x,id'变成了函数定义。\n*)\n# let id'x = id id x ;;\nval id':'a - >'a = <fun>\n</code></pre>\n","author":"lanqy"},{"title":"编程规则和约定","created":"2018/07/05","link":"2018/07/05/erlang-programming-rules","description":"编程规则和约定","content":"<h1>编程规则和约定</h1>\n<p>译自:http://www.erlang.se/doc/programming_rules.shtml</p>\n<h2>使用 Erlang 进行程序开发 - 编程规则和约定</h2>\n<h3>抽象</h3>\n<blockquote>\n<p>这是对编程规则的描述,并建议如何使用 Erlang 编写系统。</p>\n</blockquote>\n<p>注意:本文档是初步文档,不完整。</p>\n<h4>这里没有记录使用EBC“基础系统”的要求,但如果要使用“基础系统”,必须在非常早期的设计阶段遵循。这些要求记录在1/10268-AND 10406 Uen“MAP - 启动和错误恢复”中。</h4>\n<h3>1、目的</h3>\n<p>本文列出了在使用 Erlang 指定和编程软件系统时应该考虑的一些方面。它并不试图给出与 Erlang 使用无关的一般规范和设计活动的完整描述。</p>\n<h3>2、结构和 Erlang 术语</h3>\n<p>Erlang 系统分为<em>模块</em>。模块由<em>函数</em>和<em>属性</em>组成。函数要么只在模块内可见,要么被<em>导出</em>,即它们也可以被其他模块中的其他函数调用。属性以“ - ”开头,并放在模块的开头。</p>\n<p>使用 Erlang 设计的系统中的工作是由<em>进程</em>完成的。进程是一个可以在许多模块中使用函数的工作。进程通过发送消息相互通信。进程接收发送给它们的消息,进程可以决定它准备接收哪些消息。其他消息排队,直到接收进程准备接收它们。</p>\n<p>进程可以通过建立到它的链接来监督另一个进程的存在。当进程终止时,它会自动将退出信号发送到与其链接的进程。接收退出信号的进程的默认行为是终止并将信号传播到其链接进程。进程可以通过捕获出口来更改此默认行为,这会导致发送到进程的所有退出信号变为消息。</p>\n<p>纯函数是一个函数,它在给定相同参数的情况下返回相同的值,而不管函数调用的上下文如何。这是我们通常对数学函数的期望。也就是说不纯的功能有副作用。</p>\n<p>如果函数 a)发送消息 b)接收消息 c)调用 exit d)调用任何改变进程环境或操作模式的 BIF,通常会出现副作用(例如 get/1,put/2,erase/1,process_flag/2 等)。</p>\n<p><em>警告</em>:本文档包含错误代码的示例。</p>\n<h3>3、SW 工程原理</h3>\n<h4>3.1 从模块导出尽可能少的函数</h4>\n<p>模块是 Erlang 中的基本代码结构实体。模块可以包含大量函数,但只能从模块外部调用模块导出列表中包含的函数。</p>\n<p>从外部看,模块的复杂性取决于从模块导出的函数的数量。导出一个或两个函数的模块通常比导出许多函数的模块更容易理解。</p>\n<p>导出/非导出函数的比率较低的模块是可取的,因为模块的用户只需要了解从模块导出的函数的功能。</p>\n<p>此外,如果外部接口保持不变,模块中代码的编写者或维护者可以以任何适当的方式改变模块的内部结构。</p>\n<h4>3.2 尝试减少模块间依赖性</h4>\n<p>与仅在几个不同模块中调用函数的模块相比,在许多不同模块中调用函数的模块将更难以维护。</p>\n<p>这是因为每次我们对模块接口进行更改时,我们都必须检查代码中调用此模块的所有位置。减少模块之间的相互依赖性简化了维护这些模块的问题。</p>\n<p>我们可以通过减少从给定模块调用的不同模块的数量来简化系统结构。</p>\n<p>还要注意,期望模块间调用依赖性形成树而不是循环图。例:\n<img src=\"/images/module-dep-ok.gif\" alt=\"好\" /></p>\n<p>但不是</p>\n<p><img src=\"/images/module-dep-bad.gif\" alt=\"坏\" /></p>\n<h4>3.3 将常用代码放入库中</h4>\n<p>应将常用代码放入库中。库应该是相关函数的集合。应该尽力确保库包含相同类型的函数。因此,诸如仅包含用于操纵列表的函数的列表的库是一个不错的选择,而包含用于操纵列表和用于数学的函数的组合的库,lists_and_maths 是非常糟糕的选择。</p>\n<p>最好的库函数没有副作用。具有副作用函数的库限制了可重用性。</p>\n<h4>3.4 将“棘手”或“脏”代码隔离到单独的模块中</h4>\n<p>通常可以通过使用干净和脏代码的混合来解决问题。将干净和脏的代码分成单独的模块。</p>\n<p>脏代码是做坏事的代码。例:</p>\n<ul>\n<li>使用进程字典。</li>\n<li>使用 erlang:process_info/1 用于奇怪的目的。</li>\n</ul>\n<p>专注于尝试最大化干净代码的数量,并尽量减少脏代码的数量。隔离脏代码并清楚地评论或以其他方式记录与此部分代码相关的所有副作用和问题。</p>\n<h4>3.5 不要假设调用者将对函数的结果做什么</h4>\n<p>不要假设函数被调用的原因或者函数的调用者希望对结果做什么。</p>\n<p>例如,假设我们使用某些可能无效的参数调用例程。例程的实现者不应该在参数无效时对函数的调用者希望发生什么做出任何假设。</p>\n<p>因此我们不应该写这样的代码</p>\n<pre><code class=\"language-erlang\">do_something(Args) ->\n case check_args(Args) of\n ok -> \n {ok, do_it(Args)};\n {error, What} ->\n String = format_the_error(What),\n io:format("* error:~s\\n", [String]), %% 不要这样做\n error\n end.\n</code></pre>\n<p>而是这样写:</p>\n<pre><code class=\"language-erlang\">do_something(Args) ->\n case check_args(Args) of\n ok -> \n {ok, do_it(Args)};\n {error, What} ->\n {error, What}\n end.\n\nerror_report({error, What}) ->\n format_the_error(What).\n</code></pre>\n<p>在前一种情况下,错误字符串始终打印在标准输出上,在后一种情况下,错误描述符将返回给应用程序。应用程序现在可以决定如何处理此错误描述符。</p>\n<p>通过调用 error_report/1,应用程序可以将错误描述符转换为可打印的字符串,并在需要时将其打印出来。但这可能不是理想的行为 - 在任何情况下,决定如何处理结果都留给调用者。</p>\n<h4>3.6 抽象出代码或行为的常见模式</h4>\n<p>每当在代码中的两个或多个位置具有相同的代码模式时,尝试将其与公共函数隔离并调用此函数,而不是将代码放在两个不同的位置。复制的代码需要很多维护。</p>\n<p>如果您在代码中的两个或多个位置看到类似的代码模式(即几乎相同),则值得花一些时间来查看是否有人不能稍微改变问题以使不同的情况相同,然后写入少量额外的 代码来描述两者之间的差异。</p>\n<p>避免“复制”和“粘贴”编程,使用函数!</p>\n<h4>3.7 自顶向下</h4>\n<p>使用自上而下的方式编写程序,而不是自下而上(从详细信息开始)。自上而下是一种不断接近实现细节的好方法,最终定义了原始函数。代码将独立于表示,因为在设计更高级别的代码时不知道表示。</p>\n<h4>3.8 不要优化代码</h4>\n<p>不要在第一阶段优化代码。首先让它正确,然后(如果必要)让它快速(同时保持正确)。</p>\n<h4>3.9 使用“最小惊讶”原则</h4>\n<p>系统应始终以对用户造成“最小惊讶”的方式作出响应 - 即,用户应该能够预测当他们做某事时会发生什么并且不会对结果感到惊讶。</p>\n<p>这与一致性有关,这是一个一致的系统,其中不同的模块以类似的方式执行操作比每个模块以不同方式执行操作的系统更容易理解。</p>\n<p>如果你对函数的作用感到惊讶,你的函数可以解决错误的问题,也可能名字错误。</p>\n<h4>3.10 尽量消除副作用</h4>\n<p>Erlang 有几个具有副作用的原语。使用它们的函数不能轻易地重复使用,因为它们会导致对环境的永久性更改,并且在调用此类例程之前必须知道进程的确切状态。</p>\n<p>使用无副作用的代码尽可能多地编写代码。</p>\n<p>最大化纯函数的数量。</p>\n<p>收集具有副作用的功能并清楚地记录所有副作用。</p>\n<p>有点小心,大多数代码都可以以无副作用的方式编写 - 这将使系统更易于维护,测试和理解。</p>\n<h4>3.11 不允许私有数据结构“泄漏”出模块</h4>\n<p>最好通过一个简单的例子来说明这一点。我们定义了一个名为 queue 的简单模块 - 来实现队列:</p>\n<pre><code class=\"language-erlang\">-module(queue).\n-export([add/2, fetch/1]).\n\nadd(Item, Q) ->\n lists:append(Q, [Item]).\n\nfetch(H|T) ->\n {ok, H, T};\nfetch([]) -> \n empty.\n</code></pre>\n<p>这将队列作为列表实现,遗憾的是,用户必须知道该队列被表示为列表。使用它的典型程序可能包含以下代码片段:</p>\n<pre><code class=\"language-erlang\">NewQ = [], % 不要这样做\nQueue1 = queue:add(joe, NewQ), \nQueue2 = queue:add(mike, Queue1), ....\n</code></pre>\n<p>这很糟糕 - 因为用户a)需要知道队列被表示为列表而b)实现者不能改变队列的内部表示(他们可能希望稍后这样做以提供更好的模块版本) 。</p>\n<p>更好的是:</p>\n<pre><code class=\"language-erlang\">-module(queue).\n-export([new/0, add/2, fetch/1]).\n\nnew() ->\n [].\n\nadd(Item, Q) ->\n lists:append(Q, [Item]).\n\nfetch([H|T]) ->\n {ok, H, T};\nfetch([]) ->\n empty.\n</code></pre>\n<p>现在我们可以写:</p>\n<pre><code class=\"language-erlang\">NewQ = queue:new(), \nQueue1 = queue:add(joe, NewQ), \nQueue2 = queue:add(mike, Queue1), ...\n</code></pre>\n<p>哪个更好,并纠正这个问题。现在假设用户需要知道队列的长度,他们可能会想写:</p>\n<pre><code class=\"language-erlang\">Len = length(Queue) % 不要这样做\n</code></pre>\n<p>因为他们知道队列被表示为列表。再次,这是糟糕的编程实践,导致代码很难维护和理解。如果需要知道队列的长度,则必须将长度函数添加到模块中,因此:</p>\n<pre><code class=\"language-erlang\">-module(queue).\n-export([new/0, add/2, fetch/1, len/1]).\n\nnew() -> [].\n\nadd(Item, Q) ->\n lists:append(Q, [Item]).\n\nfetch([H|T]) ->\n {ok, H, T};\n\nfetch([]) ->\n empty.\n\nlen(Q) ->\n length(Q).\n</code></pre>\n<p>现在用户可以调用 queue:len(Queue) 代替。</p>\n<p>这里我们说我们“抽象出”队列的所有细节(队列实际上是所谓的“抽象数据类型”)。</p>\n<p>为什么我们会遇到这些麻烦? - 抽象出实现的内部细节的做法允许我们改变实现而不改变调用我们已经改变的模块中的函数的模块的代码。因此,例如,更好的队列实现如下:</p>\n<pre><code class=\"language-erlang\">-module(queue).\n-export([new/0, add/2, fetch/1, len/1]).\n\nnew() ->\n {[], []}.\n\nadd(Item, {X, Y}) -> % 更快地添加元素\n {[Item|X], Y}.\n\nfetch({X, [H|T]}) ->\n {ok, H, {X, Y}}.\n\nfetch({[], []}) ->\n empty;\n\nfetch({X, []}) ->\n % 有时只执行这个繁重的计算。\n fetch({[], lists:reverse(X)}).\n\nlen({X, Y}) ->\n length(X) + length(Y).\n</code></pre>\n<h4>3.12 使代码尽可能具有确定性</h4>\n<p>无论程序运行多少次,确定性程序总是以相同的方式运行。非确定性程序可能在每次运行时提供不同的结果。出于调试目的,最好使事物尽可能确定。这有助于使错误重现。</p>\n<p>例如,假设一个进程必须启动五个并行进程,然后检查它们是否已正确启动,进一步假设启动这五个进程的顺序无关紧要。</p>\n<p>然后,我们可以选择并行启动所有五个,然后检查它们是否已正确启动,但最好一次启动一个并检查每个启动是否正确,然后再启动下一个。</p>\n<h4>3.13 不要“防守”编程</h4>\n<p>防御性程序是程序员不“信任”输入数据到他们正在编程的系统部分的程序。通常,不应将输入数据测试到函数的正确性。系统中的大多数代码都应该假设所讨论的函数的输入数据是正确的。只有一小部分代码应该实际执行任何数据检查。这通常是在数据第一次“进入”系统时完成的,一旦检查了数据,因为它进入系统后应该假定它是正确的。</p>\n<p>例:</p>\n<pre><code class=\"language-erlang\">%% Args: Option is all|normal\nget_server_usage_info(Option, AsciiPid) ->\n Pid = list_to_pid(AsciiPid),\n case Option of\n all -> get_all_info(Pid);\n normal -> get_normal_info(Pid)\n end.\n</code></pre>\n<p>如果 Option 既不正常也不是全部,该函数将崩溃,它应该这样做。呼叫者负责提供正确的输入。</p>\n<h4>3.14 使用设备驱动程序隔离硬件接口</h4>\n<p>应通过使用设备驱动程序将硬件与系统隔离。设备驱动程序应该实现硬件接口,使硬件看起来像是 Erlang 进程。应该使硬件看起来和行为像普通的 Erlang 进程一样。硬件应该看起来接收和发送正常的 Erlang 消息,并且应该在发生错误时以传统方式响应。</p>\n<h4>3.15 在同一个函数中执行和撤消操作</h4>\n<p>假设我们有一个程序打开一个文件,用它做一些事情并稍后关闭它。这应编码为:</p>\n<pre><code class=\"language-erlang\">do_something_with(File) ->\n case file:open(File, read) of,\n {ok, Stream} ->\n doit(Stream),\n file:close(Stream) % The correct solution\n Error -> Error\n end.\n</code></pre>\n<p>请注意在同一例程中打开文件(file:open)和关闭文件(file:close)的对称性。下面的解决方案更难以遵循,并且关闭哪个文件并不明显。不要这样编程:</p>\n<pre><code class=\"language-erlang\">do_something_with(File) -> \n case file:open(File, read) of, \n {ok, Stream} ->\n doit(Stream)\n Error -> Error\n end.\n\ndoit(Stream) -> \n ...., \n func234(...,Stream,...).\n ...\n\nfunc234(..., Stream, ...) ->\n ...,\n file:close(Stream) %% 不要这样做\n</code></pre>\n<h3>4、错误处理</h3>\n<h4>4.1 单独的错误处理和正常的案例代码</h4>\n<p>不要使用设计用于处理异常的代码来填充“正常情况”的代码。你应该尽可能只编程正常情况。如果正常情况的代码失败,您的进程应该报告错误并尽快崩溃。不要尝试修复错误并继续。应该在不同的过程中处理错误(参见第15页的“每个过程应该只有一个”角色“)</p>\n<p>清除错误恢复代码和正常案例代码应大大简化整个系统设计。</p>\n<p>检测到软件或硬件错误时生成的错误日志将在稍后阶段用于诊断和更正错误。应保留对此过程有帮助的任何信息的永久记录。</p>\n<h4>4.2 识别错误内核</h4>\n<p>系统设计的基本要素之一是确定系统的哪个部分必须是正确的,以及系统的哪个部分不必是正确的。</p>\n<p>在传统的操作系统设计中,假定系统的内核是并且必须是正确的,而所有用户应用程序不一定必须是正确的。如果用户应用程序失败,则仅涉及发生故障的应用程序,但不应影响整个系统的完整性。</p>\n<p>系统设计的第一部分必须是识别必须正确的系统部分;我们称之为错误内核。错误内核通常具有某种实时内存驻留数据库,用于存储硬件状态。</p>\n<h3>5、进程,服务器和消息</h3>\n<h4>5.1 在一个模块中实现进程</h4>\n<p>实现单个进程的代码应包含在一个模块中。进程可以调用任何库例程中的函数,但进程的“顶部循环”的代码应该包含在单个模块中。进程顶部循环的代码不应分成几个模块 - 这将使控制流程极难理解。这并不意味着不应该使用通用服务器库,这些库用于帮助构建控制流。</p>\n<p>相反,应该在单个模块中实现不超过一种进程的代码。包含几个不同过程的代码的模块可能非常难以理解。应将每个单独流程的代码分解为单独的模块。</p>\n<h4>5.2 使用进程来构建系统</h4>\n<p>进程是基本的系统结构元素。但是,当可以使用函数调用时,不要使用进程和消息传递。</p>\n<h4>5.3 注册进程</h4>\n<p>注册的进程应使用与模块相同的名称进行注册。这样可以轻松找到进程的代码。只注册应该存在很长时间的进程。</p>\n<h4>5.4 为系统中的每个真正并发活动分配一个并行进程</h4>\n<p>在决定是否使用顺序或并行进程实现事物时,应该使用问题的内在结构所隐含的结构。主要规则是: “使用一个并行进程来模拟现实世界中每个真正的并发活动”</p>\n<p>如果并行进程的数量与现实世界中真正并行活动的数量之间存在一对一的映射,则程序将易于理解。</p>\n<h4>5.5 每个进程应该只有一个“角色”</h4>\n<p>进程可以在系统中具有不同的角色,例如在客户端 - 服务器模型中。</p>\n<p>进程应该尽可能只有一个角色,即它可以是客户端或服务器但不应该组合这些角色。</p>\n<p>进程可能具有的其他角色是:</p>\n<ul>\n<li>主管:监视其他进程并在失败时重新启动它们。</li>\n<li>工人:正常的工作进程(可能有错误)。</li>\n<li>值得信赖的工人:不允许有错误。</li>\n</ul>\n<h4>5.6 尽可能使用服务器和协议处理程序的通用函数</h4>\n<p>在许多情况下,最好使用通用服务器程序,例如标准库中实现的 generic 服务器。一致使用一小组通用服务器将大大简化整个系统结构。</p>\n<p>对于系统中的大多数协议处理软件也是如此。</p>\n<h4>5.7 标记消息</h4>\n<p>应标记所有消息。这使得 receive 语句中的顺序不那么重要,并且新消息的实现更容易。</p>\n<p>不要像这样编程:</p>\n<pre><code class=\"language-erlang\">loop(State) ->\n receive\n ...\n {Mod, Funcs, Args} -> % 不要这样做\n apply(Mod, Funcs, Args),\n loop(State);\n ...\n end.\n</code></pre>\n<p>如果将新消息 {get_status_info, From, Option} 置于 {Mod, Func, Args} 消息下方,则会引发冲突。</p>\n<p>如果消息是同步的,则返回消息应使用新原子标记,描述返回的消息。示例:如果传入消息标记为 get_status_info,则返回的消息可以标记为 status_info。选择不同标签的一个原因是使调试更容易。</p>\n<p>这是一个很好的解决方案:</p>\n<pre><code class=\"language-erlang\">loop(State) ->\n receive\n ...\n {execute, Mod, Funcs, Args} -> % 使用标记的消息。\n apply(Mod, Funcs, Args),\n loop(State);\n {get_status_info, From, Option} ->\n From ! {status_info, get_status_info(Option, State)},\n loop(State);\n ...\n end.\n</code></pre>\n<h4>5.8 刷新未知消息</h4>\n<p>每个服务器在至少一个 receive 语句中应该有一个 Other 选项。这是为了避免填充消息队列。例:</p>\n<pre><code class=\"language-erlang\">main_loop() ->\n receive\n {msg1, Msg1} ->\n ...,\n main_loop();\n {msg2, Msg2} ->\n ...\n main_loop();\n Other -> %刷新消息队列。\n error_logger:error_msg(\n "Error: Process ~w got unknown msg ~w~n.",\n [self(), Other]),\n main_loop()\n end.\n</code></pre>\n<h4>5.9 编写尾递归服务器</h4>\n<p>所有服务器必须是尾递归的,否则服务器将占用内存,直到系统用完为止。</p>\n<p>不要像这样编程:</p>\n<pre><code class=\"language-erlang\">loop() ->\n receive\n {msg1, Msg1} ->\n ...,\n loop();\n stop ->\n true;\n Other ->\n error_logger:log({error, {process_got_other, self(), Other}}),\n loop\n end,\n io:format("Server going down"). % 不要这样做!\n % 这不是尾递归\n</code></pre>\n<p>这是一个正确的解决方案:</p>\n<pre><code class=\"language-erlang\">loop() ->\n receive\n {msg1, Msg1} ->\n ...\n loop();\n stop ->\n io:format("Server going down");\n Other ->\n error_logger:log({error, {process_got_other, self(), Other}}),\n loop()\n end. % 这是尾递归\n</code></pre>\n<p>如果您使用某种服务器库,例如 generic ,则会自动避免出现此错误。</p>\n<h4>5.10 接口函数</h4>\n<p>尽可能使用接口函数,避免直接发送消息。封装传递到接口函数的消息。有些情况下你不能这样做。</p>\n<p>消息协议是内部信息,应该隐藏到其他模块。</p>\n<p>接口函数示例:</p>\n<pre><code class=\"language-erlang\">-module(fileserver).\n-export([start/0, stop/0, open_file/1, ...]).\n\nopen_file(FileName) ->\n fileserver ! {open_file_request, FileName},\n receive\n {open_file_response, Result} -> Result\n end.\n\n...<code>...\n</code></pre>\n<h4>5.11 超时</h4>\n<p>在 receive 语句中使用 after 要小心。确保在以后收到消息时处理该情况(请参见第 16 页的“刷新未知消息”)。</p>\n<h4>5.12 陷阱退出</h4>\n<p>尽可能少的进程应该捕获退出信号。进程要么退出,要么不退出。对于进程来说,“切换”捕获出口通常是非常糟糕的做法。</p>\n<h3>6、各种 Erlang 特定约定</h3>\n<h4>6.1 使用记录作为主要数据结构</h4>\n<p>使用记录作为主要数据结构。记录是标记的元组,并在 Erlang 版本 4.3 及其后引入(参见 EPK/NP 95:034)。它类似于 C 中的 struct 或 Pascal 中的 record。如果要在多个模块中使用该记录,则应将其定义放在模块中包含的头文件(带后缀.hrl)中。如果记录仅在一个模块中使用,则记录的定义应位于定义模块的文件的开头。</p>\n<p>Erlang 的记录功能可用于确保数据结构的跨模块一致性,因此在模块之间传递数据结构时应由接口函数使用。</p>\n<h4>6.2 使用选择器和构造函数</h4>\n<p>使用记录功能提供的选择器和构造函数来管理记录实例。不要使用明确假设记录是元组的匹配。例:</p>\n<pre><code class=\"language-erlang\">demo() ->\n P = #person{name = "Joe", age = 29},\n #person{name = Name1} = P, % 使用匹配,或......\n Name2 = P#person.name. % 像这样使用选择器。\n</code></pre>\n<p>不要像这样编程:</p>\n<pre><code class=\"language-erlang\">demo() ->\n P = #person{name = "Joe", age = 29},\n {person, Name, _Age, _Phone, _Misc} = P. % 不要这样做\n</code></pre>\n<h4>6.3 使用标记的返回值</h4>\n<p>使用标记的返回值。</p>\n<p>不要像这样编程:</p>\n<pre><code class=\"language-erlang\">keysearch(Key, [{Key, Value}|_Tail]) ->\n Value; %% 不要返回未标记的值!\nkeysearch(Key, [{_WrongKey, _WrongValue} | Tail]) ->\n keysearch(Key, Tail);\nKeysearch(Key, []) ->\n false.\n</code></pre>\n<p>然后 {Key, Value} 不能包含 false 值。这是正确的解决方案:</p>\n<pre><code class=\"language-erlang\">keysearch(Key, [{Key, Value} | _Tail]) ->\n {value, Value}; %% 正确。返回标记值。\nkeysearch(Key, [{_WrongKey, _WrongValue}, | Tail]) ->\n keysearch(Key, Tail);\nkeysearch(Key, []) ->\n false.\n</code></pre>\n<h4>6.4 小心使用捕获和抛异常</h4>\n<p>除非你确切知道自己在做什么,否则不要使用 catch 和 throw!使用 catch 和 throw 尽可能少。</p>\n<p>当程序处理复杂且不可靠的输入(来自外部世界,而不是来自您自己的可靠程序)时,catch 和 throw 可能很有用,这可能会导致代码中很多地方出现错误。一个例子是编译器。</p>\n<h4>6.5 小心使用进程字典</h4>\n<p>不要使用 get 和 put 等。除非你确切知道你在做什么!尽可能少地使用 get 和 put 等。</p>\n<p>可以通过引入新参数来重写使用进程字典的函数。</p>\n<p>例: 不要像这样编程:</p>\n<pre><code class=\"language-erlang\">tokenize([H|T]) ->\n ...\ntokenize([]) ->\n case get_characters_from_device(get(device)) of % 不要使用 get/1!\n eof -> [];\n {value, Chars} ->\n tokenize(Chars)\n end.\n</code></pre>\n<p>正确的解决方案:</p>\n<pre><code class=\"language-erlang\">tokenize(_Device, [H|T]) ->\n ...;\ntokenize(Device, []) ->\n case get_characters_from_device(Device) of % 这个更好\n eof -> [];\n {value, Chars} ->\n tokenize(Device, Chars)\n end.\n</code></pre>\n<p>他使用 get 和 put 会导致函数在不同的场合使用相同的输入调用时表现不同。这使得代码难以阅读,因为它是非确定性的。调试将更复杂,因为使用 get 和 put 的函数不仅是其参数的函数,还是进程字典的函数。Erlang 中的许多运行时错误(例如 bad_match )包含函数的参数,但不包括进程字典。</p>\n<h4>6.6、不要使用导入</h4>\n<p>不要使用 -import,使用它会使代码更难阅读,因为您无法直接查看函数定义的模块。使用 exref(交叉引用工具)查找模块依赖项。</p>\n<h4>6.7 导出函数</h4>\n<p>区分函数的导出原因。可以导出函数,原因如下(例如):</p>\n<ul>\n<li>它是模块的用户界面。</li>\n<li>它是其他模块的接口函数。</li>\n<li>它从 apply,spawn 等调用,但仅在其模块中调用。</li>\n</ul>\n<p>使用不同的 -export 分组并相应地注释它们。例:</p>\n<pre><code class=\"language-erlang\">%% 用户界面\n-export([help/0, start/0, stop/0, info/1]).\n\n%% 模块间导入\n-export([make_pid/1, make_pid/3]).\n-export([process_abbrevs/0, print_info/5]).\n\n%% 导出仅在模块内使用\n-export([init/1, info_log_impl/1]).\n</code></pre>\n<h3>7、具体的词汇和风格的约定。</h3>\n<h4>7.1 不要编写深层嵌套代码</h4>\n<p>嵌套代码是包含其他case / if / receive语句中的case / if / receive语句的代码。编写深层嵌套代码是一种糟糕的编程风格 - 代码倾向于在页面中向右移动并很快变得不可读。尝试将大多数代码限制为最多两个缩进级别。这可以通过将代码分成更短的函数来实现。</p>\n<h4>7.2 不要写非常大的模块</h4>\n<p>模块不应包含超过 400 行的源代码。最好分成几个小模块而不是一个大模块。</p>\n<h4>7.3 不要写很长的函数</h4>\n<p>不要编写超过 15 到 20 行代码的函数。将大函数分成几个较小的函数。不要通过写长行来解决问题。</p>\n<h4>7.4 不要写很长的行</h4>\n<p>不要写很长的行。一行不应超过 80 个字符。 (例如,它将适合A4页面。)</p>\n<p>在 Erlang 4.3 及其后,字符串常量将自动连接。例:</p>\n<pre><code class=\"language-erlang\">io:format("Name: ~s, Age: ~w,Phone: ~w ~n"\n "Dictionary: ~w.~n", [Name, Age, Phone, Dict])\n</code></pre>\n<h4>7.5 变量名称</h4>\n<p>选择有意义的变量名称 - 这非常困难。</p>\n<p>如果变量名称由多个单词组成,请使用“_”或大写字母将它们分开。例如:My_variable 或 MyVariable。</p>\n<p>避免使用“<em>” 作为无关变量,使用以 “</em>”开头的变量。例如:_Name。如果您在稍后阶段需要变量的值,则只需删除前导下划线。您将无法找到替换下划线的问题,并且代码将更易于阅读。</p>\n<h4>7.6 函数名称</h4>\n<p>函数名称必须与函数的名称完全一致。它应该返回函数名隐含的参数类型。它不应该让读者感到惊讶。使用传统函数的常规名称(start,stop,init,main_loop)。</p>\n<p>解决相同问题的不同模块中的函数应具有相同的名称。例如:Module:module_info()。</p>\n<p>错误的函数名称是最常见的编程错误之一 - 很好的选择名称是非常困难的!</p>\n<p>在编写许多不同的函数时,某种命名约定非常有用。例如,名称前缀“is_”可用于表示所讨论的函数返回原子 true 或 false</p>\n<pre><code class=\"language-erlang\">if_...() -> true | false\ncheck_...() -> {ok, ...} || {error, ...}\n</code></pre>\n<h4>7.7 模块名称</h4>\n<p>Erlang 具有扁平模块结构(即模块内没有模块)。但是,我们通常希望模拟分层模块结构的效果。这可以通过具有相同模块前缀的多组相关模块来完成。</p>\n<p>例如,如果使用五个不同且相关的模块实现ISDN处理程序。这些模块的名称应如下:</p>\n<pre><code class=\"language-erlang\">isdn_init\nisdn_partb\nisdn_...\n</code></pre>\n<h4>7.8 以一致的方式格式化程序</h4>\n<p>一致的编程风格将帮助您和其他人理解您的代码。不同的人在缩进,使用空间等方面有不同的风格。例如,您可能希望在元素之间使用单个逗号编写元组:</p>\n<pre><code class=\"language-erlang\">{12,23,45}\n</code></pre>\n<p>其他人可能会使用逗号后跟空格:</p>\n<pre><code class=\"language-erlang\">{12, 23, 45}\n</code></pre>\n<p>一旦你采用了风格 - 坚持下去。</p>\n<p>在较大的项目中,应在所有部分使用相同的样式。</p>\n<h3>8、文档化代码</h3>\n<h4>8.1 属性代码</h4>\n<p>您必须始终正确地归因于模块标头中的所有代码。说出所有有关该模块的想法来自哪里 - 如果您的代码是从其他代码派生出来的,那么说明您从哪里获得此代码以及谁编写代码。</p>\n<p>永远不要窃取代码 - 窃取代码是从其他模块编辑代码并忘记说谁编写原始代码。</p>\n<p>有用属性的示例如下:</p>\n<pre><code class=\"language-erlang\">-revision('Revision: 1.14 ').\n-created('Date: 1995/01/01 11:21:11 ').\n-created_by('eklas@erlang').\n-modified('Date: 1995/01/05 13:04:07 ').\n-modified_by('mbj@erlang').\n</code></pre>\n<h4>8.2 在代码中提供参考规范</h4>\n<p>在代码中提供与理解代码相关的任何文档的交叉引用。例如,如果代码实现某些通信协议或硬件接口,则将文档和页码的精确引用提供给用于编写代码的文档。</p>\n<h4>8.3 记录所有错误</h4>\n<p>所有错误都应与单独文档中的含义的英文说明一起列出(请参阅第32页的“错误消息”。)</p>\n<p>错误是指系统检测到的错误。</p>\n<p>在程序中检测到逻辑错误的位置调用错误记录器:</p>\n<pre><code class=\"language-erlang\">error_logger:error_msg(Format, {Descriptor, Arg1, Arg2, ....})\n</code></pre>\n<p>并确保将 {Descriptor, Arg1, ...} 行添加到错误消息文档中。</p>\n<h4>8.4 记录消息中的所有主要数据结构</h4>\n<p>在系统的不同部分之间发送消息时,使用标记的元组作为主要数据结构。</p>\n<p>Erlang 的记录功能(在 Erlang 版本 4.3 s及其后版本中引入)可用于确保数据结构的跨模块一致性。</p>\n<p>应记录所有这些数据结构的英文描述(参见第32页的“消息描述”)。</p>\n<h4>8.5 注释</h4>\n<p>注释应清晰简洁,避免不必要的冗长。确保注释与代码保持同步。检查注释是否增加了对代码的理解。注释应以英文撰写。</p>\n<p>关于模块的注释应该没有缩进,并以三个百分比的字符(%%%)开头,(参见第29页的“文件头,描述”)。</p>\n<p>关于函数的注释应该没有缩进,并以两个百分比字符(%%)开头,(参见第27页的“注释每个函数”)。</p>\n<p>Erlang 代码中的注释应以一个百分比字符(%)开头。如果一行只包含注释,则它应缩进为 Erlang 代码。这种评论应放在它所引用的陈述之上。如果注释可以与语句放在同一行,则首选。</p>\n<pre><code class=\"language-erlang\">%% Comment about function\nsome_useful_functions(UsefulArgugument) ->\n another_functions(UsefulArgugument), % Comment at end of line\n % Comment about complicated_stmnt at the same level of indentation\n complicated_stmnt,\n......\n</code></pre>\n<h4>8.6、注释每个函数</h4>\n<p>要记录的重要事项是:</p>\n<ul>\n<li>函数的目的。</li>\n<li>函数的有效输入域。也就是说,函数参数的数据结构及其含义。</li>\n<li>函数输出的域。也就是说,返回值的所有可能数据结构及其含义。</li>\n<li>如果函数实现了复杂的算法,请对其进行描述。</li>\n<li>可能由exit / 1,throw / 1或任何非明显的运行时错误产生的故障和退出信号的可能原因。注意失败和返回错误之间的区别。</li>\n<li>函数的任何副作用。</li>\n</ul>\n<p>例如:</p>\n<pre><code class=\"language-erlang\">%%----------------------------------------------------------------------\n%% 函数: get_server_statistics/2\n%% 目的: 从进程中获取各种信息.\n%% 参数: Option is normal|all.\n%% 返回: 列表 {Key, Value} \n%% or {error, Reason} (如果进程死掉)\n%%----------------------------------------------------------------------\nget_server_statistics(Option, Pid) when pid(Pid) ->\n</code></pre>\n<h4>8.7 数据结构</h4>\n<p>记录应与计划文本描述一起定义。例:</p>\n<pre><code class=\"language-erlang\">%% File: my_data_structures.h\n\n%%---------------------------------------------------------------------\n%% Data Type: person\n%% where:\n%% name: A string (default is undefined).\n%% age: An integer (default is undefined).\n%% phone: A list of integers (default is []).\n%% dict: A dictionary containing various information about the person. \n%% A {Key, Value} list (default is the empty list).\n%%----------------------------------------------------------------------\n-record(person, {name, age, phone = [], dict = []}).\n</code></pre>\n<h4>8.8 文件头,版权所有</h4>\n<p>每个源代码文件必须以版权信息开头,例如:</p>\n<pre><code class=\"language-erlang\">%%%--------------------------------------------------------------------- \n%%% Copyright Ericsson Telecom AB 1996\n%%%\n%%% All rights reserved. No part of this computer programs(s) may be \n%%% used, reproduced,stored in any retrieval system, or transmitted,\n%%% in any form or by any means, electronic, mechanical, photocopying,\n%%% recording, or otherwise without prior written permission of \n%%% Ericsson Telecom AB.\n%%%--------------------------------------------------------------------- \n</code></pre>\n<h4>8.9 文件头,修订历史</h4>\n<p>每个源代码文件都必须记录其修订历史记录,该历史记录显示了谁在处理文件以及他们对文件做了什么。</p>\n<pre><code class=\"language-erlang\">%%%--------------------------------------------------------------------- \n%%% Revision History\n%%%--------------------------------------------------------------------- \n%%% Rev PA1 Date 960230 Author Fred Bloggs (ETXXXXX)\n%%% Intitial pre release. Functions for adding and deleting foobars\n%%% are incomplete\n%%%--------------------------------------------------------------------- \n%%% Rev A Date 960230 Author Johanna Johansson (ETXYYY)\n%%% Added functions for adding and deleting foobars and changed \n%%% data structures of foobars to allow for the needs of the Baz\n%%% signalling system\n%%%--------------------------------------------------------------------- \n</code></pre>\n<h4>8.10 文件头,描述</h4>\n<p>每个文件必须以文件中包含的模块的简短描述和所有导出函数的简要描述开头。</p>\n<pre><code class=\"language-erlang\">%%%--------------------------------------------------------------------- \n%%% Description module foobar_data_manipulation\n%%%--------------------------------------------------------------------- \n%%% Foobars are the basic elements in the Baz signalling system. The\n%%% functions below are for manipulating that data of foobars and for\n%%% etc etc etc\n%%%--------------------------------------------------------------------- \n%%% Exports\n%%%--------------------------------------------------------------------- \n%%% create_foobar(Parent, Type)\n%%% returns a new foobar object\n%%% etc etc etc\n%%%--------------------------------------------------------------------- \n</code></pre>\n<p><em>如果您知道任何弱点,错误,经过严格测试的函数,请在特别注释中记下它们,不要试图隐藏它们。如果模块的任何部分不完整,请添加特殊注释。</em> 添加有关对模块的未来维护者有帮助的任何内容的注释。如果您正在编写的模块的产品是成功的,那么在未来可能永远不会见到的人的十年内,它仍然可能会被更改和改进。</p>\n<h4>8.11 不要注释掉旧代码 - 删除它</h4>\n<p>在修订历史记录中添加注释。请记住,源代码控制系统将帮助您!</p>\n<h4>8.12 使用源代码控制系统</h4>\n<p>所有非平凡项目都必须使用源代码控制系统(如 RCS,CVS 或 Clearcase)来跟踪所有模块。</p>\n<h3>9、最常见的错误:</h3>\n<ul>\n<li>编写跨越多页的函数(请参阅第23页的“不要写很长的功能”)。</li>\n<li>使用深度嵌套的if、receive、case 等编写函数(请参见第23页的“不要编写深层嵌套代码”)。</li>\n<li>编写输入错误的函数(请参见第19页的“使用标记的返回值”)。</li>\n<li>函数名称不能反映函数的作用(参见第24页的“函数s名称”)。</li>\n<li>无意义的变量名称(请参见第23页的“变量名称”)。</li>\n<li>在不需要时处理进程(请参阅第14页的“为系统中的每个真正并发活动分配一个并行进程”)。</li>\n<li>选择错误的数据结构(错误表示)。</li>\n<li>糟糕的注释或根本没有注释(总是记录参数和返回值)。</li>\n<li>未缩进的代码。</li>\n<li>使用put / get(请参阅第20页的“小心使用进程字典”。)。</li>\n<li>无法控制消息队列(请参见第16页的“刷新未知消息”和第18页的“超时”)。</li>\n</ul>\n<h3>所需文件</h3>\n<p>本节描述了设计和维护使用Erlang编程的系统所必需的一些系统级文档。</p>\n<h4>10.1 模块描述</h4>\n<p>每个模块一章。包含每个模块的描述,以及所有导出的函数,如下所示:</p>\n<ul>\n<li>函数参数的含义和数据结构</li>\n<li>返回值的含义和数据结构。</li>\n<li>函数的目的</li>\n<li>可能由于显式调用exit / 1而产生的失败和退出信号的可能原因。</li>\n</ul>\n<p>稍后定义的文档格式:</p>\n<h4>10.2 消息描述</h4>\n<p>除一个模块内定义的消息之外的所有进程间消息的格式。</p>\n<p>稍后定义的文档格式:</p>\n<h4>10.3 进程</h4>\n<p>系统中所有已注册服务器的描述及其接口和用途。</p>\n<p>动态进程及其接口的描述。</p>\n<p>稍后定义的文档格式:</p>\n<h4>10.4 错误消息</h4>\n<p>错误消息的描述</p>\n<p>稍后定义的文档格式:</p>\n","author":"lanqy"},{"title":"在 ReasonReact 中创建全局状态","created":"2018/06/21","link":"2018/06/21/creating-global-state-in-reasonreact","description":"在 ReasonReact 中创建全局状态","content":"<h1>在 ReasonReact 中创建全局状态</h1>\n<p>译自:https://medium.com/@Hehk/creating-global-state-in-reasonreact-f84701c6ab6</p>\n<p>默认情况下,ReasonReact 为通过使用 Reducer 组件管理有状态组件提供了一个解决方案。Reducer 组件对于管理整个系统中的小部分状态非常好,但在更大的应用程序中遇到一些严重问题。具有更持久的全局状态,使用减速器体系结构并将状态提升到组件之外将提供两全其美。</p>\n<h2>设置一个基本的 Reducer 组件</h2>\n<p>ReasonReact 减速器组件是管理 ReasonReact 应用程序中的状态的基本方式。他们通过建立一个 state ,一系列 actions 和一个 reducer 来实现。</p>\n<ul>\n<li>state:组件的当前状态</li>\n<li>action:一组可以修改状态的动作</li>\n<li>reducer:一个函数,它需要一个动作和一个状态来计算一个新的状态。</li>\n</ul>\n<pre><code class=\"language-ocaml\">type state = {count: int};\n\n/* the actions */\ntype action = \n | Increment\n | Decrement;\n\nlet component = ReasonReact.reducerComponent("Counter");\n\nlet make = (_children) => {\n ...component,\n /* the state */\n initialState: () => {\n count: 0\n },\n\n /* the reducer */\n\n reducer: (action, state) => \n switch action {\n | Increment => ReasonReact.Update({count: state.count + 1})\n | Decrement => ReasonReact.Update({count: state.count - 1})\n },\n render: ({state, send}) =>\n <div>\n <h1> (ReasonReact.string(string_of_int(state.count))) </h1>\n <button onClick=((_e) => send(Increment))> (ReasonReact.string("+")) </button>\n <button onClick=((_e) => send(Decrement))> (ReasonReact.string("-")) </button>\n </div>\n}\n</code></pre>\n<p>这个组件是一个简单的计数器,它通过reducer组件体系结构进行变异。</p>\n<ol>\n<li>用户点击“+”按钮,发送增量动作</li>\n<li>减速器在动作和当前状态下被触发</li>\n<li>Reducer以新状态返回ReasonReact.Update</li>\n</ol>\n<p>就是这样,(除了背景布线)这个减速器组件是如何经历变异的。</p>\n<p>一个更复杂的例子是待办事项列表。</p>\n<pre><code class=\"language-ocaml\">type filter = \n | Completed\n | UnCompleted\n | None;\n\ntype item = {\n name: string,\n completed: bool\n};\n\ntype state = {\n input: string,\n items: list(item),\n filter\n};\n\nlet initialState = () => {\n input: "test",\n items: [{name: "initial item", completed: true}, {name: "kewl item", completed: false}],\n filter: None\n}\n\ntype action = \n | AddItem(item)\n | RemoveItem(item)\n | ChangeInput(string)\n | ToggleItem(string)\n | ChangeFilter(filter);\n \nlet reducer = (action, state) =>\n switch action {\n | AddItem(item) => ReasonReact.Update({...state, items: [item, ...state.items], input: ""})\n | RemoveItem(item) =>\n ReasonReact.Update({...state, items: List.filter((elem: item) => elem.name != item.name, state.items)})\n | ChangeInput(input) => ReasonReact.Update({...state, input})\n | ToggleItem(name) => ReasonReact.Update({\n ...state,\n items: List.map((item) => name == item.name ? {...item, completed: !item.completed }: item, state.items)\n })\n | ChangeFilter(filter) => ReasonReact.Update({...state, filter})\n };\n\nlet createItem = (~name, ()) => {name, completed: false};\n\nlet component = ReasonReact.reducerComponent("App");\n\nlet make = (_children) => {\n ...component,\n render: ({state, send}) => \n <div>\n <input value=state.input onChange=((e) => send(ChangeInput(Obj.magic(e)##target##value))) />\n <button onClick=((_e) => send(AddItem(createItem(~name=state.input, ()))))>\n (ReasonReact.string("add"))s\n </button>\n </div>\n}\n\n</code></pre>\n<p>虽然这个例子中有很多代码,但系统实际上相当简单。所有操作都是 actions,它们通过 reducer 映射到简单的状态更改。如果你感到困惑,我会建议你走进一个动作的生命周期,这些部分应该是有意义的。</p>\n<p>我强烈建议您阅读文档,他们会详细解释不同类型的更新以及与 reducer 组件交互的更有趣方式。https://reasonml.github.io/reason-react/docs/en/state-actions-reducer.html</p>\n<h2>减速器组件的缺点</h2>\n<p>上述示例的最大问题是状态本质上与组件绑定。例如,如果删除组件,则状态将丢失,如果丢失状态,组件将从其初始状态重新启动。在处理路由等问题时,这种短暂的状态成为一个巨大的问题,因为无法在URL更改之间维护您的状态。</p>\n<h2>你怎么解决这个问题</h2>\n<p>全局状态,通过将状态从这些短暂的组件中移出,我们可以创建一个可以轻松处理被删除和重新创建的应用程序。</p>\n<p>怎么样?简单,减速器组件! :P</p>\n<p>上面的体系结构可以通过创建处理状态的父组件向上移动到组件树中,并且仅向下传递我们获得两全其美所需的内容。减速器组件模型和状态与短暂组件分离。</p>\n<pre><code class=\"language-ocaml\">type element =\n | Todo\n | Counter;\n\n/* 将 state 合并为一个全球 state */\ntype state = {\n todo: Todo.state,\n counter: Counter.state,\n activeElement: element\n};\n\n/* 将来自 todo 和 counter 的动作组合到全局动作包装器中 */\ntype action =\n | Todo(Todo.action)\n | Counter(Counter.action)\n | ChangeElement(element);\n\nlet component = ReasonReact.reducerComponent("App");\n\nlet make = (_children) => {\n ...component,\n /* 结合所有初始状态 */\n initialState: () => {todo: Todo.initialState(), counter: Counter.initialState(), activeElement: Todo},\n /* 让组件减速器处理状态部分 */\n reducer: (action, state) =>\n switch action {\n | Todo(todoAction) => ReasonReact.Update({...state, todo: Todo.reducer(todoAction, state.todo)})\n | Counter(counterAction) =>\n ReasonReact.Update({...state, counter: Counter.reducer_2(counterAction, state.counter)})\n | ChangeElement(element) => ReasonReact.Update({...state, activeElement: element})\n },\n render: ({state, send}) =>\n <div>\n <button onClick=((_e) => send(ChangeElement(Todo)))> (ReasonReact.stringToElement("todo")) </button>\n <button onClick=((_e) => send(ChangeElement(Counter)))> (ReasonReact.stringToElement("counter")) </button>\n <h1>(ReasonReact.stringToElement("Component"))</h1>\n (\n /* 将一段状态传递给组件 */\n switch state.activeElement {\n | Todo => <Todo todoState=state.todo dispatch=((action) => send(Todo(action))) />\n | Counter => <Counter counterState=state.counter dispatch=((action) => send(Counter(action))) />\n }\n )\n </div>\n};\n</code></pre>\n<p>这确实需要对原始组件进行微小修改。</p>\n<pre><code class=\"language-ocaml\">type state = {count: int};\n\n/* 将初始状态移出组件 */\nlet initialState = () => {count: 0};\n\ntype action =\n | Increment\n | Decrement;\n\n/* 将 reducer 移出组件 */\nlet reducer = (action, state) =>\n switch action {\n | Increment => {count: state.count + 1}\n | Decrement => {count: state.count - 1}\n };\n\nlet component = ReasonReact.statelessComponent("Counter");\n\nlet make = (~dispatch, ~counterState, _children) => {\n ...component,\n render: (_self) =>\n <div>\n <h1> (ReasonReact.stringToElement(string_of_int(counterState.count))) </h1>\n /* 使用传入的调度函数而不是self.send */\n <button onClick=((_e) => dispatch(Increment))> (ReasonReact.stringToElement("+")) </button>\n <button onClick=((_e) => dispatch(Decrement))> (ReasonReact.stringToElement("-")) </button>\n </div>\n};\n</code></pre>\n<p>如果你想看到 todo 组件,它在最后链接的源代码中,应用程序托管在http://hehk.github.io/example-reason-react-global-state</p>\n<p>正如您所看到的,这些更改非常小,我所做的只是创建一个具有子状态并集的父组件,并让它负责更新该新状态。我们也只是改变原始组件中的一些函数调用来使用新的调度函数,我们是金色的。</p>\n<p><img src=\"/images/1__kAXzPNzq0y1fSyyOjOzOA.gif\" alt=\"示例演示截图\" /></p>\n<p>管用!</p>\n<h2>优点</h2>\n<ul>\n<li>这将状态与组件分离,您实际上并不需要将 reducer 和初始状态放在同一模块中。它们可能完全不同,处理方式不同。例如,您可以创建一个包含状态,操作和缩减器的语言模块,但没有特定的语言组件,其状态只会传递给组件</li>\n<li>您现在可以通过删除和重新创建元素来持续存在</li>\n</ul>\n<h2>这个方法有问题</h2>\n<ul>\n<li>这个解决方案相当简单,可能会很快变得难看。</li>\n<li>您必须通过 props 将状态显式传递给组件。 **您可以在州周围创建一个 observable,并将所有子组件订阅为一种可能的解决方法。 **</li>\n<li>除了通用更新之外的任何更改都需要重写,目前只有子减速器返回状态。你可以让他们返回一个状态和更新类型的元组,但这也只是一个更多的样板。</li>\n</ul>\n<p>我希望在 Reason / OCaml 上有一个更好的人最终会推出一个优雅的解决方案来打破这个代码,但是,就目前而言,我认为这是一种创建全局状态的简单方法。</p>\n<h2>关闭笔记</h2>\n<p>感谢阅读,我是新来写博客文章,所以希望它并不可怕。我之所以写这篇文章,是因为我是工作范例的忠实粉丝,我找不到更有趣的状态管理。如果您认为有什么不对或可以更好地解释,请告诉我。</p>\n<p>如果您想了解有关使用 reducer 组件的更多信息,请访问https://reasonml.github.io/reason-react/docs/en/state-actions-reducer.html 查看文档。</p>\n<p>此帖子的所有代码均来自以下示例应用:https://github.com/Hehk/example-reason-react-global-state</p>\n","author":"lanqy"},{"title":"如何将 BuckleScript / ReasonML 对象绑定到 JavaScript 对象","created":"2018/06/15","link":"2018/06/15/how-to-bind-bucklescript-reasonml-objects-to-javascript-objects","description":"如何将 BuckleScript / ReasonML 对象绑定到 JavaScript 对象","content":"<h1>如何将 BuckleScript / ReasonML 对象绑定到 JavaScript 对象。</h1>\n<blockquote>\n<p>注1:当我在本文中说 BuckleScript 时,我指的是 OCaml。\n注2:JavaScript对象与 BuckleScript / ReasonML中的 Object 不同。</p>\n</blockquote>\n<p>尽管它们本身支持面向对象的风格,但 BuckleScript / ReasonML 是函数式语言,这意味着它们不鼓励使用类的概念。相反,要在 BuckleScript / ReasonML 中创建 JavaScript 对象,可以使用 Js.Dict 或 Record 类型。当需要可变数量的 keys 时,应使用前者。后者用于键被固定并且其类型被预先确定的情况。</p>\n<h2>使用 Js.Dict 创建一个对象</h2>\n<pre><code class=\"language-ocaml\">let myDict = Js.Dict.empty ()\nlet _ Js.Dict.set myDict "myKey1" 100\nlet _ Js.Dict.set myDict "myKey2" 200\n\nlet myDict2 = Js.Dict.empty ()\nlet _ Js.Dict.set myDict2 "myKey1" 100\nlet _ Js.Dict.set myDict2 "myKey2" "hello" (* 错误,值必须是相同的类型 *)\n</code></pre>\n<p>*用 BuckleScript Js.Dict 创建 JavaScript 对象*</p>\n<pre><code class=\"language-ocaml\">let myDict = Js.Dict.empty();\nJs.Dict.set(myDict, "myKey1", 100);\nJs.Dict.set(myDict, "myKey2", 200);\n\nlet myDict2 = Js.Dict.empty();\nJs.Dict.set(myDict2, "myKey1", 100);\nJs.Dict.set(myDict2, "myKey2", "hello"); /* 错误,值必须是相同的类型 */\n\n</code></pre>\n<p>* 上面代码片段的 ReasonML 对应部分。*</p>\n<p>Js.Dict 是一个 JavaScript 字典,我们可以将任何值作为值,但是,强类型系统要求其值必须是相同的类型。我们创建的字典将直接转换为普通的 JavaScript 对象。</p>\n<p>如果我们想要在 Js.Dict 中存储多个类型的值,我们可以使用嵌套的 Js.Dict 结构或变体类型来实现。这看起来可能很麻烦,但这是对型号安全的一种折衷。</p>\n<p>现在,让我们看看如何在 Js.Dict 中存储变量类型的值。</p>\n<pre><code class=\"language-ocaml\">type myVariantType = \n | Nothing\n | Something of int\n | LotOfThing of int array\n [@@bs.deriving accessors]\n\nlet myDict = Js.Dict.empty ()\nlet _ = Js.Dict.set myDict "foo" Nothing\nlet _ = Js.Dict.set myDict "bar" ((Something (1)))\nlet _ = Js.Dict.set myDict "foobar" ((LotOfThing ([|1;2;3|])))\n</code></pre>\n<p>* 代码片段显示了如何在Dictionary对象中存储变体类型 *</p>\n<pre><code class=\"language-ocaml\">[@bs.deriving accessors]\ntype myVariantType = \n | Nothing\n | Something(int)\n | LotOfThing(array(int));\n\nlet myDict = Js.Dict.empty();\nJs.Dict.set(myDict, "foo", Nothing);\nJs.Dict.set(myDict, "bar", Something(1));\nJs.Dict.set(myDict, "foobar", LotOfThing([|1,2,3|]));\n</code></pre>\n<p>* 上述代码片段的ReasonML对象 *</p>\n<p>Variant 类型的值不会按原样存储在 JavaScript 对象中,但它们会被转换并转换为普通的 JavaScript 对象,如下所示:</p>\n<pre><code class=\"language-ocaml\">var myDict = { };\n\nmyDict["foo"] = /* Nothing */0;\nmyDict["bar"] = /* Something */Block.__(0, [1]);\nmyDict["foobar"] = /* LotOfThing */Block.__(1, [/* array */[\n 1,\n 2,\n 3\n ]]);\n</code></pre>\n<p><code>Nothing</code> 变成 <code>0</code>, <code>Something</code> 变成 <code>Block.__(0, [1]);</code> 和 <code>LotOfThing</code> 变成 <code>Blocks.__(1, [[1,2,3]]);</code></p>\n<p>这意味着我们在运行时不会有 Variant 类型的值。通过不查看注释来运行代码,我们无法取回 Variant 的名称。这就是注解 <code>accessors</code> 在那里的原因。它将根据每种变体类型绑定变量,以便我们可以自然地在 JavaScript 中使用 Variant,例如 <code>myDict["foo"] = nothing;</code> 代替 <code>myDict["foo"] = /* Nothing */0;</code></p>\n<h2>使用 Record 创建一个对象</h2>\n<p>虽然我们使用 Js.Dict 来存储相同类型的键和值(称为字段),并且它可能具有可变数量的字段。<a href=\"https://bucklescript.github.io/docs/en/object.html#object-as-record\">一个对象可以被定义为一个记录</a></p>\n<ul>\n<li>具有已知数量的字段</li>\n<li>可能包含或可能不包含异构类型的值</li>\n</ul>\n<p>与 Variant 略有不同,可以使用 type 关键字使用<a href=\"https://realworldocaml.org/v1/en/html/records.html\">以下语法</a>定义 Record:</p>\n<pre><code class=\"language-ocaml\">type <记录名称> =\n { <字段> : <类型> ;\n <字段> : <类型> ;\n ...\n }\n</code></pre>\n<p>例如,</p>\n<pre><code class=\"language-ocaml\">type person = {\n name: string;\n age: int;\n job: string;\n}\n\nlet p = {name = "John"; age = 20; job = "jobless"}\nlet _ = Js.log p\n</code></pre>\n<p>上面的代码产生了一个 ["John", 20, "jobless"] 的数组,这并不是我们想要的。要保留键,我们必须使用对象语法将我们的 Record 包装在 JavaScript 对象类型 Js.t 中。</p>\n<pre><code class=\"language-ocaml\">type person = <name: string; age: int; job: string> Js.t\n</code></pre>\n<p>这里的角括号是在 BuckleScript 中创建对象。请注意,这与典型的基于类的面向对象语言不同,它不需要 Class 的概念。对于来自其他世界的人来说,这可能看起来很奇怪,但是<a href=\"https://realworldocaml.org/v1/en/html/objects.html#ocaml-objects\">在 OCaml 类型中并不等于类</a>。一个对象可以在没有类的情况下创建。</p>\n<p>为了创建,我们可以使用 person 类型的对象</p>\n<pre><code class=\"language-ocaml\">let p = object\n method name = "John"\n method age = 20\n method job = "jobless"\nend;;\n</code></pre>\n<p>不,这不是一个错字。我们想要暴露给外部世界的属性被定义为方法。这是未包装在 Js.t 中的对象的原始 OCaml 语法。但是,BuckleScript 将所有 JavaScript 对象解除为 Js.t. 它通过提供 bs.obj s注释帮助我们避免语法负担。所以,上面的代码将成为</p>\n<pre><code class=\"language-ocaml\">let p = [%bs.obj {name="John";age=20;job="jobless"}]\n</code></pre>\n<p>ReasonML 将语法糖带到另一个层面。要定义一个 JavaScript 绑定:</p>\n<pre><code class=\"language-ocaml\">type person = {\n .\n "name": string,\n "age": int,\n "job": string,\n};\n</code></pre>\n<p>并在 ReasonML 中创建 JavaScript 对象:</p>\n<pre><code class=\"language-ocaml\">let p = {"name": "John", "age": 20, "job": "jobless"};\n</code></pre>\n<h2>类</h2>\n<p>在 ES6 中引入的 JavaScript 类仅仅是使用基于原型的继承和函数闭包的功能。</p>\n<blockquote>\n<p><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes\">类语法不会为 JavaScript 引入新的面向对象的继承模型。</a></p>\n</blockquote>\n<p>他们<a href=\"https://bucklescript.github.io/docs/en/class.html#bind-to-js-classes\">不鼓励使用</a>:</p>\n<blockquote>\n<p>通常,更喜欢使用先前的对象部分的特征来绑定到 JS 对象。</p>\n</blockquote>\n<p>最后,本文旨在讨论如何将 BuckleScript / ReasonML 对象绑定到 JavaScript 对象,但到目前为止,我们所做的仅仅是定义使 OCaml 类型系统识别我们要使用的对象的结构。</p>\n<p><code>external</code> 是我们想要将值绑定到 JavaScript 值时使用的关键字。例如</p>\n<pre><code class=\"language-ocaml\">external johb : person = "john" [@@bs.val]\n</code></pre>\n<p>这意味着我们将 john 绑定到 JavaScript 变量名称 john 并且它有一个 person 类型。</p>\n<h2>总结</h2>\n<p>使用 JavaScript 对象时,可能会试图将其与(Js.t)框一起使用。但是,为了获得 OCaml 类型系统的全部好处,我们宁愿将 Js.t 转换为本地结构。</p>\n","author":"lanqy"},{"title":"ReasonML:为 NPM 包创建绑定","created":"2018/06/15","link":"2018/06/15/reasonml-create-bindings-for-npm-package","description":"ReasonML:为 NPM 包创建绑定","content":"<h1>ReasonML:为 NPM 包创建绑定</h1>\n<p>译自:https://medium.com/@DmytroGladkyi/reasonml-create-bindings-for-npm-package-b8a3c6d0703e</p>\n<p>ReasonML 正在上升。最新的 https://www.reason-conf.com/ 表明,很多人都对 Facebook 这种语言感兴趣。您可以非常容易地将 ReasonML 添加到现有的 JavaScript / TypeScript 项目中,并获得强类型语言的全部好处,但很多库都是用 JS 编写的,并发布到 NPM 。要从 ReasonML 使用它们,您必须提供绑定到包。为不同的库创建了很多绑定,例如:<a href=\"https://github.com/reasonml-community/bs-moment\">MomentJS 的绑定</a>。</p>\n<p>在这篇文章中,我将向您展示如何从头开始创建绑定,以及如何在您的 ReasonML 项目中使用它们:</p>\n<pre><code class=\"language-ocaml\">[@bs.module "semver"] [@bs.val]\nexternal clean : string => Js.nullable(string) = "clean";\n\nlet clean = a => clean(a) |> Js.Nuellable.toOption;\n\n/***** COMPARISON START *****/\n[@bs.module "semver"] [@bs.val] external gt : (string, string,) => bool = "gt";\n\nlet gt = (a, b) => gt(a, b);\n[@bs.module "semver"] [@bs.val]\nexternal gte : (string, string) => bool = "gte";\n\n</code></pre>\n<h2>入门</h2>\n<p>ReasonML 提供了一个从 ReasonML 到 JavaScript 世界的非常薄的绑定层。官方的 BuckleScript 文档是一个很好的起点。</p>\n<p>我们将为 NPM 官方的 semver 软件包编写绑定。</p>\n<p>这个包暴露了不同的函数,我们也可以实例化 Semver 类,就像在这个 JavaScript 例子中一样:</p>\n<pre><code class=\"language-ocaml\">const semver = require('semver')\nsemver.valid('1.2.3') // '1.2.3'\nsemver.valid('a.b.c') // null\nsemver.clean(' =v1.2.3 ') // '1.2.3'\nsemver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true\n</code></pre>\n<h4>创建新的 NPM 包</h4>\n<p>运行命令</p>\n<pre><code class=\"language-ocaml\">npm init\n</code></pre>\n<p>这将启动一个向导,创建一个新的 NPM 包。提供你想要的任何信息,只要给它一个带有前缀 'bs'( BuckleScript )的好名字。 <code>bs</code>是 BuckleScript 或 ReasonML 绑定的社区约定。</p>\n<p>创建一个文件夹 'src':</p>\n<pre><code class=\"language-ocaml\">mkdir src\n</code></pre>\n<p>它将包含我们绑定的所有源文件。</p>\n<p>创建一个文件夹'<strong>tests</strong>':</p>\n<pre><code class=\"language-ocaml\">mkdir __tests__\n</code></pre>\n<p>它将包含由 <a href=\"https://github.com/glennsl/bs-jest\">bs-jest 库</a> 执行的绑定测试。</p>\n<h4>添加 bsconfig.json</h4>\n<p>为了使我们的包与 ReasonML 编译器一起工作,我们必须添加一个 bsconfig.json。</p>\n<pre><code class=\"language-ocaml\">{\n "name": "@gladimdim/bs-semver",\n "version": "0.2.0",\n "sources": [\n\t{\n\t "dir" : "src",\n\t "subdirs" : true\n\t},\n\t{\n\t "dir": "__tests__",\n\t "type": "dev"\n\t}\n ],\n "package-specs": {\n "module": "commonjs",\n "in-source": true\n },\n "suffix": ".bs.js",\n "bs-dependencies": [\n ],\n "bs-dev-dependencies": ["@glennsl/bs-jest"],\n "warnings": {\n "error" : "+101"\n },\n "refmt": 3\n}\n</code></pre>\n<p>最重要的导入属性:</p>\n<pre><code class=\"language-ocaml\">name: '@gladimdim/bs-semver'\n</code></pre>\n<p>必须与 package.json 中的完全相同。</p>\n<pre><code class=\"language-ocaml\">sources: [...src....__tests__....]\n</code></pre>\n<p>指定要编译为 JavaScript 代码的文件夹。测试文件夹的类型为 “<code>dev</code>”,所以它不会出现在建议中,也不会被编译进软件包中。</p>\n<h4>编辑 package.json</h4>\n<p>现在打开package.json,我们必须为它添加一些绑定特定的属性</p>\n<pre><code class=\"language-ocaml\">"script": {\n "clean": "bsb -clean-world",\n "build": "bsb -make-world",\n "watch": "bsb -make-world -w",\n "test": "jest"\n},\n</code></pre>\n<p>这些是脚本,用于构建,编译,测试和运行监视器。</p>\n<p>提供 dev 依赖:</p>\n<pre><code class=\"language-ocaml\">"devDependencies": { \n "bs-platform": "^3.0.0", \n "jest": "22.1.2", \n "@glennsl/bs-jest": "0.3.2" \n},\n</code></pre>\n<p>请注意,您必须提供像 'jest' 这样的'真正' JavaScript NPM 包装代码,因为它们包含真实代码,在测试或编译任务期间,这些代码将被来自 'bs-jest' 的绑定使用。</p>\n<p>现在告诉 NPM 包含哪些文件:</p>\n<pre><code class=\"language-ocaml\">"files": [\n "src/semver.re",\n "bsconfig.json"\n]\n</code></pre>\n<p>这是应该发布给 NPM 注册管理机构的内容。包含 bsconfig.json 非常重要,因为它已被最终用户的构建过程所使用。</p>\n<h4>指定目标NPM软件包的 Peer Dependencies</h4>\n<p>当我们为 semver 包创建绑定时,我们必须告诉 NPM 使其成为对等关系。我们软件包的最终用户将不得不为我们提供这种对等关系。</p>\n<pre><code class=\"language-ocaml\">"peerDependencies": { "semver": "^5.5.0" },\n</code></pre>\n<h2>如何编写绑定</h2>\n<p>在 src 文件夹中,创建一个名为 semver.re 的文件。这将是我们的主要和唯一的绑定文件。</p>\n<p>让我们为函数 'clean' 编写绑定,它作为 semver 包中的独立函数公开。</p>\n<p>在我们编写 <code>clean</code> 函数的主体之前,我们需要深入到可怕的JS世界:</p>\n<p><em><strong>您必须始终在运行时检查这些函数在实际中返回什么</strong></em>。</p>\n<p>每个 npm 软件包页面都有一个 “Test With RunKit” 按钮,您可以在不安装软件包的情况下使用它来调用函数:</p>\n<p><img src=\"/images/1_j4hBNwdBskluPQKGKLPKUA.png\" alt=\"1_j4hBNwdBskluPQKGKLPKUA.png\" /></p>\n<p>函数 'clean' 的问题如下:它可能会返回一个有效的 semver 字符串;如果无法解析输入的 semver 版本,则返回 null。所以,从 ReasonML 的角度来看,这个函数的结果是一个 Option。它要么返回字符串,要么无返回( None )。</p>\n<p>我们使用指令 @bs.module 和 @bs.val 来指示下一个函数没有 ReasonML 主体。相反,它将从 JavaScript 世界中获取。欲了解更多信息,请阅读官方文档:</p>\n<p>https://bucklescript.github.io/docs/en/intro-to-external.html</p>\n<pre><code class=\"language-ocaml\">[@bs.module "semver"] [@bs.val]\nexternal clean : string => Js.nullable(string) = "clean";\nlet clean = a => clean(a) |> Js.Nullable.toOption;\n</code></pre>\n<p>第二行中的类型签名意味着以下内容:函数 'clean' 接受一个字符串作为输入参数并输出一个字符串或 null。指令 @bs.module “semver” 和 “clean” 将把它转换成 JavaScript:</p>\n<pre><code class=\"language-ocaml\">semver.clean()\n</code></pre>\n<p>我们可以保持原样,但我们希望使这个函数的返回类型更具有 ReasonML -规范,并使用 Option 类型。这就是为什么在#3 行我们有这个函数的主体。它以下面的方式读取:函数 'clean' 将参数 a,发送到 'clean'(来自 semver 包的 JavaScript 函数),然后将结果传送到 toOption 转换器。</p>\n<p>ReasonML 将继承“ external clean ”声明中的类型定义,因此您不必重复它们。</p>\n<p>我们的 ReasonML 函数 'clean' 的输出将是一个 String 类型的 Option 。</p>\n<p>我们来写一下绑定测试。使用以下内容在<code>__tests__</code>文件夹中创建一个文件 semver_spec.re:</p>\n<pre><code class=\"language-ocaml\">open Jest;\n\nlet () = \n describe(\n "semver",\n ExpectJs.(\n () => {\n test("#clean", () =>\n expect(\n Semver.clean(" =1.5.0 ")\n |> (\n result =>\n switch (result) {\n | Some(v) => v\n | None => raise(Not_found)\n }\n ),\n )\n |> toBe("1.5.0")\n );\n }\n ),\n );\n\n</code></pre>\n<p>semver 模块将从我们的 semver.re 文件中自动加载。请记住,我们不测试 Semver 功能,我们测试绑定。</p>\n<p>所以我们只需要验证一下,我们的绑定返回的是可选类型,结果是字符串。</p>\n<p>我们可以继续覆盖从官方文档到 semver 的其他简单方法:https://github.com/npm/node-semver</p>\n<h2>如何为 'string' 枚举创建类型</h2>\n<p>函数 semver.cmp(a, c, b) 接受3个参数:第一个版本,操作(字符串),第二个版本。</p>\n<p>绑定到它看起来像这样:</p>\n<pre><code class=\"language-ocaml\">[@bs.module "semver"] [@bs.val]\nexternal cmp : (string, string, string) => bool = "cmp";\n</code></pre>\n<p>但是,名为“操作”的第二个参数可以是仅来自特定“操作”集的字符串。例如:“<,>,≤,≥,==,!==”等等。</p>\n<p>ReasonML 中的用法如下所示:</p>\n<pre><code class=\"language-ocaml\">Semver.cmp("1.5.0", "<", "2.3.5");\n</code></pre>\n<p>通过定义第二个参数“<”,作为一个字符串,它使我们有可能犯下以下错误:</p>\n<pre><code class=\"language-ocaml\">Semver.cmp("1.5.0", "hello", "2.3.5");\n</code></pre>\n<p>我们可以把它作为一个字符串类型,但是在 ReasonML 中,我总是喜欢为这些重要的参数设置类型。</p>\n<p>我们必须引入一个只对'cmp'方法字符串有效的类型:</p>\n<pre><code class=\"language-ocaml\">type comparator = \n | LooseEqual \n | LooseNotEqual \n | Equal \n | Empty \n | NotEqual\n | Gt\n | Gte\n | Lt\n | Lte;\n</code></pre>\n<p>编写一个将这些类型转换为字符串的函数,因为JavaScript需要一个字符串作为输入:</p>\n<pre><code class=\"language-ocaml\">let comparatorToString = comparator : string =>\n switch (comparator) {\n | LooseEqual => "=="\n | LooseNotEqual => "!=="\n | Equal => "==="\n | Empty => ""\n | NotEqual => "!=="\n | Gt => ">"\n | Gte => ">="\n | Lt => "<"\n | Lte => "<="\n };\n</code></pre>\n<p>现在,增强我们的绑定:</p>\n<pre><code class=\"language-ocaml\">[@bs.module "semver"] [@bs.val]\nexternal cmp : (string, string, string) => bool = "cmp";\n\nlet cmp = (a: string, c: comparator, b: string) =>\n cmp(a, c |> comparatorToString, b);\n</code></pre>\n<p>这个 ReasonML 代码将返回一个编译错误:</p>\n<pre><code class=\"language-ocaml\">Semver.cmp("1.5.0", "hello", "2.3.0");\n</code></pre>\n<p>我们必须重用提供的类型 Semver.Gt :</p>\n<pre><code class=\"language-ocaml\">Semver.cmp("1.5.0", Semver.Gt, "2.3.0");\n</code></pre>\n<p>该绑定将将 Semver.Gt 转换为 “>” 并将其发送到外部“真实” JavaScript 函数。</p>\n<h2>为 Semver 类创建类型</h2>\n<p>Semver 包还提供了一个实例化 Semver 类的可能性:</p>\n<pre><code class=\"language-ocaml\">const s = new semver("1.5.0");\ns.minor(); // 5\n</code></pre>\n<p>我们可以在 ReasonML 中定义一个类类型来覆盖所有的 'semver' 对象属性:</p>\n<pre><code class=\"language-ocaml\">class type semverInstance = \n [@bs]\n {\n pub inc: tRelease => semverInstance;\n pub version: string;\n pub major: int;\n pub minor: int;\n pub patch: int;\n pub raw: string;\n pub build: array(string);\n pub prerelease: array(string)\n };\n</code></pre>\n<p>然后,我们添加 'createSemver' 函数,这将帮助我们使所有类型安全:</p>\n<pre><code class=\"language-ocaml\">type tSemver = Js.t(semverInstance);\n\n[@bs.new] [@bs.module] external createSemver : string => tSemver = "semver";\n</code></pre>\n<p>用法:</p>\n<pre><code class=\"language-ocaml\">let a = Semver.createSemver("1.5.0");\nJs.log(a##minor); // 5\n</code></pre>\n<h2>总结</h2>\n<p>我希望这篇文章能够帮助你为其他软件包创建自己的类型。有很多很好的绑定 https://github.com/reasonml-community/bs-moment, https://github.com/glennsl/bs-jest 您可以查看它们的源代码,以获取关于如何编写绑定的更多见解。这实际上是我就是这么做的:-)</p>\n<h2>ReasonML 周报</h2>\n<p>要获取有关ReasonML的最新消息,您可以按照我们的 twitter:https://twitter.com/@WeeklyReason 并订阅我们的每周新闻简报:https://news.reasonml.online。</p>\n<p>GitHub repo bs-semver 绑定: https://github.com/gladimdim/bs-semver</p>\n","author":"lanqy"},{"title":"ReasonML PPX","created":"2018/06/15","link":"2018/06/15/reasonml-ppx","description":"ReasonML PPX","content":"<h1>ReasonML PPX</h1>\n<p>译自:https://blog.hackages.io/reasonml-ppx-8ecd663d5640</p>\n<p>自从我第一次见到 <a href=\"https://reasonml.github.io/\">Reason</a> 后,我看到了 “PPX” 这个词。我想知道 PPX 是什么,它做了什么以及如何构建 PPX 。我努力了一下收集方式和原因,所以我想和你分享我的经验。</p>\n<h2>什么是 PPX</h2>\n<p>PPX 重写器是一个接受序列化抽象语法树(AST)并输出另一个可能被修改的 AST 的程序。</p>\n<p>所以,PPX 的目标只是修改 AST ,例如,我们可以在这个 <a href=\"https://github.com/jaredly/ppx_autoserialize\">Github</a> 项目中看到,添加一个通用的方法:</p>\n<pre><code class=\"language-ocaml\">type myThing = {\n something: int\n};\n\nlet myValue = {something: 10};\n/* 哦,神奇的 myThing__to_devtools 可用! */\n\nJs.log(myThing__to_devtools myValue);\n</code></pre>\n<p>你可以真正做到一个或多个 PPX 链接。他们非常强大。例如,您可以使用它将 Reason JSX 翻译成 ReactJS 能够理解的内容。这就是 reactjs_jsx_ppx 所做的。</p>\n<h2>构建一个 PPX</h2>\n<p>为了更好地理解 PPX 如何工作,我们来构建一个 PPX。我们将其称为 ppx_test,它会将 [%test] 转换为 42。</p>\n<h2>配置 BuckleScript 构建</h2>\n<p>您应该先配置 <a href=\"https://bucklescript.github.io/bucklescript/Manual.html#_first_example_by_hand_without_samples\">BuckleScript</a> 构建,方法是更新 bsconfig.json 并指定在构建过程中使用哪个 PPX :</p>\n<pre><code class=\"language-json\">{\n "name" : "build-a-ppx",\n "ppx-flags": ["ppx_test/ppx"],\n "sources": "src"\n}\n</code></pre>\n<p>正如 bsconfig.json 的文档所述,您应该传递一个 package_name / binary 并且 BuckleScript 将在您的 node_modules 中查找它。所以,通过这个配置,我们应该在 node_modules / ppx_test / ppx 中有一个二进制 PPX。只需在你的 node_modules 中创建一个 ppx_test 目录。我们将在其中编写我们的 ppx_test.re 文件。</p>\n<h2>写 PPX</h2>\n<p>PPX 是类型为 Ast_mapper.mapper 的映射器。这个映射器类型只是一个包含 ParseTree 数据类型的很多映射器函数签名的记录。它看起来像这样:</p>\n<pre><code class=\"language-ocaml\">type mapper = {\n attribute: mapper => Parsetree.attribute => Parsetree.attribute,\n case: mapper => Parsetree.case => Parsetree.case,\n cases: mapper => list Parsetree.case => list Parsetree.case,\n /* 还有很多其他的mapper函数类型 */\n}\n</code></pre>\n<p>Ast_mapper 模块已经为我们提供了一个默认映射器:</p>\n<p>Ast_mapper.default_mapper(类型为 Ast_mapper.mapper )。</p>\n<pre><code class=\"language-ocaml\">let Ast_mapper.default_mapper: Ast_mapper.mapper;\n</code></pre>\n<p>我们将继承这个默认的映射器来创建我们的 PPX ,并且只覆盖将 [%test] 转换为 42 所需的内容。</p>\n<pre><code class=\"language-ocaml\">/* ppx_test.re */\nopen Asttypes;\nopen Parsetree;\nopen Ast_mapper;\n\nlet test_mapper argv => {\n ...default_mapper, /* 我们扩展了 default_mapper */\n /* 并覆盖 'expr' 属性 */\n expr: fun mapper expr => \n /* 如果表达式是 [%测试] */\n\n switch expr {\n | {pexp_desc: Pexp_extension {text: "test"} (PStr []) [@implicit_arity]} =>\n /* 然后换成 42 */\n Ast_helper.Exp.constant (Const_int 42)\n | other => default_mapper.expr mapper other\n }\n};\n\nlet () = register "ppx_test" test_mapper;\n\n</code></pre>\n<p>我们扩展 default_mapper 并覆盖 'expr'属性。如果参数中给出的表达式匹配 [%test] ,则返回 42 .否则,返回 default_mapper 实现。</p>\n<h2>构建 PPX</h2>\n<p>BuckleScript 需要一个二进制文件:node_modules / packages_name / binary_file。在我们的例子中:node_modules / ppx_test / ppx。</p>\n<p>关于 ReasonML 编译如何在这里有一个很好的介绍。让我们看看我们如何在二进制文件中逐步构建我们的 .re 文件:</p>\n<pre><code class=\"language-ocaml\">// 1. 构建 OCaml 文件\n\nocamlc -o outputfile yourOcamlFile.ml\n\n/*\n2. 构建 ReasonML 文件\n\n如果你想构建 ReasonML 文件,你需要通过带有 pp 标志的预处理器来传递它。\nrefmt 是 Reason reformat(它随 [reason-cli](https://github.com/reasonml/reason-cli) 提供)。在这种情况下,它将打印ReasonML文件的二进制文件。-impl 告诉编译器 .re 应该被认为是一个普通的 .ml( OCaml 文件)\n*/\nocamlc -pp "refmt --print binary" -o outputFile -impl yourReasonFile.re\n\n/*\n3.使通用模块可用 我们需要 OCaml Common 模块。 -I 搜索依赖关系,'+' 使其相对于 OCaml 目录\n*/\n\nocamlc -pp "refmt --print binary" -o ppx -I +compiler-libs ocamlcommon.cma -impl ppx_test.re\n\n</code></pre>\n<p>用最后一条命令,我们生成我们的 ppx 二进制文件。所以我们现在有了 node_modules / ppx_test / ppx</p>\n<h2>使用魔法</h2>\n<pre><code class=\"language-ocaml\">render: fun _ => {\n <div>\n (ReasonReact.stringToElement (string_of_int [%test]))\n </div>\n}\n</code></pre>\n<p>我们拥有它。当我们用 bsb -make-world 运行我们的项目时。 BuckleScript 将读取 bsconfig.json 中的构建配置,然后通过在我们的 node_modules 中查找来注册我们的 PPX。</p>\n<blockquote>\n<p>您可以在我们的网站上找到更多关于 Hackages 的信息。我们是一家社区驱动的公司,提供围绕 JavaScript 的最新框架和技术的高级培训。我们也乐于<a href=\"https://github.com/hackages\">贡献开源资源</a>,并组织全欧各地的免费社区活动!通过https://hackages.io 查看我们即将举办的活动。</p>\n</blockquote>\n","author":"lanqy"},{"title":"开始使用 ReasonML 和 React Native","created":"2018/05/30","link":"2018/05/30/getting-started-with-reasonml-and-react-native","description":"开始使用 ReasonML 和 React Native","content":"<h1>开始使用 ReasonML 和 React Native</h1>\n<p>译自:https://blog.callstack.io/getting-started-with-reasonml-and-react-native-299476389c3e</p>\n<p><img src=\"/images/1_s4A8_PwjA1Ichs6VnVq3Dg.jpeg\" alt=\"1_s4A8_PwjA1Ichs6VnVq3Dg.jpeg\" />\nPhoto by Will O (https://unsplash.com/photos/St4qInZrYC4)</p>\n<blockquote>\n<p>以下是如何开始使用 React Native和 ReasonML 的指南。为了本博文的目的,我假设您已经熟悉 React Native 并部分使用 ReasonML 。如果您尚未接触过 ReasonML,请查看<a href=\"https://reasonml.github.io/docs/en/global-installation.html\">文档</a>。</p>\n</blockquote>\n<p>首先安装 <a href=\"https://facebook.github.io/react-native/docs/getting-started.html\">React Native CLI</a>:</p>\n<pre><code class=\"language-ocaml\">npm i -g react-native-cli\n</code></pre>\n<p>现在我们可以初始化一个新的 React Native 项目,就像我们对每个 React Native 应用程序所做的一样:</p>\n<pre><code class=\"language-ocaml\">react-native init MyReasonApp\n</code></pre>\n<h2>添加 “ Reason 部分”</h2>\n<p>我们将需要 3 个包:</p>\n<ul>\n<li><a href=\"https://bucklescript.github.io/docs/en/what-why.html\"><code>bs-platform</code></a> - 将 ReasonML / OCaml 编译为干净,可读和高性能的 JavaScript 代码</li>\n<li><a href=\"https://reasonml.github.io/reason-react\"><code>reason-react</code></a> - ReactJS 的 Reason 绑定</li>\n<li><a href=\"https://github.com/reasonml-community/bs-react-native\"><code>bs-react-native</code></a> - React Native 的 BuckleScript 绑定</li>\n</ul>\n<p>让我们将它们添加到我们的项目中:</p>\n<pre><code class=\"language-ocaml\">npm i -P bs-platform reason-react bs-react-native\n</code></pre>\n<p>现在我们需要创建一个 <code>bsconfig.json</code> ,它是一个 <a href=\"https://bucklescript.github.io/docs/en/build-configuration.html\">BuckleScript 的配置文件</a>:</p>\n<pre><code class=\"language-ocaml\">{\n "name": "my-reason-app",\n "bsc-flags": ["-bs-no-version-header", "-bs-super-errors"],\n "refmt": 3,\n "bs-dependencies": ["reason-react", "bs-react-native"],\n "reason": {\n "react-jsx": 2\n },\n "package-specs": {\n "module": "commonjs",\n "in-source": true\n },\n "sources": [\n {\n "dir": "src",\n "subdirs": true\n }\n ]\n}\n</code></pre>\n<p>让我们在这里停一分钟。有几个不同于通常的设置。</p>\n<p>首先是 <a href=\"https://bucklescript.github.io/docs/en/build-configuration.html#sources\"><code>"subdirs": true</code></a>,使得 BuckleScript 知道它应该检查应该编译的代码的子目录。</p>\n<p>另一个是 <a href=\"https://bucklescript.github.io/docs/en/build-configuration.html#package-specs\"><code>"in-source": true</code></a> ,这个非常方便,与源文件一起生成输出(默认情况下,它们输出到 <code>lib/js</code> 目录下)。当我们引用 <code>.js</code> 文件或资源文件时,这非常有用。</p>\n<p>没有它,要导入一个图像,你会参考它:</p>\n<pre><code class=\"language-ocaml\"><Image\n style=Styles.icon\n source=(\n Required(Packager.require("../../../assets/right.png"))\n )\n/>\n</code></pre>\n<p>使用 <code>"in-source": true</code> ,它会看起来像:</p>\n<pre><code class=\"language-ocaml\"><Image\n style=Styles.icon\n source=(Required(Packager.require("./assets/right.png")))\n/>\n</code></pre>\n<p>我更喜欢后者,因此我启用了该选项。</p>\n<h2>React Native 中的 ReasonML</h2>\n<p>我们完成了配置,回顾一下,我们添加了三个软件包:<code>bs-platform</code>,<code>reason-react</code> 和 <code>bs-react-native</code>。然后我们添加了 <code>BuckleScript</code> 的配置文件 <code>bsconfig.json</code>,就是这样! 🎉</p>\n<p>现在我们来写一些 Reason 吧!</p>\n<p>正如我们之前在 <code>bsconfig.json</code> 中定义的,我们将把所有的 ReasonML 代码保存在 <code>src</code> 目录中。在新创建的 <code>src</code> 目录中(在我们项目的根目录中),让我们添加<code>App.re</code> ,它可能看起来像这样:</p>\n<pre><code class=\"language-ocaml\">open BsReactNative;\n\n/* 这里我们定义一些样式 */\nmodule Styles = {\n open Style;\n\n let container = \n style([\n flex(1.),\n justifyContent(Center),\n alignItems(Center),\n backgroundColor(String("tomato")),\n ]);\n let text = style([color(String("#fff")), fontSize(Float(24.))]);\n};\n\nmodule Main = {\n let component = ReasonReact.statelessComponent("Main");\n\n let make = _children => {\n ...component,\n render: _self =>\n <View style=Styles.container>\n <Text style=Styles.text>\n (ReasonReact.string("Let's play with ReasonML!"))\n </Text>\n </View>,\n };\n};\n\nlet app = () => <Main />;\n</code></pre>\n<p>我还从项目的根目录中删除了 <code>App.js</code>(这是由 React Native CLI 生成的文件)。</p>\n<p>我们需要做的最后一件事是将我们编译好的 <code>App</code> 导入到 <code>index.js</code> 中:</p>\n<pre><code class=\"language-ocaml\">import { AppRegistry } from 'react-native';\nimport { app } from './src/App';\n\n/*\n 如果 "in-source" 选项为 false (默认为 false ),您将以这种方式导入app:\n import { app } from "./lib/js/src/App.js";\n*/\n\nAppRegistry.registerComponent('MyReasonApp', () => app);\n</code></pre>\n<p>最后,我们可以通过运行来编译代码:</p>\n<pre><code class=\"language-ocaml\">npm run watch\n</code></pre>\n<p>这将监视您对 Reason 代码所做的任何更改并进行编译(如果没有错误的话)。</p>\n<p>现在让我们开始运行 React Native 应用程序:</p>\n<pre><code class=\"language-ocaml\">react-native run-ios\n</code></pre>\n<p>你应该看到:</p>\n<p><img src=\"/images/1_tsDhHE5u-a4v8Url4lXlUw.png\" alt=\"1_tsDhHE5u-a4v8Url4lXlUw.png\" /></p>\n<p>快乐 hacking ! 🎉</p>\n<p>这里是与上述设置的 repo 链接:</p>\n<p>https://github.com/knowbody/ReasonReactNativeApp</p>\n<p>...</p>\n<p><a href=\"https://twitter.com/matzatorski\">在 Twitter 上关注 Mateusz Zatorski</a></p>\n","author":"lanqy"},{"title":"Reason 入门手册","created":"2018/05/30","link":"2018/05/30/reason-in-nutshell-getting-started-guide","description":"Reason 入门手册","content":"<h1>Reason 入门手册</h1>\n<p>译自:https://zaiste.net/reason_in_nutshell_getting_started_guide/</p>\n<p><img src=\"/images/zaiste-reason.png\" alt=\"Reason\" /></p>\n<blockquote>\n<p>本教程旨在提供对 Reason 的全面但相对较短的介绍。</p>\n</blockquote>\n<p><a href=\"https://reasonml.github.io/\">Reason</a> 是一种构建在 <a href=\"https://ocaml.org/\">OCaml</a> 之上的编程语言。它提供了函数式和面向对象的功能,并且具有类型安全性并关注性能。它是在 Facebook 创建的。它的语法与 JavaScript 类似。目的是使 JavaScript 与 JavaScript 程序员的互操作更容易。Reason 可以访问 JavaScript 和 OCaml 生态系统。OCaml 创建于 1996 年,它是一种具有类型推导的函数式编程语言。</p>\n<p>Reason 网站包含一个<a href=\"https://reasonml.github.io/en/try.html\">在线游乐场</a>。它允许使用该语言并直观的查看生成的 JavaScript。它也可以从 OCaml 转换为 Reason。</p>\n<h2>为什么</h2>\n<ul>\n<li>在 JavaScript 类型注释中,linting 或统一格式是作为外部依赖项提供的,例如 Flow,TypeScript,ESLint 或 Prettier。Reason 提供了这些功能的开箱即用。这使得开发过程更加简化和方便。</li>\n<li>Reason 提供对 <a href=\"https://reasonml.github.io/reason-react/\">ReasonReact</a> 的 React 支持。它还支持 JSX 语法( React 中使用的类似 HTML 的语法)。开箱即用</li>\n<li>Reason 还有生成本机二进制文件的能力。生成的代码是高性能的。没有虚拟机开销。它提供了一个便于部署过程的二进制文件。</li>\n</ul>\n<h2>它是如何工作的</h2>\n<p>Reason 被编译为 OCaml 的抽象语法树。这使 Reason 成为一个转译器。 OCaml 不能直接在浏览器中运行。AST 可以转换为各种目标。可以使用 BuckleScript 将 AST 编译为 JavaScript。它还提供了 OCaml 和 JavaScript 生态系统之间的互操作。</p>\n<p>BuckleScript 速度极快,可生成可读的 JavaScript。它还提供了外部函数接口(FFI)以允许与 JavaScript 现有库的互操作性。检查<a href=\"https://github.com/neonsquare/bucklescript-benchmark\">BuckleScript 基准</a>。 BuckleScript 由 Messanger 团队在 Facebook 上使用,在 WebAssembly spec 解释器上由 Google 使用。在这里检查 Bucklescript 演示。 BuckleScript 由 <a href=\"https://twitter.com/bobzhang1988/\">Hongbo Zhang</a> 创建。</p>\n<h2>你好 Reason</h2>\n<p>我们将使用 BuckleScript 生成一个 Reason 项目。该工具提供即时可用的项目模板,称为 <code>themes</code>。</p>\n<p>我们先从全局安装 <code>bs-platform</code> 开始:</p>\n<pre><code class=\"language-ocaml\">npm install -g bs-platform\n</code></pre>\n<p>我们现在可以使用 <code>bs-platform</code> 提供的 <code>bsb</code> 二进制文件生成项目脚手架。我们将使用 <code>basic-reason</code> 模板从最基本的 Reason 项目结构开始。</p>\n<pre><code class=\"language-ocaml\">bsb -init reason-1 -theme basic-reason\n</code></pre>\n<pre><code class=\"language-ocaml\">Making directory reason-1\nSymlink bs-platform in /Users/zaiste/code/reason-1\n</code></pre>\n<p>以下是通过 BuckleScript 从 <code>basic-reason</code> 模板生成的 Reason 目录结构:</p>\n<pre><code class=\"language-ocaml\">.\n├── README.md\n├── bsconfig.json\n├── lib\n├── node_modules\n├── package.json\n└── src\n └── Demo.re\n</code></pre>\n<p><code>bsconfig.json</code> 包含 Reason 项目的 BuckleScript 配置。它允许指定要通过源编译的文件,通过 <code>bs-dependencies</code> 的 BuckleScript 依赖关系,编译器的附加标志等等。</p>\n<p>下一步是构建项目。这将采取 Reason 代码并通过 BuckleScript 传递它以生成 JavaScript。默认情况下,编译器将以 Node.js 为目标。</p>\n<pre><code class=\"language-ocaml\">npm run build\n</code></pre>\n<pre><code class=\"language-ocaml\">(* 输出 *)\n> reason-1@0.1.0 build /Users/zaiste/code/reason-1\n> bsb -make-world\n\nninja: Entering directory `lib/bs'\n[3/3] Building src/Demo.mlast.d\n[1/1] Building src/Demo-MyFirstReasonml.cmj\n</code></pre>\n<p>最后,我们可以使用 <code>node</code> 来运行 由 BuckleScript 生成的文件。</p>\n<pre><code class=\"language-ocaml\">node src/Demo.bs.js\n\n(* 将输出 Hello, BuckleScript and Reason! *)\n</code></pre>\n<h2>语法 101</h2>\n<p>在本节中,我将详细介绍我发现的特殊的,新的或不同的语法元素。</p>\n<h3>模块</h3>\n<p>Reason 文件是模块。在 JavaScript 或类似的编程语言中没有 <code>require</code> 或 <code>import</code> 语句。模块定义必须以模块名称作为前缀以在外部工作。该功能来自 OCaml 。因此,您可以自由移动文件系统中的模块文件,而无需修改代码。</p>\n<h3>函数</h3>\n<p>函数使用 <code>let</code> 和 <code>=></code> 来定义。</p>\n<pre><code class=\"language-ocaml\">let greet = name =>\n Js.log("Hello, " ++ name ++ "!");\n\ngreet("Zaiste");\n</code></pre>\n<p><code>++</code> 运算符用于连接字符串。</p>\n<p>函数的输入参数可以被标记。这使得函数调用更加明确:传入的值不再需要遵循函数定义中的参数顺序。用 <code>~</code> 作为参数名称的前缀使其标记。</p>\n<pre><code class=\"language-ocaml\">let greet = (~name, ~location) =>\n Js.log("Hello, " ++ name ++ "! You're in " ++ location);\n\ngreet(~location="Vienna", ~name="Zaiste");\n</code></pre>\n<h3>数据结构</h3>\n<h4>变体</h4>\n<p>变体是一个数据结构,它保存来自一组固定值的值。这也被称为标记或不相交联合或代数数据类型。变体中的每个案例都必须大写。可选,它可以接收参数。</p>\n<pre><code class=\"language-ocaml\">type animal = \n | Dog\n | Cat\n | Bird;\n</code></pre>\n<h4>记录</h4>\n<p>这是一个记录</p>\n<pre><code class=\"language-ocaml\">let p = {\n name: "Zaiste",\n age: 13\n}\n</code></pre>\n<p>记录需要明确的类型定义。</p>\n<pre><code class=\"language-ocaml\">type person = {\n name: string,\n age: int\n}\n</code></pre>\n<p>在模块的作用域中,类型将被继承:p 绑定将被识别为 <code>person</code> 类型。在模块之外,您可以通过在文件名前添加前缀来引用该类型。</p>\n<pre><code class=\"language-ocaml\">let p: Person.person = {\n name: "Sean",\n age: 12\n}\n</code></pre>\n<p>有一个约定为每个类型创建一个模块并将类型命名为 <code>t</code> 以避免重复,即用 <code>Person.t</code> 代替 <code>Person.person</code>。</p>\n<h3>异步编程和 Promise</h3>\n<p>通过 BuckleScript 提供的内置 Promise 支持,作为 JS.Promise 模块提供。以下是使用 <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\">Fetch API</a> 进行 API 调用的示例:</p>\n<pre><code class=\"language-ocaml\">Js.Promise.(\n Fetch.fetch(endpoint)\n |> then_(Fetch.Response.json)\n |> then_(json => doSomethingOnReponse(json) |> resolve)\n)\n</code></pre>\n<p>您需要使用 <code>then_</code> ,因为 <code>then</code> 是 OCaml 中的保留字。</p>\n<h3>模式匹配</h3>\n<p>模式匹配是基于提供值的形状的调度机制。在 Reason 中,模式匹配是通过 <code>switch</code> 语句实现的。它可以与变体类型或解构机制一起使用。</p>\n<pre><code class=\"language-ocaml\">switch pet {\n | Dog => "woof"\n | Cat => "meow"\n | Bird => "chirp"\n};\n</code></pre>\n<p>我们可以使用模式匹配进行列表解构:</p>\n<pre><code class=\"language-ocaml\">let numbers = ["1", "2", "3", "4"];\nswitch numbers {\n | [] => "Empty"\n | [n1] => "Only one number: " ++ n1\n | [n1, n2] => "Only two numbers"\n | [n1, _, n3, ...rest] => "At least three numbers"\n}\n</code></pre>\n<p>或者,我们可以将其用于记录解构</p>\n<pre><code class=\"language-ocaml\">let project = {\n name: "Huncwot",\n size: 101101,\n forks: 42,\n deps: [{name: "axios"}, {name: "sqlite3"}]\n};\n\nswitch project {\n | {name: "Huncwot", deps} => "Matching by `name`"\n | {location, years: [{name: "axios"}, ...rest]} => "Matching by one of `deps`"\n | project => "Any other situation"\n}\n</code></pre>\n<h3>可选值</h3>\n<p>option() 是 Reason 中描述 “ <code>nullable</code> (可空)” 值的内置变量:</p>\n<pre><code class=\"language-ocaml\">type option('a) = None | Some('a);\n</code></pre>\n<h3>不同</h3>\n<ul>\n<li><code>unit</code> 意味着“无”</li>\n<li><code>unit => unit</code> 是一个函数的签名,它不接受任何输入参数并且不返回任何值;主要用于回调函数</li>\n</ul>\n<h2>Reason 中的 React</h2>\n<h3>你好 ReasonReact</h3>\n<p><a href=\"https://reasonml.github.io/reason-react/\">ReasonReact</a> 是一个 Reason 中用于创建 React 应用程序的内置功能。</p>\n<p>我们使用 BuckleScript 及其 <code>React</code> 模板创建一个 ReasonReact 项目。</p>\n<pre><code class=\"language-ocaml\">bsb -init reasonreact-1 -theme react\n</code></pre>\n<p>Reason 团队推荐 ReasonReact 项目脚手架使用此方法。也可以使用带有 <a href=\"https://github.com/reasonml-community/reason-scripts\">reason-scripts</a> 模板的 yarn 来获得更完整的起点。</p>\n<p>ReasonReact 提供了两种类型的组件:<code>statelessComponent</code> 和 <code>reducerComponent</code>。与 <code>stateless</code> (无状态)组件相反,<code>reducer</code> (减速器)组件是有状态的,提供类似 Redux 的 <code>reducer</code>。</p>\n<pre><code class=\"language-ocaml\">let s = ReasonReact.string;\n\nlet component = ReasonReact.statelessComponent("App");\n\nlet make = (~message, _children) => {\n ...component,\n render: _self => \n <h1 className="header">(s(message))</h1>\n}\n</code></pre>\n<p>如前所述 <code>~</code> 指定一个带标号的参数来自由排序函数的输入参数。绑定名称中的 <code>_</code> 告诉编译器该函数的主体中未使用该参数。扩展运算符(<code>...</code>)与 <code>component</code> (组件)一起意味着我们扩展了现有的组件。在这个例子中,我们也重写(覆盖)了 <code>render</code> 函数。</p>\n<p>Reason 中的 JSX 比 React 更严格:我们需要使用 <code>ReasonReact.string()</code> 显式包装字符串。</p>\n<h3>构建非平凡的 ReasonReact 应用程序</h3>\n<p>让我们构建一个超越显示预定义数据的 ReasonReact 应用程序。我们将为趋势库创建一个 GitHub 查看器。目的是展示如何与外部 API 集成,如何管理状态以及如何使用 React 的生命周期方法的方法。</p>\n<p>为了这个例子的目的,我们将使用 <a href=\"https://github.com/reasonml-community/reason-scripts\">reason-scripts</a> 来引导我们的 Reason 项目。</p>\n<pre><code class=\"language-ocaml\">yarn create react-app reasonreact-github --scripts-version reason-scripts\n</code></pre>\n<p>安装依赖</p>\n<pre><code class=\"language-ocaml\">cd reasonreact-github\nyarn\n</code></pre>\n<p>从以下开始:</p>\n<pre><code class=\"language-ocaml\">yarn start\n</code></pre>\n<p><em><strong>存储库</strong></em>是这个应用程序的中心概念。我们首先定义一个描述该实体的类型。我们将把它放在一个名为 Repo 的单独模块中。</p>\n<pre><code class=\"language-ocaml\">type t = {\n name: string,\n size: int,\n forks: int\n};\n</code></pre>\n<p>从现在开始,我们可以从应用程序中的任何 Reason 文件引用此类型的 <code>Repo.t</code>,而不需要 requiring(引入)它。</p>\n<h3>管理状态</h3>\n<p>我们已经看到了一个无状态的组件。现在让我们创建一个具有状态的组件。在我们的上下文中,我们将使用 <code>RepoList</code> 组件管理从 GitHub 的 API 中获取的趋势库列表。</p>\n<p>首先定义由 <code>RepoList</code> 组件管理的状态的类型。</p>\n<pre><code class=\"language-ocaml\">type state = {\n repos: list(Repo.t)\n}\n</code></pre>\n<p>但是,有一个问题。最初,在从 GitHub API 获取趋势库列表之前,<code>repos</code> 是未定义的。Reason 类型系统不允许我们有 <code>undefined </code> (未定义) 的值。我们可以用一个空列表来模拟初始状态,但这不是最优的。空列表还可能意味着我们对提取趋势库的查询没有返回任何结果。</p>\n<p>让我们使用 Reason 的可选值来处理这种情况。</p>\n<pre><code class=\"language-ocaml\">type state = {\n repos: option(list(Repo.t))\n}\n</code></pre>\n<p>下一步是定义该组件的可能操作。在ReasonReact中, <code>actions</code> (操作)表示为变体。现在我们只会有一个名为 <code>ReposFetched</code> 的 <code>action</code> (操作)。</p>\n<pre><code class=\"language-ocaml\">type action = \n | ReposFetched(list(Repo.t));\n</code></pre>\n<p>为了在 ReasonReact 中创建一个有状态的组件,我们需要使用 <code>reducerComponent()</code> 函数。</p>\n<pre><code class=\"language-ocaml\">let component = ReasonReact.reducerComponent("App");\n</code></pre>\n<p>这样的组件允许定义描述状态如何响应于 <code>actions</code> (动作)而被转换的 <code>reducer</code> (减速器)。Reducer将当前状态作为输入采取<code>actions</code> (动作)并将新状态作为输出返回。<code>reducer</code> (减速器)必须是纯粹的函数。</p>\n<pre><code class=\"language-ocaml\">reducer: (action, _prevState) => {\n switch action {\n | ReposFetched(repos) =>\n ReasonReact.Update({repos: Some(repos)})\n }\n};\n</code></pre>\n<p>基于我们在 <code>reducer()</code> 方法中收到的参数,我们是模式匹配 <code>action</code> (操作)。模式匹配必须是详尽的。所有变体值必须匹配。<code>reducer</code> 定义放置在组件的 <code>main</code> 函数中。</p>\n<p>为了完成组件的定义,我们来定义它的初始状态:</p>\n<pre><code class=\"language-ocaml\">initialState: () => {\n repos: Some([\n {\n name: "Huncwot",\n size: 101101,\n forks: 42\n }\n ])\n};\n</code></pre>\n<h3>与 API 集成</h3>\n<p>我们将使用 <a href=\"https://github.com/reasonml-community/bs-fetch\">bs-fetch</a> 从外部 API 获取数据。它是一个 BuckleScript 库,充当 Fetch API 之上的一个薄层。一旦数据被提取,我们将使用 <code>bs-json</code> 来提取我们感兴趣的字段。</p>\n<p>开始安装 <code>bs-fetch</code> 和 <code>bs-json</code>:</p>\n<pre><code class=\"language-ocaml\">npm i bs-fetch @glennsl/bs-json\n</code></pre>\n<p>将它们添加到 <code>bsconfig.json</code> 中的 <code>bs-dependencies</code>:</p>\n<pre><code class=\"language-ocaml\">{\n "bs-dependencies": [\n ...,\n "bs-fetch",\n "@glennsl/bs-json"\n ]s\n}\n</code></pre>\n<p>我们将 Repo 类型定义为一组三个字段:<code>name</code>,<code>size</code> 和 <code>forks</code>。一旦从 GitHub API 获取有效载荷,我们就解析它以提取这三个字段。</p>\n<pre><code class=\"language-ocaml\">let parse = json =>\n Json.Decode.{\n name: json |> field("name", string),\n size: json |> field("size", int),\n forks: json |> field("forks", int)\n }\n</code></pre>\n<p><code>field</code> 是 <code>Json.Decode</code> 的一个方法。<code>Json.Decode.{...}</code>(注意这个点号)打开 <code>Json.Decode</code> 模块。它的属性现在可以在这些大括号内使用,而不需要使用 <code>Json.Decode</code> 作为前缀。</p>\n<p>由于 GitHub 在 <code>items</code> 下返回 <code>repos</code> ,我们定义另一个函数来获取该列表。</p>\n<pre><code class=\"language-ocaml\">let extract = (fields, json) =>\n Json.Decode.(\n json |> at(field, list(parse))\n );\n</code></pre>\n<p>最后,我们可以发出请求并通过解析函数传递返回的数据:</p>\n<pre><code class=\"language-ocaml\">let list = () => \n Js.Promise.(\n Fetch.fetch(endpoint)\n |> then_(Fetch.Response.json)\n |> then_(text => extract(["items"], text) |> resolve)\n );\n</code></pre>\n<h3>React 生命周期方法</h3>\n<p>让我们使用 <code>didMount</code> 生命周期方法触发从 GitHub API 获取存储库。</p>\n<pre><code class=\"language-ocaml\">didMount: self => {\n let handle = repos => self.send(ReposFetched(repos));\n Repo.list()\n |> Js.Promise.then_(repos => {\n handle(repos);\n Js.Promise.resolve();\n });\n |> ignore;\n};\n</code></pre>\n<p><code>handle</code> 是一个将 <code>ReposFetched</code> 操作分派给 <code>reducer</code> 的方法。一旦承诺解决,操作将把获取的存储库传送到 <code>reducer</code> (减速器)。这将更新我们的状态。</p>\n<h3>渲染</h3>\n<p>由于我们区分了非初始化状态和存储空列表,因此处理初始<em><strong>加载进度</strong></em>消息很简单。</p>\n<pre><code class=\"language-ocaml\">render: self => {\n <div>\n (\n switch self.state.repos {\n | None => s("Loading repositories...")\n | Some([]) => s("Empty list")\n | Some(repos) =>\n <ul>\n (\n repos\n |> List.map((repo: Repo.t) => <li> (s(repo.name)) </li>)\n |> Array.of_list\n |> ReasonReact.array\n )\n </ul>\n }\n )\n </div>\n};\n</code></pre>\n<h3>错误处理</h3>\n<p>无</p>\n<h3>CSS 中的类型</h3>\n<p>使用 <a href=\"https://github.com/SentiaAnalytics/bs-css\">bs-css</a> 的 CSS 类型。</p>\n<p>通过 <code>yarn</code> 安装</p>\n<pre><code class=\"language-ocaml\">yarn add bs-css\n</code></pre>\n<p>并将它添加到 <code>bsconfig.json</code> 中的 <code>bs-dependencies</code>:</p>\n<pre><code class=\"language-ocaml\">"bs-dependencies": [\n ...,\n "bs-css"\n]\n</code></pre>\n<p>组件:</p>\n<pre><code class=\"language-ocaml\">let style = \n Css.(\n {\n "header": style([backgroundColor(rgba(111, 37, 35, 1.0)), display(Flex)]),\n "title": style([color(white), fontSize(px(28)), fontWeight(Bold)]),\n }\n );\n\nlet make = _children => {\n ...component,\n render: _self =>\n <header className=style##header>\n <h1 className=style##title> (s("This is title")) </h1>\n </header>\n};\n</code></pre>\n<h2>词汇</h2>\n<ul>\n<li><code>rtop</code> 是 Reason 的交互式命令行。</li>\n<li>Merlin 是 OCaml 和 Reason 的自动完成服务文件。</li>\n<li>[@bs...] FFI 的 Bucklescript 注释</li>\n</ul>\n<h2>其他资源</h2>\n<ul>\n<li><a href=\"https://github.com/vramana/awesome-reasonml\">Awesome ReasonML</a></li>\n<li><a href=\"https://realworldocaml.org/\">Real World OCaml</a></li>\n<li>http://2ality.com/archive.html?tag=reasonml</li>\n<li>https://jamesfriend.com.au/a-first-reason-react-app-for-js-developers</li>\n<li>https://github.com/reasonml-community/reason-scripts</li>\n<li>https://dev.to/jlewin_/reasonml-getting-started-53gi</li>\n<li>https://medium.com/@ryyppy/a-quick-look-on-my-reasonml-workflow-with-vscode-637685f9417a</li>\n<li>https://redex.github.io/</li>\n<li>https://github.com/arecvlohe/reasonml-cheat-sheet</li>\n<li>https://news.ycombinator.com/item?id=16500481</li>\n<li><a href=\"https://github.com/reasonml/reason-tools\">ReasonTools browser extension</a></li>\n</ul>\n<h2>待定</h2>\n<pre><code class=\"language-ocaml\">module History = {\n type h;\n [@bs.send] external goBack : h => unit = "";\n [@bs.send] external goForward: h => unit = "";\n [@bs.send] external go : (h, ~jumps: int) => unit = "";\n [@bs.get] external length : h => int = "";\n};\n</code></pre>\n<p>BuckleScript 允许我们将原始 JavaScript 与 Reason 代码混合使用。</p>\n<pre><code class=\"language-ocaml\">[%bs.raw {|require('./app.css')|}];\n</code></pre>\n","author":"lanqy"},{"title":"在 ReasonReact中解码 JSON","created":"2018/05/28","link":"2018/05/28/decoding-json-in-reasonreact","description":"在 ReasonReact中解码 JSON","content":"<h1>在 ReasonReact中解码 JSON</h1>\n<p>译自:https://medium.com/@idkjs/decoding-json-in-reasonreact-cff3a07c1200</p>\n<p><img src=\"/images/1_OizIr4Tc2m6lCGdr9478ew.png\" alt=\"在ReasonReact中解码JSON\" /></p>\n<p>Reason 是一种新的来自 Facebook 的静态类型函数编程语言,可以编译为 Javascript。ReasonReact 是 React 的一个包装器,可以很容易地在 Reason 中使用。</p>\n<p>我们将构建一个小型单页网络应用程序,通过其步伐放置 Reason React。该应用将显示与 Reason 相关的最佳 Github repos 列表。这是一个足够小的任务,我们可以在几个小时内完成它,但是它也有足够的复杂性使我们可以开始厌倦这种新语言。本教程预期不存在 Reason 的现有知识,但对静态类型的基本熟悉会有所帮助。</p>\n<h2>一个新项目</h2>\n<p>我们将使用使用 <code>create-react-app</code> 的 <code>reason-scripts</code>,这将为我们的应用程序创建一个起点,该应用程序将被称为 <code>decode-json</code>:</p>\n<pre><code class=\"language-ocaml\">yarn create react-app decoding-reason --script-version reason-scripts\n</code></pre>\n<p>通过运行 <code>yarn start</code> 来启动项目,在浏览器中打开 <code>https://localhost:3000</code>。</p>\n<h2>记录类型</h2>\n<p>我们将解码来自此Github API请求的公共数据的响应:https://api.github.com/search/repositories?q=topic%3Areasonml&type=Repositories 但首先可以使用假数据进行设置。</p>\n<p>创建一个名为 <code>RepoData.re</code> 的新文件并将其添加到其中。</p>\n<pre><code class=\"language-ocaml\">type repo = {\n full_name: string,\n stargazers_count: int,\n html_url: string\n};\n</code></pre>\n<h2>文件既是模块</h2>\n<p>我们已经在文件的顶层定义了我们的类型。</p>\n<blockquote>\n<p>顶层基本上意味着视觉上,没有缩进(非常糟糕的解释方式,但你明白了)。<code>let foo = 1</code>不嵌套在其他任何内容中都是顶级的。<code>let foo = 1</code>在一个函数体内时,则不是顶层。<a href=\"https://reasonml.chat/t/bucklescript-top-level-declarations/399/3?u=idkjs\">@chenglou</a></p>\n</blockquote>\n<p>在 Reason 中,每个文件都是一个模块,并且使用关键字 <code>let</code>,<code>type</code> 和 <code>module</code> 在文件顶层定义的所有东西都暴露在其他文件(即其他模块)中使用。在这种情况下,其他模块可以将我们的 repo 类型引用为 RepoData.repo。与 Javascript 不同,不需要导入来引用其他模块的东西。</p>\n<p>让我们在 <code>app.re</code> 中使用我们的类型。repos 页面只是 repos 列表,列表中的每个项目都包含 repos 名称(链接到 Github 上的 repos)以及 repos 的 stars 数量。我们将定义一些虚拟数据,并在 <code>App.re</code> 中绘制一个名为 RepoItem 的新组件来表示 repos 列表中的项目:</p>\n<pre><code class=\"language-ocaml\">// App.re\nlet component = ReasonReact.statelessComponent("App");\n\nlet make = (_children) => {\n ...component,\n render:(_self) => {\n /* our dummy data */\n let dummyRepo: RepoData.repo = {\n stargazers_count: 27,\n full_name: "jsdf/reason-react-hacker-news",\n html_url: "https://github.com/jsdf/reason-react-hacker-news"\n };\n\n <div className="App">\n <h1>{ReasonReact.stringToElement("Reason Projects")}</h1>\n <RepoItem repo=dummyRepo />\n </div>\n }\n};\n\n</code></pre>\n<p>在声明开始 <code>let dummyRepo: RepoData.repo = {...}</code>,<code>dummyRepo</code> 是我们正在定义的常量的名称, <code>RepoData.repo</code> 是我们注释它的类型,它来自我们在其中定义它的位置 <code>RepoData.re</code>。请记住,该模块在项目的任何地方都可用。Reason 可以推断出我们声明的大部分事物的类型,但是在这里包含注释是很有用的,这样类型分析者可以告诉我们我们的测试数据是否犯了错误。</p>\n<h2>Reason 中的返回值</h2>\n<p>请注意,渲染函数的主体现在包含在 <code>{}</code> 括号中,因为它包含多个语句。在 Javascript 中,如果我们在 <code>=></code> 箭头函数的主体周围使用大括号,我们需要添加一个返回语句来返回一个值。然而在 Reason 中,函数返回最后一条语句产生的值,这里:</p>\n<pre><code class=\"language-ocaml\">···\n<div className="App">\n <h1>{ReasonReact.stringToElement("Reason Projects")}</h1>\n <RepoItem repo=dummyRepo />\n</div>\n</code></pre>\n<p>自动成为返回值。如果你不想从函数返回任何东西,你可以创建最后一个语句 ()(在 <a href=\"https://reasonml.github.io/docs/en/function.html#optional-labeled-arguments\">Reason</a> 中称为 “unit”)。</p>\n<h2>在 ReasonReact 中定义组件</h2>\n<p>你现在可能会看到一个错误提示,说 RepoItem 文件或模块没有找到。这是因为我们在App组件渲染函数中添加了 <code><RepoItem repo=dummyRepo /></code>,但是我们还没有创建该模块。添加一个名为 <code>RepoItem.re</code> 的新文件,包含:</p>\n<pre><code class=\"language-ocaml\">let component = ReasonReact.statelessComponent("RepoItem");\n\nlet make = (~repo: RepoData.repo, _children) => \n{\n ...component,\n render: (_self) =>\n <div>{ReasonReact.string(repo.full_name)}</div>\n};\n</code></pre>\n<p>这里发生了什么?让我们来剖析这个文件中发生了什么。</p>\n<p>每个 Reason React 组件都是一个 Reason 模块,它定义了一个名为 make 的函数,它定义了 props 和 children 参数。props 被指定为<a href=\"https://reasonml.github.io/docs/en/function.html#labeled-arguments\">标签参数</a>。</p>\n<pre><code class=\"language-ocaml\">let compoent = ReasonReact.statelessComponent("SomeComponent");\n\nlet make = (~someProp, ~anotherProp, _children) => /* some stuff here */;\n</code></pre>\n<p><code>make</code> 函数返回一条记录。此记录中的第一件事通常是 <code>...component</code>,其中 <code>component</code> 是<code>ReasonReact.reducerComponent</code> 或 <code>ReasonReact.statelessComponent</code> 的返回值(对于分别使用状态和不使用状态的组件)。如果这看起来有点奇怪,可以把它看作是从 React组件类继承的,就像做 Foo 类的扩展等同于在ReactJS中扩展 <code>class Foo extends React.Component {...</code> 一样。</p>\n<pre><code class=\"language-ocaml\">// RepoItem.re\nlet component = ReasonReact.statelessComponent("RepoItem");\n\nlet make = (~someProp, ~anotherProp, _children) => \n {\n ...component,\n /* render and lifecycle methods go here */\n };\n\n\n</code></pre>\n<p>记录的其余部分是您可以从 React 中添加渲染函数和您习惯使用的生命周期方法的位置。</p>\n<p>因此,回到 <code>RepoItem.re</code>:</p>\n<pre><code class=\"language-ocaml\">let compoent = ReasonReact.statelessComponent("RepoItem");\n\nlet make = (~repo: RepoData.repo, _children) =>\n {\n ...component,\n render: (_self) =>\n <div>{ReasonReact.stringToElement(repo.full_name)}</div>\n }\n\n</code></pre>\n<p>我们这里有一个 <code>stateless component</code> (无状态的组件),它接受一个名为repo的属性(注解 RepoData 模块中的类型回购),并渲染一个 div。</p>\n<p>在ReactJS中,你可以使用 this.props 来访问render方法中的组件的属性。在 ReasonReact 中,我们接收属性作为 make 函数的标签参数,我们可以直接在渲染函数中使用它们(就像我们正在访问上面的 repos 属性的 full_name 字段一样)。</p>\n<p>make 函数也会传递一个 children 参数,但我们并没有在这个组件中使用子元素,所以我们在 _children 参数名称的开始处放置了一个 _ 下划线。这只是让 Reason 知道我们并没有真正使用这个参数。尽管我们没有使用这个参数,但它仍然需要包含在函数参数中,否则会出错。</p>\n<p>接下来,我们将充实渲染函数以呈现 repos 记录的字段:</p>\n<pre><code class=\"language-ocaml\">let component = ReasonReact.statelessComponent("RepoItem");\n\nlet make = (~repo: RepoData.repo, _children) =>\n {\n ...component,\n render: (_self) =>\n <div className="RepoItem">\n <a href=repo.html_url>\n <h2>{ReasonReact.string(repo.full_name)}</h2>\n </a>\n {ReasonReact.string(string_of_int(repo.stargazers_count) ++ " stars")}\n </div>\n };\n</code></pre>\n<p>请注意,我们必须使用 string_of_int 函数将 repo.stargazers_count 的 int 值转换为一个字符串。然后我们使用++字符串连接运算符将它与字符串 “stars” 结合起来。</p>\n<p>现在是保存并查看浏览器进度的好时机。</p>\n<p><img src=\"/images/1_rVw1dhPmkJ_cyT63uDbN0g.png\" alt=\"1_rVw1dhPmkJ_cyT63uDbN0g\" /></p>\n<h2>有状态的 React 组件又名 reducerComponent 和 Variants</h2>\n<p>我们的应用程序将加载一些数据然后渲染它,这意味着我们需要一个地方来加载数据。 React 状态组件似乎是一个明显的选择。所以我们会让我们的应用组件成为有状态的。我们通过将 <code>ReasonReact.statelessComponent</code> 更改为 <code>ReasonReact.reducerComponent</code> 来完成此操作。</p>\n<p>在 <code>App.re</code>:</p>\n<pre><code class=\"language-ocaml\">type state = {\n repoData: option(RepoData.repo)\n};\n\n/* 我们的虚拟数据 */\n\nlet dummyRepo: RepoData.repo = {\n stargazers_count: 27,\n full_name: "jsdf/reason-react-hacker-news",\n html_url: "https://github.com/jsdf/reason-react-hacker-news",\n};\n\nlet repoItem = (repoData: option(RepoData.repo)) =>\n switch (repoData) {\n | Some(repo) => <RepoItem repo />\n | None => ReasonReact.string("Loading")\n };\n\nlet make = _children => {\n ...component,\n initialState: () => {\n repoData: Some(dummyRepo)\n },\n\n reducer: ((), _) => ReasonReact.NoUpdate,\n\n render: ({state:{repoData}}) =>\n <div className="App">\n <h1> (ReasonReact.string("Decoding JSON in ReasonReact")) </h1>\n (repoItem(repoData))\n </div>,\n};\n\n\n</code></pre>\n<p>我们改变了一些关键的东西:我们已经为使用 Reason 的内置 option Variant 类型的组件状态定义了一个类型。这里简单地称为 <code>state</code>,<code>ReasonReact.statelessComponent</code> 已经成为 <code>ReasonReact.reducerComponent</code>,我们已经为组件添加了一个 <code>initialState</code> 函数,并且我们已经改变了渲染以将自身作为参数(删除 _ 下划线以便自 self 不再被忽略 ),现在正在使用 <code>{state:{repoData}</code> 作为 RepoItem 的属性。什么?!!以下语法称为解构。我们现在正在访问的 <code>self</code> 方法有一个 <code>state</code> 属性,并且使用 <code>{state:...}</code>我们说从上面的第22行开始使用它的状态。</p>\n<h2>变体!选项</h2>\n<p>我们将我们的 <code>state</code> 类型定义为:</p>\n<pre><code class=\"language-ocaml\">type state = {\n repoData: option(RepoData.repo)\n};\n</code></pre>\n<p>Reason 中 <code>option</code> 是由 “变体” 组成的类型。这基本上意味着这种类型的值可以是已经明确定义的几种可能变化中的一种。在 <code>option</code> 的情况下,变体是 <code>Some</code> 和 <code>None</code> 。<code>Some</code> 用于存在值(并且包含值本身),而 <code>None</code> 表示没有值(如 Javascript 中的 <code>null</code> )。在这里,我们'包裹' dummyRepo 在 <code>Some</code> 变体中,因为我们有一个值。</p>\n<p>该 <code>option</code> 告诉我们(和编译器),该 <code>state</code> 可以是 <code>Some</code> 或 <code>None</code> 的任何值。所以当我们使用这种类型时会有 <code>Some(RepoData.repo)</code> 或 <code>None</code> 。</p>\n<p>那么为什么使用这个包装器,而不是让我们的 <code>repoData</code> 字段包含一个值或 null ?Reason 迫使我们在实际使用价值时处理两种可能的情况。这很好,因为这意味着我们不会意外忘记处理'null(空)'情况。请注意,在调用 <code>ReasonReact.reducerComponent</code> 之前,必须定义 <code>state</code> 类型,否则会出现类似“类型构造函数状态将脱离其作用域”的错误。我们将通过创建一个名为 <code>repoItem</code> 的变量来告诉组件在每种情况下应该做什么,并定义在类型状态下定义的变例中我们想要发生的事情。</p>\n<h2>选项和模式匹配</h2>\n<p>当我们定义组件的初始状态时,目前我们已经有了 <code>repoData</code> 伪数据,但是一旦我们从服务器加载它,它的初始值将为空。但是,在 Reason 中,您不能像记录 Javascript 那样只是将记录字段的值设为 <code>null</code>。相反,可能不存在的事物需要用称为 <code>option</code> 的另一种类型“包装”。我们可以改变我们的状态类型来表示如下:</p>\n<pre><code class=\"language-ocaml\">type state = {repoData: option(RepoData.repo)};\n</code></pre>\n<p>并且在我们的 <code>initialState</code> 函数中,我们将我们的 <code>repo</code> 记录包装在 <code>Some()</code> 中:</p>\n<pre><code class=\"language-ocaml\">initialState: () => {\n repoData: Some(dummyRepo),\n}\n</code></pre>\n<p>在上面的代码中,我们使用 <code>Some</code> 和 <code>None</code> 变体来定义一个 <code>repoItem</code>,如果有一些数据,我们将这些数据传递给我们的 <code><RepoItem /></code> 模块,并将它返回给我们组件中的 UI。如果没有数据,我们告诉该函数使用None选项,返回一个 <code>div </code>来呈现 “Loading” 到 UI。</p>\n<p>然后在渲染 <code>div</code> 中,我们传递当前的 <code>repoData</code> ,然后传递给 <code>renderItem</code> 函数来处理在每种情况下要做的操作 <code>Some</code> 或 <code>None</code> 。我们无法直接将 <code>state.repoData</code> 作为 <code>RepoItem</code> 的 <code>repo</code> 的属性,因为它被包装在一个 <code>option()</code> 中,但是 <code>RepoItem</code> 期待它没有选项包装。那么我们如何解开它呢?我们使用模式匹配。这是 <code>Reason</code> 用来涵盖所有可能的情况(或者至少明确地抛出错误)的地方。模式匹配使用 <code>switch</code> 语句。然而,与 Javascript 中的 <code>switch</code> 语句不同,reason 中的 <code>switch</code> 语句可以匹配值的类型(例如 <code>Some</code> 和 <code>None</code> ),而不仅仅是值本身。我们将改变我们的渲染方法,使用 <code>switch</code> 在每种可能的情况下提供逻辑来呈现我们的 <code>repo</code> 项目。我们可以通过创建一个函数 <code>renderItem</code> 来处理每个 <code>case</code> 并根据结果进行渲染。</p>\n<pre><code class=\"language-ocaml\">repoItem = (repoData: option(RepoData.repo)) => \n switch (repoData) {\n | Some(repo) => <RepoItem repo />\n | None => ReasonReact.string("Loading")\n };\n</code></pre>\n<p>在这里你可以看到 <code>switch</code> 语句有一个 <code>case</code> 与 <code>state.repoData</code> 类型的 <code>Some</code> 类型匹配,并且将实际的 <code>repo</code> 记录提取到一个名为 <code>repo</code> 的变量中,然后它将它用在 => 右边的表达式中, 返回一个 <RepoItem> 元素。这个表达式只会在 <code>state.repoData</code> 是 <code>Some</code> 的情况下使用。或者,如果 <code>state.repoData</code> 为 <code>None</code>,则将显示文本 “Loading”。</p>\n<p>我们将在我们的 div 中调用 repoItem 并将它作为 repoData 解构的 state.repoData。</p>\n<pre><code class=\"language-ocaml\">render: ({state: {repoData}}) => \n <div className="App">\n <h1> (ReasonReact.string("Decoding JSON in ReasonReact")) </h1>\n (repoItem(repoData))\n </div>,\n</code></pre>\n<p>如果您运行 <code>yarn start</code> 开始,您应该在浏览器中看到与以前相同的输出:</p>\n<p><img src=\"/images/1_rVw1dhPmkJ_cyT63uDbN0g.png\" alt=\"1_rVw1dhPmkJ_cyT63uDbN0g\" /></p>\n<h2>Reducer Components</h2>\n<p>那么,为什么 Reason React 中的有状态组件类型称为 reducerComponent ?与 ReactJS 相比,ReasonReact 处理组件状态更改的方式略有不同。如果你已经使用 <a href=\"https://redux.js.org/\">Redux</a> ,它会看起来很熟悉。如果你还没有,不要担心,这里不需要背景知识。</p>\n<p>基本上,不是像 onClick 那样在事件处理程序中做一堆事情然后调用 <code>this.setState</code> ,我们只需要知道我们想要对组件状态做出什么样的改变,然后调用 <code>self.send</code> 一个“ <code>action</code> ”,它只是一个表示应该发生的状态更改的值,以及我们需要更改的任何信息。这意味着大部分状态变化代码都可以用纯函数隔离,这使得它更容易跟踪,并且更容易编写测试。</p>\n<p>我们可以尝试通过这种方式进行状态更改,方法是首先将 <code>state</code> 设置为 <code>None</code> ,然后在用户单击按钮后更改状态。这是一个人为的例子,但它对说明状态变化很有用。点击此按钮后,假设我们正在从API加载数据:)。</p>\n<p>首先,我们需要添加一个名为 <code>action</code> 的类型,它列举了可能发生在我们组件中的各种可能的状态变化。现在只有一个:<code>Loaded</code>,用于加载 repo 数据时:</p>\n<pre><code class=\"language-ocaml\">type action = \n | Loaded(RepoData.repo);\n</code></pre>\n<p>之后,我们添加一个 <code>reducer</code> 方法,它接受一个这样的动作和当前状态,然后计算并返回更新的状态:</p>\n<pre><code class=\"language-ocaml\">reducer = (action, _state) => \n switch (action) {\n | Loaded(loadedRepo) =>\n ReasonReact.Update({\n repoData: Some(loadedRepo)\n })\n };\n</code></pre>\n<p>您可以看到,我们的实现是对动作类型进行模式匹配并返回包含新状态的 <code>ReasonReact.Update</code>。现在我们只是为 <code>Loaded</code> 行动提供一个案例,但在未来,我们可以想象在这里实施其他类型的状态更改,以响应不同的 <code>action</code> 变体。</p>\n<p>接下来我们更改 <code>initialState</code> ,以无 repo 数据开始:</p>\n<pre><code class=\"language-ocaml\">initialState: () => {\n repoData: None\n},\n</code></pre>\n<p>最后,我们在渲染函数中添加一个按钮元素。我们使用 <code>self.send</code> 方法添加到我们的解构对象中,为按钮的 <code>onClick</code> <code>prop</code> 创建一个处理函数。</p>\n<p><code>send</code> 采取点击事件调用我们想要使用的动作和它期望的任何值。在这里,<code>send(Loaded(dummyRepo))</code>,将点击转换为我们 <code>reducer</code> 的动作。像这样的处理程序也可以使用来自 <code>click</code> 事件对象的信息,但在这种情况下我们不需要它,所以我们把下划线 <code>_</code> 放在它之前忽略它。我们可以创建这样一个按钮:</p>\n<pre><code class=\"language-ocaml\"><button onClick=(_event => send(Loaded(dummyRepo)))>\n (ReasonReact.string("Load Repos"))\n</button>\n</code></pre>\n<p>我们可以显示一条消息,在初始空白状态下(当 <code>state.repoData</code> 为 <code>None</code> 时)点击按钮来代替呈现的 <code>RepoItem</code> :</p>\n<pre><code class=\"language-ocaml\">repoItem = (repoData: option(RepoData.repo)) => \n switch (repoData) {\n | Some(repo) => <RepoItem repo />\n | None => ReasonReact.string("Click Button To Load")\n };\n</code></pre>\n<p>与在 JS React 中调用 <code>setState</code> 相比,使用 <code>action</code> 和 <code>reducer</code> 的额外步骤似乎过于复杂,但随着有状态组件的增长并具有更多可能的状态(它们之间可能存在越来越多的转换),组件很容易成为难以跟踪和无法测试的纠结。 这是 <code>action-reducer</code> 模型真正闪耀的地方。</p>\n<p>您可以在 <a href=\"https://github.com/idkjs/decoding-json-in-reason-react/tree/render-button-detour\">render-button-detour</a> 附带的 repo 中看到此版本的代码。</p>\n<p>好,现在我们知道如何进行状态更改,让我们把它变成一个更现实的应用程序。</p>\n<h2>使用数组与单个 Repo</h2>\n<p>在我们从 JSON 加载数据之前,还需要对组件进行一次更改。我们实际上想要显示 repo 列表,而不仅仅是一个清单,所以我们需要改变我们的状态类型:</p>\n<pre><code class=\"language-ocaml\">type state = {\n repoData: option(array(RepoData.repo))\n};\n</code></pre>\n<p>并对我们的虚拟数据进行相应的更改:</p>\n<pre><code class=\"language-ocaml\">dummyRepos: array(RepoData.repo) = [|\n {\n stargazers_count: 27,\n full_name: "jsdf/reason-react-hacker-news",\n html_url: "https://github.com/jsdf/reason-react-hacker-news"\n },\n {\n stargazers_count: 93,\n full_name: "reasonml/reason-tools",\n html_url: "https://github.com/reasonml/reason-tools"\n }\n|];\n</code></pre>\n<p>呃,<code>[| ... |]</code> 语法?这是 Reason 的数组字面量语法。如果你没有 <code>|</code> 管道字符(所以它看起来像正常的 JS 数组语法),那么你会定义一个 <code>List</code> 而不是一个数组。在 Reason 中列表是不可变的,而数组是可变的(如 Javascript 数组),但是如果处理可变数量的元素,则列表更容易处理。无论如何,我们正在使用一个数组。</p>\n<p>我们需要查看代码并将所有引用 <code>repoData</code> 的地方都改为 <code>RepoData.repo</code>,而不是指定 <code>array(RepoData.repo)</code>。</p>\n<p>最后,通过映射 <code>repos</code> 数组并为每个 <code>RepoItem</code> 创建一个 <code><RepoItem /></code>,我们将改变渲染方法来渲染一个 <code>RepoItem</code> 数组而不是一个。 我们必须使用 <code>ReasonReact.array</code> 将元素数组转换为元素本身,以便它可以在下面的 JSX 中使用。</p>\n<pre><code class=\"language-ocaml\">repoItems = (repoData: option(array(RepoData.repo))) =>\n switch (repoData) {\n | Some(repo) =>\n ReasonReact.array(\n Array.map(\n (repo: RepoData.repo) => <RepoItem key=repo.full_name repo />,\n repos,\n ),\n )\n | None => ReasonReact.string("Loading")\n };\n\nlet make = _children => {\n ...component,\n\n initialState: () => {\n repoData: Some(dummyRepos)\n },\n\n reducer: ((), _) => ReasonReact.NoUpdate,\n\n render: ({state: {repoData}}) =>\n <div className="App">\n <h1> (ReasonReact.string("Decoding JSON in ReasonReact")) </h1>\n (repoItems(repoData))\n </div>,\n};\n</code></pre>\n<p>我在 <code>repo</code> 中标记了代码的各个部分。所以在这一点上,你可以检查 <a href=\"https://github.com/idkjs/decoding-json-in-reason-react/tree/setting-up-arrays\"><code>setting-up-arrays tag</code></a>。</p>\n<p>现在,加载一些真实的数据。</p>\n<h2>BuckleScript</h2>\n<p>在获取我们的 JSON 并将其转换为记录之前,首先我们需要安装一些额外的依赖关系。运行:</p>\n<pre><code class=\"language-ocaml\">npm install --save bs-fetch @glennsl/bs-json\n</code></pre>\n<p>或者</p>\n<pre><code class=\"language-ocaml\">yarn add bs-fetch @glennsl/bs-json\n</code></pre>\n<p>以下是这些包的作用:<a href=\"https://github.com/reasonml-community/bs-fetch\"><code>bs-fetch</code></a>:包装浏览器获取 API,以便我们可以从 Reason <a href=\"https://github.com/glennsl/bs-json\"><code>@glennsl/bs-json</code></a> 中使用它:允许使用将从服务器获取的 JSON 转换为 Reason 记录。</p>\n<p>这些包与我们一直使用的 Reason-to-JS 编译器一起工作,这就是所谓的 <a href=\"https://bucklescript.github.io/\">BuckleScript</a> 。</p>\n<p>在我们可以使用这些新安装的 BuckleScript 包之前,我们需要让 BuckleScript 知道它们。为此,我们需要对项目根目录下的 <code>.bsconfig</code> 文件进行一些更改。在 <code>bs-dependencies</code> 部分中,添加 <code>bs-fetch</code> 和 <code>bs-json</code>:</p>\n<pre><code class=\"language-ocaml\">{\n"name": "reason-scripts",\n"sources": ["src"],\n"bs-dependencies": [\n "reason-react",\n "bs-jest","bs-fetch", // add this\n "@glennsl/bs-json" // and this too\n],\n// ...more stuff\n</code></pre>\n<p>您需要终止并重新启动 <code>yarn start</code> / <code>npm start</code> 命令,以便构建系统可以获取对 <code>.bsconfig</code> 的更改。</p>\n<h2>读取 JSON</h2>\n<p>现在我们已经安装了 <code>bs-json</code>,我们可以使用 <code>Json.Decode</code> 来读取 JSON 并将其转化为记录。</p>\n<p>我们将在 <code>RepoData.re</code> 的末尾定义一个名为 <code>parseRepoJson</code> 的函数:</p>\n<pre><code class=\"language-ocaml\">// RepoData.re\n\ntype repo = {\n full_name: string,\n stargazers_count: int,\n html_url: string\n};\n\nlet parseRepoJson = (json: Js.Json.t): repo => {\n full_name: Json.Decode.field("full_name", Json.Decode.string, json),\n stargazers_count: Json.Decode.field("stargazers_count", Json.Decode.int, json),\n html_url: Json.Decode.field("html_url", Json.Decode.string, json)\n}\n\n</code></pre>\n<p>我们定义了一个名为 <code>parseRepoJson</code> 的函数,它接受一个名为 <code>json</code> 的参数并返回 <code>RepoData.repo</code> 类型的值。<code>Json.Decode</code> 模块提供了一组函数,我们将它们组合在一起来提取 JSON 的字段,并确保我们得到的值是正确的类型。</p>\n<h2>不要重复自己</h2>\n<p>这看起来有点罗嗦。我们是否真的必须一遍又一遍地写 <code>Json.Decode</code> ?</p>\n<p>不,Reason 有一些方便的语法来帮助我们,当我们需要一次又一次地引用特定模块的输出时。一种选择是“打开”模块,这意味着它的所有输出在当前作用域都可用,所以我们可以抛弃 <code>Json.Decode</code> 限定符:</p>\n<pre><code class=\"language-ocaml\">Json.Decode;\n\nlet parseRepoJson = (json: repo) =>\n{\n full_name: field("full_name", string, json),\n stargazers_count: field("stargazers", int, json),\n html_url: field("html_url", string, json)\n};\n\n</code></pre>\n<p>但是,如果打开多个模块,这会引起名称冲突的风险。另一种选择是使用模块名称,后跟一个句点<code>.</code>在表达之前。在表达式内部,我们可以使用模块的任何导出,而不用模块名称进行限定:</p>\n<pre><code class=\"language-ocaml\">let parseRepoJson = (json: Js.Json.t): repo =>\nJson.Decode.{\n full_name: field("full_name", string, json),\n stargazers_count: field("stargazers_count", int, json),\n html_url: field("html_url", string, json),\n};\n</code></pre>\n<p>注意 <code>(json: Js.Json.t):repo</code>。在这里,我们输入预期的 json 值作为 <code>Js.Json.t</code>,也就是说传入的 <code>repo</code> <code>json</code> 类型必须是(json:Js.Json.t)。请参阅 <a href=\"https://twitter.com/@nikgraf\">@nikgraf</a> 的 <a href=\"https://egghead.io/lessons/reason-type-parameters-in-reason\">egghead系列 Reason 类型参数视频</a> 以了解更多信息。事实上,如果你对Reason感兴趣并且没有看过,现在就去看看,然后再回来。然后每周观看一次,直到你找到它。每次你都会学到一些东西。</p>\n<p>现在让我们通过添加一些代码来测试它,该代码定义了一个 JSON 字符串并使用我们的 <code>parseRepoJson</code> 函数来解析它。</p>\n<p>在app.re中:</p>\n<pre><code class=\"language-ocaml\">dummyRepos: array(RepoData.repo) = [|\n RepoData.parseRepoJson(\n Js.Json.parseExn(\n {js|\n {\n "stargazers_count": 93,\n "full_name": "reasonml/reason-tools",\n "html_url": "https://github.com/reasonml/reason-tools"\n }\n |js}\n )\n )\n|];\n</code></pre>\n<p>不要担心理解什么 <code>Js.Json.parseExn</code> 或奇怪的 <code>{js | ... | js}</code> 的东西(这是一个可选的<a href=\"https://bucklescript.github.io/bucklescript/Manual.html#_bucklescript_annotations_for_unicode_and_js_ffi_support\">字符串字面量语法</a>)。返回到浏览器,您应该看到从该 JSON 输入成功呈现页面。</p>\n<p><img src=\"/images/1_vJeQUw9FWZBb2lK_muJnaQ.png\" alt=\"1_vJeQUw9FWZBb2lK_muJnaQ.png\" /></p>\n<p>请参阅 repo 标签 <a href=\"https://github.com/idkjs/decoding-json-in-reason-react/tree/bs-json-parsing\">bs-json-parsing</a>。</p>\n<h2>获取数据</h2>\n<p>请参阅 <a href=\"https://github.com/idkjs/decoding-json-in-reason-react/tree/fetching-data\">fetching-data</a></p>\n<p>查看 <a href=\"https://api.github.com/search/repositories?q=topic%3Areasonml&type=Repositories\">Github API 响应</a>的形式,我们对items 字段感兴趣。</p>\n<p><img src=\"/images/1_Tc7TgXVmGv4i_xt4Lz4dxA.png\" alt=\"1_Tc7TgXVmGv4i_xt4Lz4dxA.png\" /></p>\n<p>该字段包含一个 repos 数组。我们将添加另一个函数,它使用我们的 <code>parseRepoJson</code> 函数将 items 字段解析为一个记录数组。</p>\n<p>在 <code>RepoData.re</code>中:</p>\n<pre><code class=\"language-ocaml\">parseReposResponseJson = json => \n Json.Decode.field("items", Json.Decode.array(parseRepoJson), json);\n</code></pre>\n<p>最后,我们将使用 <code>bs-fetch</code> 包来向 API 发送 HTTP 请求。</p>\n<p>但首先,更多的新语法!我保证这是最后一点。管道运算符 <code>|></code> 只是将 <code>|></code> 运算符左边的表达式的结果取出来,并使用该值调用 <code>|></code> 运算符右边的函数。</p>\n<p>例如,以下代码:</p>\n<pre><code class=\"language-ocaml\">(doThing3(doThing2(doThing1(arg))));\n</code></pre>\n<p>通过管道运算符,我们可以做到:</p>\n<pre><code class=\"language-ocaml\">arg |> doThing1 |> doThing2 |> doThing3\n</code></pre>\n<p>这让我们可以模拟类似 Javascript 中的 Promises 的链式 API ,不同之处在于 <code>Js.Promise.then_</code> 是我们用 promise 作为参数调用的函数,而不是 promise 对象上的方法。</p>\n<p>在 <code>RepoData.re</code> 中:</p>\n<pre><code class=\"language-ocaml\">\nlet reposUrl = "https://api.github.com/search/repositories?q=topic%3Areasonml&type=Repositories";\n\nlet fetchRepos = () => {\n Fetch.fetch(reposUrl)\n |> Js.Promise.then_(Fetch.Response.text)\n |> Js.Promise.then_(\n jsonText =>\n Js.Promise.resolve(parseReposResponseJson(Js.Json.parseExn(jsonText)))\n );\n}\n\n</code></pre>\n<p>我们可以通过暂时打开 <code>Js.Promise</code> 来使 Promise 链式 <code>fetchRepos</code> 更简洁:</p>\n<pre><code class=\"language-ocaml\">fetchRepos = () =>\n Js.Promise.(\n Fetch.fetch(reposUrl)\n |> then_(Fetch.Response.text)\n |> then_(\n jsonText =>\n resolve(parseReposResponseJson(Js.Json.parseExn(jsonText)))\n )\n );\n</code></pre>\n<p>最后,回到 <code>App.re</code> 中,我们将添加一些代码来加载数据并将其存储在组件状态中:</p>\n<pre><code class=\"language-ocaml\">type state = {\n repoData: option(array(RepoData.repo))\n};\n\ntype action = \n | Loaded(array(RepoData.repo));\n\nlet component = ReasonReact.reducerComponent("App");\n\nlet dummyRepos: array(RepoData.repo) = [|\n RepoData.parseRepoJson(\n Js.Json.parseExn(\n {js|\n {\n "stargazers_count": 93,\n "full_name": "reasonml/reason-tools",\n "html_url": "https://github.com/reasonml/reason-tools"\n }\n |js},\n ),\n ),\n|];\n\nlet repoItems = (repoData: option(array(RepoData.repo))) =>\n switch (repoData) {\n | Some(repos) =>\n ReasonReact.array(\n Array.map(\n (repo: RepoData.repo) => <RepoItem key=repo.full_name repo />,\n repos,\n ),\n )\n | None => ReasonReact.string("Loading")\n };\n\nlet reducer = (action, _state) => \n switch (action) {\n | Loaded(loadedRepo) => ReasonReact.Update({\n repoData: Some(loadedRepo)\n })\n };\n\nlet make = _children => {\n ...component,\n initialState: () => {\n repoData: None\n },\n\n didMount: self => {\n let handleReposLoaded = repoData => self.send(Loaded(repoData));\n\n RepoData.fetchRepos()\n |> Js.Promise.then_(repoData => {\n handleReposLoaded(repoData);\n Js.Promise.resolve();\n })\n |> ignore;\n },\n\n reducer,\n\n render: ({state: {repoData}}) =>\n <div className="App">\n <h1> (ReasonReact.string("Decoding JSON in ReasonReact")) </h1>\n (repoItems(repoData))\n </div>,\n};\n\n</code></pre>\n<p>首先我们实现 <code>didMount</code> 生命周期方法。我们使用 <code>self.send</code> 创建一个名为 <code>handleReposLoaded</code> 的函数来处理我们加载的数据并更新组件状态。我们将在我们的 <code>RepoData.fetchRepos()</code> 函数中调用它,并将它传递给期望的 <code>repoData</code> 值。 <code>handleReposLoaded</code> 然后将该值传递给 <code>self.send</code>,我们将它传递给定义的 <code>action</code> 类型。<em><strong>self.send 与 <code>action</code> 类型一起使用以便使用它,请确保您已经定义了要使用它的 <code>action</code> 。</strong></em> 因此,使用我们的 <code>RepoData.fetchRepos()</code> 函数,我们加载数据。就像 Javascript中的链式 Promise ,我们将其传递到 <code>Js.Promise.then_</code> 中,在那里我们用加载的数据调用 <code>handleReposLoaded</code> 函数,更新组件状态。</p>\n<p>我们通过返回 <code>Js.Promise.resolve()</code> 来结束 promise 链。定义 promise 链的整个表达式然后 <code>|></code> 传送到一个称为 <code>ignore</code> 的特殊函数,它只是告诉 Reason 我们不打算对 promise 链表达式计算的值做任何事情(我们只关心副作用 它具有调用更新函数的功能)。</p>\n<p>这就是它在浏览器中的样子:</p>\n<p><img src=\"/images/1_lYil0QkDtiH1PQLDrXvg3g.png\" alt=\"1_lYil0QkDtiH1PQLDrXvg3g.png\" /></p>\n<h2>添加一些 CSS</h2>\n<p>让我们回到 <code>index.re</code>。将此代码添加到文件的顶部:</p>\n<pre><code class=\"language-ocaml\">[%bs.raw {|\nrequire('./index.css');\n|}];\n</code></pre>\n<p>这<code>%bs.raw</code> 的东西允许我们在 <code>{|</code> 和 <code>|}</code> 之间加入一些简单的 Javascript 代码。在这种情况下,我们只是使用它来以通常的 Webpack 方式包含一个 CSS 文件。保存后,您应该看到应用于应用的一些样式更改。您可以打开为我们制作的 <code>create-react-app</code> 的 <code>index.css</code> 文件,并根据您的内容定制样式。让我们添加一些 <code>margin</code>,以便 ui 不会被推到左侧。打开 <code>index.css</code> 并将其更改为:</p>\n<pre><code class=\"language-ocaml\">{\n margin:2em;\n padding:0;\n font-family: sans-serif;\n}\n</code></pre>\n<p>您还可以通过传递使用 <a href=\"https://reasonml.github.io/reason-react/docs/en/style.html#docsNav\"><code>ReactDOMRe.Style.make</code></a> 创建的样式属性,在 React 组件中使用内联样式:</p>\n<pre><code class=\"language-ocaml\">style={ReactDOMRe.Style.make(~color="red", ~fontSize="68px")()}\n</code></pre>\n<p>就是这样!</p>\n<p>你可以在<a href=\"https://decoding-json-in-reason-react.netlify.com/\">这里</a>看到完成的应用程序。完整的源代码在 <a href=\"https://github.com/idkjs/decoding-json-in-reason-react\">Github</a> 上可用。</p>\n<h2>奖金:使用 Netlify 和 git 子树部署 Demo。</h2>\n<p>然后 <code>yarn build</code> 生成以获得项目的生产版本。</p>\n<p>从项目的 <code>.gitignore</code> 文件中删除 <code>build</code> (它由 create-react-app 默认忽略)。</p>\n<p>将以下命令添加到 package.json 的脚本键值中:</p>\n<pre><code class=\"language-ocaml\">...\n "deploy": "git subtree push --prefix build origin gh-pages",\n...\n</code></pre>\n<p>确保 git 知道你的子树(你的网站的子文件夹)。运行:</p>\n<pre><code class=\"language-ocaml\">add build && git commit -m "Initial dist subtree commit"\n</code></pre>\n<p>运行 <code>yarn deploy</code>。</p>\n<p>如果没有,请注册 Netlify 。登录后,点击大蓝 “Git的新站点” 按钮。</p>\n<p>选择您的 repo,将目录更改为您的 gh-pages 目录,然后单击 “Deploy Site” 按钮。</p>\n<p><img src=\"/images/1_UbMYnovAa1GUt0PYsO7x9A.png\" alt=\"1_UbMYnovAa1GUt0PYsO7x9A.png\" /></p>\n<h2>谢谢!</h2>\n<p>如果您对本文有任何反馈,可以发推文给我:<a href=\"https://www.twitter.com/@_idkjs\">@_idkjs</a>。感谢 <a href=\"https://www.twitter.com/@ur_friend_james\">@ur_friend_james</a> 的原始帖子,可以在<a href=\"https://jamesfriend.com.au/a-first-reason-react-app-for-js-developers\">这里</a>找到。</p>\n","author":"lanqy"},{"title":"OCaml 符号ReasonML 中的持久性 React Native Apps","created":"2018/05/28","link":"2018/05/28/persistence-in-reasonml-react-native-apps","description":"ReasonML 中的持久性 React Native Apps","content":"<h1>ReasonML 中的持久性 React Native Apps</h1>\n<p>译自: https://jwheatley.co/persistence-in-reasonml-react-native-apps/</p>\n<blockquote>\n<p>让我们学习如何通过翻转 <code>switch</code> 来保持 ReasonML RN 应用程序中的状态!</p>\n</blockquote>\n<h2>什么是ReasonML</h2>\n<p>ReasonML(或作为非SEO友好缩写的 Reason)是来自 Facebook 的开源项目,它使得 OCaml 易于用于 JS 开发人员。它通过为语言提供更友好的语法并提供与 JS 生态系统互操作性如黄油一样平滑的工具。</p>\n<p>OCaml 有一个超强的类型系统,它可以取代 Flow 或 TypeScript 提供的任何东西,而且 ReasonML 与工具捆绑在一起,可以替代几乎所有的工具来让 JS 每天都可以编写(ESlint→ReasonML / OCaml 类型系统 ,Flow / TS → ReasonML 语法,Prettier → refmt,JS / ES 模块捆绑器 → ReasonML / OCaml 模块,Babel → BuckleScript 等)。</p>\n<p>ReasonML 受 Facebook 支持的重要之处在于它通过 ReasonReact 对 React 项目提供一流的支持,所以如果您厌倦了与 Flow 错误作斗争,并且仍然对 TypeScript 如何适应 React 生态系统感到困惑,那么您应该试一试 ReasonML!</p>\n<p>本指南旨在帮助您将状态持久性集成到您的ReasonML React Native 应用程序中,并促进为开发 ReasonReact 应用程序的人创建良好的脱机体验,而无需借助基于 JS 的状态管理解决方案(例如:Redux + Redux-Persist)。</p>\n<h2>入门</h2>\n<p>要开始编写 ReasonReact Native 应用程序,我建议您使用 <code>create-react-native-app</code> 路线:</p>\n<p>假设您已经安装了最新版本的 <a href=\"https://nodejs.org/en/\">Node.js</a>,请在终端中运行这些命令</p>\n<pre><code class=\"language-ocaml\">npm install -g create-react-native-app\n create-react-native-app Switcheroo --scripts-version reason-react-native-scripts\n cd Switcheroo\n npm start\n # 最好是,我会在前面的命令前加上`code。 &&`\n # 在运行启动脚本之前在VS代码中打开它\n # 以防止需要额外努力在编辑器中打开\n</code></pre>\n<p>然后,如 CRNA 文档(截至1/1/2018)所详述:</p>\n<blockquote>\n<p>在您的 iOS 或 Android 手机上安装 <a href=\"https://expo.io/\">Expo</a> 应用程序,并使用终端中的 QR 码打开您的应用程序。在应用程序的“项目”选项卡中查找 QR 扫描仪。</p>\n</blockquote>\n<h2>UI 脚手架</h2>\n<p>现在我们已经启动了开发环境并运行了,现在是时候进入有趣的部分并编写一些代码了!</p>\n<p>对于初学者,您需要创建一个名为Switcheroo.re的新文件,该文件将位于App.re旁边的src文件夹中。这是我们将为我们的切换创建逻辑的地方。</p>\n<p>在这个文件中,我们为一个简单的 RN Switch 组件设置了基础,处理 <a href=\"https://reasonml.github.io/reason-react/docs/en/state-actions-reducer.html\">ReasonReact</a> 中提供的 <code>reducerComponent</code> 构件块的状态。</p>\n<pre><code class=\"language-ocaml\">\n/* Switcheroo.re */\n\nopen BsReactNative;\n\ntype state = {toggled: bool};\n\ntype action = \n | SetSwitchValue(bool);\n\nlet component = ReactReact.reducerComponent("Switcheroo);\n\nlet make = (_children) => {\n ...component,\n initialState: () => {toggled: false},\n reducer: (action, _state) =>\n switch action {\n | SetSwitchValue(v) => ReasonReact.Update({toggled: v})\n },\n render: (self) =>\n <Switch\n value=self.state.toggled\n onTintColor="#DD4C39"\n onValueChange=((value) => self.reduce(() => SetSwitchValue(value), ()))\n />\n};\n\n</code></pre>\n<p>在App.re中,您可以用新编写的 Switcheroo 组件替换 View 的内部(下面的代码)。</p>\n<pre><code class=\"language-ocaml\">\n/* App.re */\n\nopen BsReactNative;\n\nlet app = () => \n <View style=Style.(style([flex(1.), justifyContent(Center), alignItems(Center)]))>\n <Switcheroo />\n </View>;\n</code></pre>\n<p>很好,我们有一个很酷的 <code>switch</code> ,我们可以来回切换,但是为什么当我重新打开应用程序的时候,它不会保持开启? 哦,是的,我们忘记了在本地保存状态;让我们去做吧!</p>\n<h2>持久性</h2>\n<p>要在 React Native中 保持状态,您必须使用 AsyncStorage 模块。这使您可以将序列化数据设置为长期数据存储并在以后检索它,即使应用程序已关闭并由用户重新启动,也可使应用程序停留在其数据上。</p>\n<p>上一节中的关键词是“序列化”,这意味着您的数据必须转换为字符串格式才能保存并从字符串中恢复并解析回实时数据结构以在您的应用中使用它。</p>\n<p>为了在 ReasonML 中做到这一点,我们需要调用 bs-json 的强大功能,它为使用 JSON 结构提供帮助。</p>\n<ul>\n<li>首先,您需要运行 <code>npm i -S bs-json</code> 来安装软件包。</li>\n<li>接下来,将它添加到你的 bsconfig.json 的 bs-dependencies 数组中。</li>\n<li>当你在它的时候,改变你的 bsconfig.json 的名字道具为 “Switcheroo”。</li>\n</ul>\n<p>完成后,bsconfig.json(支持 ReasonML 开发的 BuckleScript 工具链的配置文件)应如下所示:</p>\n<pre><code class=\"language-json\">{\n "name": "Switcheroo",\n "reason": {\n "react-jsx": 2\n },\n "bsc-flags": ["-bs-super-errors"],\n "bs-dependencies": ["bs-react-native", "reason-react", "bs-json"],\n "sources": [\n {\n "dir": "src"\n }\n ],\n "refmt": 3\n}\n</code></pre>\n<p>现在,让我们支撑我们的持久性函数,并将其设置为在组件更新状态时运行。</p>\n<pre><code class=\"language-ocaml\">open BsReactNative;\ntype state = {toggled: bool};\ntype action =\n | SetSwitchValue(bool);\nlet persist = state => {\n /* convert state to JSON */\n /* set it in RN's AsyncStorage */\n ()\n};\nlet component = ReasonReact.reducerComponent("Switcheroo");\nlet make = (_children) => {\n ...component,\n initialState: () => {toggled: false},\n reducer: (action, _state) =>\n switch action {\n | SetSwitchValue(v) => ReasonReact.Update({toggled: v})\n },\n didUpdate: ({newSelf}) => persist(newSelf.state),\n render: (self) =>\n <Switch\n value=self.state.toggled\n onTintColor="#DD4C39"\n onValueChange=((value) => self.reduce(() => SetSwitchValue(value), ()))\n />\n};\n</code></pre>\n<p>在这个函数中,我们需要使用 bs-json 将我们的状态编码为 JSON 并将其设置为我们的 AsyncStorage 位置,即 “Switcheroo.state”。</p>\n<pre><code class=\"language-ocaml\">/* Switcheroo.re (partial) */\nlet persist = (state) => {\n /* convert state to JSON */\n let stateAsJson =\n Json.Encode.(object_([("toggled", Js.Json.boolean(Js.Boolean.to_js_boolean(state.toggled)))]))\n |> Js.Json.stringify;\n /* set it in RN's AsyncStorage */\n AsyncStorage.setItem(\n "Switcheroo.state",\n stateAsJson,\n ~callback=\n (e) =>\n switch e {\n | None => ()\n | Some(err) => Js.log(err)\n },\n ()\n )\n |> ignore\n};\n</code></pre>\n<p>所以,现在,如果您现在查看您的应用程序并翻转开关几次,您会发现没有任何问题(这太棒了!),但是当您刷新应用程序时,您会注意到您的应用程序仍然没有正常工作,捡起它离开的地方。</p>\n<h2>Re-hydration(美化或优化)</h2>\n<p>为了重新维护我们的状态,我们需要:</p>\n<ul>\n<li>创建一个 re-hydrate <code>action</code> 来更新我们 reducerComponent 的状态</li>\n<li>创建一个 rehydrate 函数,从 AsyncStorage 中检索 JSON 并将其解码为一个 ReasonML 记录</li>\n<li>设置 Switcheroo 组件在组件激活时调用我们的 Rehydrate 函数</li>\n</ul>\n<p>满足上述步骤的代码在这里:</p>\n<pre><code class=\"language-ocaml\">/* Switcheroo.re */\nopen BsReactNative;\nlet storageKey = "Switcheroo.state";\ntype state = {toggled: bool};\ntype action =\n | SetSwitchValue(bool)\n | Rehydrate(state);\nlet persist = (state) => {\n /* convert state to JSON */\n let stateAsJson =\n Json.Encode.(object_([("toggled", Js.Json.boolean(Js.Boolean.to_js_boolean(state.toggled)))]))\n |> Js.Json.stringify;\n /* set it in RN's AsyncStorage */\n AsyncStorage.setItem(\n storageKey,\n stateAsJson,\n ~callback=\n (e) =>\n switch e {\n | None => ()\n | Some(err) => Js.log(err)\n },\n ()\n )\n |> ignore\n};\nlet rehydrate = (self) => {\n Js.Promise.(\n /* begin call to AsyncStorage */\n AsyncStorage.getItem(storageKey, ())\n |> then_(\n (json) =>\n (\n switch json {\n | None => ()\n | Some(s) =>\n /* parse JSON, decode it into a ReasonML Record, and reset the state */\n let parsedJson = Js.Json.parseExn(s);\n let state = Json.Decode.{toggled: parsedJson |> field("toggled", bool)};\n self.ReasonReact.reduce(() => Rehydrate(state), ());\n ()\n }\n )\n |> resolve\n )\n |> ignore\n );\n ReasonReact.NoUpdate\n};\nlet component = ReasonReact.reducerComponent("Switcheroo");\nlet make = (_children) => {\n ...component,\n initialState: () => {toggled: false},\n reducer: (action, _state) =>\n switch action {\n | SetSwitchValue(v) => ReasonReact.Update({toggled: v})\n | Rehydrate(s) => ReasonReact.Update(s)\n },\n didUpdate: ({newSelf}) => persist(newSelf.state),\n didMount: (self) => rehydrate(self),\n render: (self) =>\n <Switch\n value=self.state.toggled\n onTintColor="#DD4C39"\n onValueChange=((value) => self.reduce(() => SetSwitchValue(value), ()))\n />\n};\n</code></pre>\n<p>现在,你应该有一个很酷的小应用程序,让你切换一个 ReasonML 主题的开关并保持它的状态。甜!</p>\n<p>有关 ReasonML 中持久化和重新保持 JSON 状态的更复杂示例,请查看我为我的个人健身追踪器编写的代码。</p>\n<p>我希望你从这篇文章中学到了很多东西,并在阅读时享受自己的乐趣! 欲了解更多内容,你可以在 <a href=\"https://twitter.com/FiberJW\">Twitter</a> 上关注我! 这是与我联系并了解我正在做的新事情,我正在探索的新想法或正在学习的新技术的最简单的地方!</p>\n<p>祝你有个好的一天!</p>\n<p>—--- Juwan</p>\n","author":"lanqy"},{"title":"ReasonML 入门","created":"2018/05/28","link":"2018/05/28/reasonml-getting-started","description":"ReasonML 入门","content":"<h1>ReasonML 入门</h1>\n<p>译自: https://dev.to/jlewin_/reasonml-getting-started-53gi</p>\n<p>在本教程中,我们将使用Reason构建一个小型天气应用程序。有一个链接到页面底部的源代码。本教程假设您对React有基本的了解,因为我们将使用ReasonReact绑定来构建应用程序。如果您以前没有使用过 React,那么<a href=\"https://dev.to/tylermcginnis/a-comprehensive-guide-to-reactjs-in-2018--4nbc\">这篇文章</a>是一个很好的开始。</p>\n<h2>什么是 Reason</h2>\n<p>Reason 是 OCaml 的新语法,由 Facebook 开发,受 JavaScript 影响很大。它有 100% 的类型覆盖率,这导致了一个非常强大的类型系统。</p>\n<p>Reason 也适用于跨平台开发。我们可以使用 BuckleScript 将我们的代码编译成(可读的)JavaScript,从而打开整个 Web 平台。感谢 OCaml,它也可以使用 Reason 进行本地开发。</p>\n<p>此外,Reason 还可以访问整个 JS 和 OCaml 生态系统,并提供 ReasonReact 以使用 ReactJS 构建 UI 组件。文档中有一个<a href=\"https://reasonml.github.io/docs/en/what-and-why.html\">有用的页面</a>,可以更详细地解释优势!</p>\n<h2>要求</h2>\n<p>首先,让我们确保我们安装了正确的工具。</p>\n<p>我们将使用 Create React App 引导项目。如果您之前没有使用过,请通过运行 <code>npm i -g create-react-app</code> 进行安装。还有两个我们需要开始的软件包:</p>\n<ul>\n<li>Reason CLI: Reason 工具链。<a href=\"https://github.com/reasonml/reason-cli#1-choose-your-platform\">检查安装文档</a>。</li>\n<li>在撰写本文时,macOS用户可以通过运行 <code>npm i -g reason-cli@3.1.0-darwin</code>。</li>\n<li>BuckleScript: <code>npm i -g bs-platform</code>。</li>\n</ul>\n<p>我也使用 <a href=\"https://github.com/reasonml-editor/vscode-reasonml\">vscode-reasonml</a> 编辑器插件。如果您使用的是其他编辑器,请检查<a href=\"https://reasonml.github.io/docs/en/editor-plugins.html\">插件列表</a>以找到适合您的插件。</p>\n<h2>我们的第一个组件</h2>\n<p>要开始,我们将为我们的应用程序创建样板代码:</p>\n<p><code>create-react-app weather-app --scripts-version reason-scripts</code></p>\n<p>这给了我们一个基本的 App 组件:</p>\n<pre><code class=\"language-ocaml\">[%bs.raw {|require('./app.css')|}];\n\n[@bs.module] external logo : string = "./logo.svg";\n\nlet component = ReasonReact.statelessComponent("App");\n\nlet make = (~message, _children) => {\n ...component,\n render: (_self) =>\n <div className="App">\n <div className="App-header">\n <img src=logo className="App-logo" alt="logo" />\n <h2> (ReasonReact.stringToElement(message)) </h2>\n </div>\n <p className="App-intro">\n (ReasonReact.stringToElement("To get started, edit"))\n <code> (ReasonReact.stringToElement(" src/app.re ")) </code>\n (ReasonReact.stringToElement("and save to reload."))\n </p>\n </div>\n};\n</code></pre>\n<p>我们可以使用 <code>yarn start</code> 开始编译和运行。我们来看看一些有趣的部分......</p>\n<pre><code class=\"language-ocaml\">[%bs.raw {|require('./app.css')|}];\n</code></pre>\n<p>BuckleScript 允许我们将原始的 JavaScript 代码混合到我们的代码中,从一个一行代码到一个完整的库(如果我们只是在 hacking)。这应该很少使用,但是在我们开始的时候可以快速开始。</p>\n<pre><code class=\"language-ocaml\">let component = ReasonReact.statelessComponent("App");\n</code></pre>\n<p>我们将使用两种类型的ReasonReact组件:<code>statelessComponent</code> 和 <code>reducerComponent</code>。无状态组件按照他们在锡上所说的话做。 Reducer组件是有状态的,并且内置了类似 Redux 的 reducers。 我们稍后再讨论。</p>\n<pre><code class=\"language-ocaml\">let make = (~message, _children) => { ... }\n</code></pre>\n<p>这是定义我们组件的方法。 这两个参数具有不同的符号:<code>〜</code> 是一个带标签的参数,意味着我们可以通过名称引用参数,而 <code>_</code> 是一种更明确的方式显示参数未被使用(否则编译器会给我们一个警告)。</p>\n<p><code>...component</code> 扩展运算符意味着我们的 make 函数正在构建我们刚定义的组件,覆盖默认值。</p>\n<pre><code class=\"language-ocaml\"><h2> (ReasonReact.stringToElement(message)) </h2>\n</code></pre>\n<p>JSX 中的 Reason 比正常的 React 更严格。我们不能仅仅编写 <code><h2> {message} </h2></code>,而是必须将 <code>message</code> 字符串显式转换为 JSX 元素。</p>\n<p>稍后我们将构建自己的组件时,我们将使用此样板。</p>\n<h2>Reason 中的类型</h2>\n<p>我们创建一个新文件 <code>WeatherData.re</code>。这将为我们的天气记录定义数据结构和任何相关方法。首先,我们来创建一个类型:</p>\n<pre><code class=\"language-ocaml\">type weather = {\n summary: string,\n temp: float\n};\n</code></pre>\n<p>在这个文件中,我们可以使用这个数据结构创建新记录,编译器会知道它是一个 Weather 项目。从其他文件中,我们需要告诉编译器该类型是什么。在 Reason 中,<a href=\"https://reasonml.github.io/docs/en/faq.html#i-don-t-see-any-import-or-require-in-my-file-how-does-module-resolution-work\">文件可以作为模块引用</a>,这意味着我们不必显式导入它们!我们可以这样做:</p>\n<pre><code class=\"language-ocaml\">let today: WeatherData.weather = {\n summary: "Warm throughout the day",\n temp: 30.5\n};\n</code></pre>\n<p>我之前提到 Reason 有 100% 的类型覆盖率,但我们只定义了我们的 Weather 类型......其余覆盖范围从哪里来?我们可以明确地为每个我们使用的变量定义一个类型,例如:<code>let greeting: string = "Hello"</code>;但幸运的是 OCaml 系统可以为我们推断类型。所以,如果我们写 <code>let greeting = "Hello"</code> ;编译器仍然会知道 greeting 是一个字符串。这是 Reason 中的一个关键概念,可确保类型安全。</p>\n<h2>保持状态</h2>\n<p>回到我们的项目,让我们修改app.re,以便它可以存储我们想要显示的数据。这将涉及:</p>\n<ul>\n<li>定义我们的状态类型</li>\n<li>设置我们的初始状态(目前有一些虚拟数据)</li>\n<li>定义可应用于状态的操作(<code>actions</code>)</li>\n<li>定义组件的 <code>reducers</code> 来处理这些事件</li>\n</ul>\n<p><code>Actions</code> 定义了我们可以对操作状态做不同的事情。例如,<code>Add</code> 或 <code>Subtract</code>。 Reducers 是纯粹的函数,它定义了这些动作如何影响状态,就像在 Redux 中一样。他们采取 <code>action</code> 和我们以前的状态作为参数,并返回一个<a href=\"https://reasonml.github.io/reason-react/docs/en/state-actions-reducer.html#state-update-through-reducer\">更新类型</a>。</p>\n<pre><code class=\"language-ocaml\">type state = {\n weather: WeatherData.weather\n};\n\ntype action = \n | WeatherLoaded(WeatherData.weather);\n\nlet component = ReasonReact.reducerComponent("App");\n\nlet dummyWeather: WeatherData.weather = {\n summary: "Warm throughout the day",\n temp: 30.5\n};\n\nlet make = (_children) => {\n ...component,\n\n initaState: () => {\n weather: dummyWeather\n },\n\n reducer: (action, _prevState) => {\n switch action {\n | WeatherLoaded(newWeather) =>\n ReasonReact.Update({\n weather: newWeather\n })\n }\n },\n\n render: (self) =>\n <div className="App">\n <p> (ReasonReact.stringToElement(self.state.weather.summary)) </p>\n </div>\n};\n\n</code></pre>\n<p>这里有两个新的 Reason 概念:变体和模式匹配。</p>\n<pre><code class=\"language-ocaml\">type action = \n | WeatherLoaded(WeatherData.weather);\n</code></pre>\n<p>这是一个变体:代表不同值的选择的数据结构(像枚举)。变体中的每个案例都必须大写,并且可以选择接收参数。在 ReasonReact 中,<code>action</code> 表示为变体。这些可以与 switch 表达式一起使用:</p>\n<pre><code class=\"language-ocaml\">switch action {\n | WeatherLoaded(newWeather) =>\n ReasonReact.Update({...})\n}\n</code></pre>\n<p>这是 Reason 中最有用的功能之一。这里我们是基于我们在 <code>reducer()</code> 方法中接收到的参数的模式匹配 <code>action</code>。如果我们忘记处理一个案例,编译器知道,并会告诉我们!</p>\n<p><img src=\"/images/sfutmltmhu1fmzzjs9zs.png\" alt=\"错误提示例子\" />\nReason 编译器捕获未处理的案例。</p>\n<p>在前面的例子中,我们使用解构来访问 newWeather 的值。我们也可以使用它来根据它们包含的值匹配 <code>actions</code>。这给了我们一些<a href=\"https://reasonml.github.io/docs/en/pattern-matching.html\">非常强大的行为</a>!</p>\n<h2>获取数据</h2>\n<p>到目前为止,我们的应用呈现虚拟天气数据 - 现在让我们从 API 中加载它。我们将把获取和解析数据的方法放在我们现有的 WeatherData.re 文件中。</p>\n<p>首先,我们需要安装 <a href=\"https://github.com/reasonml-community/bs-fetch\">bs-fetch</a> :<code>npm i bs-fetch</code> 和 <a href=\"https://github.com/glennsl/bs-json\">bs-json</a>:<code>npm i @glennsl/bs-json</code>。我们还需要将它们添加到我们的 <code>bsconfig.json</code> 中:</p>\n<pre><code class=\"language-ocaml\">{\n ...\n "bs-dependencies": [\n "bs-fetch",\n "@glennsl/bs-json"\n ]\n}\n</code></pre>\n<p>我们将使用 <a href=\"https://developer.yahoo.com/weather\">Yahoo Weather API</a> 来获取我们的数据。我们的 <code>getWeather()</code> 方法将调用 API,然后使用<code>parseWeatherResultsJson()</code> 解析结果,然后解析天气项目:</p>\n<pre><code class=\"language-ocaml\">type weather = {\n summary: string,\n temp: float\n};\n\nlet url = "https://query.yahooapis.com/v1/public/yql?q=select%20item.condition%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22london%22)%20AND%20u%3D%22c%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys";\n\nlet parseWeatherJson = json: weather => \n Json.Decode.{\n summary: field("text", string, json),\n temp: float_of_string(field("temp", string, json))\n };\n\nlet parseWeatherResultsJson = json =>\n Json.parseOrRaise(json) |> Json.Decode.(at([\n "query",\n "results",\n "channel",\n "item",\n "condition"\n ], parseWeatherJson));\n\nlet getWeather = () =>\n Js.Promise.(\n Bs_fetch.fetch(url)\n |> then_(Bs_fetch.Response.text)\n |> then_(\n jsonText => {\n let result = parseWeatherResultsJson(jsonText);\n resolve(result);\n }\n )\n );\n\n</code></pre>\n<pre><code class=\"language-ocaml\">Josn.parseOrRaise(json) |> Json.Decode.(at([\n ...\n], parseWeatherJson));\n</code></pre>\n<p>这会在通过指定字段遍历数据之前解析 JSON 字符串响应。然后它使用 parseWeatherJson() 方法来解析在条件字段中找到的数据。</p>\n<pre><code class=\"language-ocaml\">Json.Decode.{\n summary: field("text", string, json),\n temp: float_of_string(field("temp", string, json))\n};\n</code></pre>\n<p>在这个片段中,字段和字符串是 <code>Json.Decode</code> 的属性。这个新的语法“打开” <code>Json.Decode</code>,所以它的属性可以在大括号内自由使用(而不是重复<code>Json.Decode.foo</code>)。该代码生成一个 <code>weather</code> 项目,使用 <code>text</code> 和 <code>temp</code> 字段分配 <code>summary</code> 和 <code>temp</code>。</p>\n<p><code>float_of_string</code> 完全符合你的期望:它将字符串中的温度(从API获得)转换为浮点数。</p>\n<h2>更新状态</h2>\n<p>现在我们有一个返回 <code>promise</code> 的 <code>getWeather()</code> 方法,我们需要在 App 组件加载时调用它。ReasonReact 对 React.js 有一组类似的生命周期方法,但有一些<a href=\"https://reasonml.github.io/reason-react/docs/en/lifecycles.html\">细微差别</a>。我们将使用 <code>didMount</code> 生命周期方法使 API 调用获取天气。</p>\n<p>首先,我们需要改变我们的状态,以表明可能没有状态的天气项目 - 我们将摆脱虚拟数据。 <code>option()</code> 是 Reason 中的一个内置变体,它描述了一个“<code>nullable</code>(可空)”值:</p>\n<pre><code class=\"language-ocaml\">type option('a) = None | Some('a);\n</code></pre>\n<p>我们需要在我们的状态类型和初始状态中指定 <code>None</code>,并在 <code>WeatherLoaded</code> reducer 中指定 <code>Some(weather)</code>:</p>\n<pre><code class=\"language-ocaml\">type state = {\n weather: option(WeatherData.weather)\n};\n\n// ...\n\nlet make = (_children) => {\n ...component,\n\n initialState: () => {\n weather: None\n },\n\n reducer: (action, _prevState) => {\n switch action {\n | WeatherLoaded(newWeather) =>\n ReasonReact.Update({\n weather: Some(newWeather)\n })\n }\n },\n\n // ...\n};\n</code></pre>\n<p>现在,我们实际上可以在组件装入时发出 API 请求。查看下面的代码,<code>handleWeatherLoaded</code> 是一个将我们的 <code>WeatherLoaded</code> action 分派给 reducer 的方法。</p>\n<p>注意:重要的是从大多数组件生命周期中返回 <code>ReasonReact.NoUpdate</code>。reducer 将在下一次改变中处理所有状态变化。</p>\n<pre><code class=\"language-ocaml\">let make = (_children) => {\n // ...\n\n didMount: (self) => {\n let handleWeatherLoaded = weather => self.send(WeatherLoaded(weather));\n WeatherData.getWeather()\n |> Js.Promise.then_(\n weather => {\n handleWeatherLoaded(weather);\n Js.Promise.resolve();\n }\n )\n |> ignore;\n\n ReasonReact.NoUpdate;\n },\n\n // ...\n};\n</code></pre>\n<p>如果我们现在运行我们的应用程序,我们会遇到错误...我们当前正在尝试呈现关于 <code>self.state.weather</code> 的信息,但是直到我们收到来自 API 的响应时,它才设置为 None。让我们更新我们的应用程序组件,以便在等待时显示加载消息:</p>\n<pre><code class=\"language-ocaml\">let make = (_children) => {\n // ... \n\n render: (self) => \n <div className="App">\n <p>\n {\n switch self.state.weather {\n | None =>\n ReactReact.stringToElement("Loading weather...");\n | Some(weather) => \n ReactReact.stringToElement(weather.summary);\n }\n }\n </p>\n </div>\n};\n</code></pre>\n<p>结果...</p>\n<p><img src=\"/images/ezgif-3-cf07dc176b.gif\" alt=\"效果图\" /></p>\n<h2>错误处理</h2>\n<p>我们没有想过的一件事是如果我们无法加载数据会发生什么。如果API停机,或者它返回了我们预料不到的情况呢?我们需要认识到这一点并拒绝承诺:</p>\n<pre><code class=\"language-ocaml\">let getWeather = () =>\n Js.Promise.(\n Bs_fetch.fetch(url)\n |> then_(Bs_fetch.Response.text)\n |> then_(\n jsonText => {\n switch (parseWeatherResultsJson(jsonText)){\n | exception e => reject(e);\n | weather => resolve(weather);\n };\n }\n )\n );\n</code></pre>\n<pre><code class=\"language-ocaml\">switch (parseWeatherResultsJson(jsonText)) {\n | exception e => reject(e);\n | weather => resolve(weather);\n};\n</code></pre>\n<p>这个 switch 语句试图解析 API 响应。如果发生异常,它会拒绝承诺那个错误。如果解析成功,该承诺将与天气项目一起解决。</p>\n<p>接下来,我们将改变我们的状态,让我们认识到是否发生错误。我们来创建一个新的类型,它向我们之前的 <code>Some('a)</code> 或 <code>None</code> 添加一个 <code>Error</code> 案例。</p>\n<pre><code class=\"language-ocaml\">type optionOrError('a) = \n | Some('a)\n | None\n | Error;\n\ntype state = {\n weather: optionOrError(WeatherData.weather)\n};\n</code></pre>\n<p>在这样做的同时,我们还需要向渲染函数添加一个 <code>Error</code> 案例 - 我会让你自己添加。最后,我们需要创建一个新的 <code>action</code>和 <code>reducer</code>,以便在我们的 <code>getWeather()</code> 承诺被拒绝时使用。</p>\n<pre><code class=\"language-ocaml\">// ...\ntype action = \n | WeatherLoaded(WeatherData.weather)\n | WeatherError;\n\nlet make = (_children) => {\n ...component,\n\n didMount: (self) => {\n let handleWeatherLoaded = weather => self.send(WeatherLoaded(weather));\n let handleWeatherError = () => self.send(WeatherError);\n\n WeatherData.getWeather()\n |> Js.Promise.then_(\n // ...\n )\n |> Js.Promise.catch(\n _err => {\n handleWeatherError();\n Js.Promise.resolve();\n }\n )\n |> ignore;\n\n ReasonReact.NoUpdate;\n },\n\n reducer: (action, _prevState) => {\n switch action {\n | WeatherLoaded(newWeather) =>\n // ...\n | weatherError =>\n ReasonReact.Update({\n weather: Error\n })\n }\n },\n\n // ...\n}\n\n</code></pre>\n<p>这些是我们已经使用过的概念,但让用户知道是否出现了问题是很有用的。我们不想让他们挂着“加载”消息!</p>\n<p>我们有它,我们的第一个 ReasonReact 网络应用程序。干得不错!我们已经介绍了很多新的概念,但希望您已经看到了使用 Reason 的一些好处。</p>\n<p>如果您发现这个有趣的事情,并希望看到另一篇文章,请点击下面的反应让我知道! ❤️🦄🔖</p>\n<h2>扩展阅读</h2>\n<ul>\n<li><a href=\"https://jacklewin.com/2018/getting-started-with-reason\">更多的上下文</a>,包括源代码的链接。</li>\n<li><a href=\"http://reasonmlhub.com/exploring-reasonml\">探索ReasonML和函数式编程</a> - 一本关于(你猜对了)的免费在线书籍 Reason 和 FP。</li>\n</ul>\n<h2>OSS项目</h2>\n<ul>\n<li><a href=\"https://github.com/glennsl/bs-jest\">bs-jest</a> - Jest 的 BuckleScript 绑定。</li>\n<li><a href=\"https://github.com/kennetpostigo/lwt-node\">lwt-node</a> - Node.js API的 Reason 实现</li>\n<li><a href=\"https://github.com/apollographql/reason-apollo\">reason-apollo</a>绑定 Apollo 客户端和 React Apollo</li>\n</ul>\n<h2>其他</h2>\n<ul>\n<li><a href=\"https://discord.gg/reasonml\">Discord 频道</a></li>\n<li><a href=\"https://reasonml.chat/\">论坛</a></li>\n<li><a href=\"https://reason.town/\">Reason Town</a> - ReasonML 语言和社区的播客</li>\n<li><a href=\"https://redex.github.io/\">Redex</a> - Reason 包的索引</li>\n</ul>\n","author":"lanqy"},{"title":"ReasonML - React 为首要目标","created":"2018/05/28","link":"2018/05/28/reasonml-react-as-first-intended","description":"ReasonML - React 为首要目标","content":"<h1>ReasonML - React 为首要目标</h1>\n<p>译自:https://www.imaginarycloud.com/blog/reasonml-react-as-first-intended/</p>\n<p>ReasonML 是 <a href=\"https://www.facebook.com/\">Facebook</a> 用来开发 React 应用程序并将其作为 JavaScript 的未来版本推广的新技术(<a href=\"https://reasonml.github.io/docs/en/what-and-why.html\">他们说</a>ES2030)。在这篇文章中,我将简要介绍一下这项技术。</p>\n<h2>简而言之,ReasonML是:</h2>\n<ul>\n<li>一种编写 React 应用程序的新方法;</li>\n<li>OCaml语义的JavaScript友好语法;</li>\n<li>静态类型 - 带有类型推断;</li>\n<li>函数式,但不是纯粹的;</li>\n<li>主要编译为JavaScript;</li>\n<li>由 Facebook 和 Bloomberg。</li>\n</ul>\n<h2>以前的 React 与现在的 React 的不同</h2>\n<p>React 的编程风格比面向对象编程更接近函数式。因此,发现<a href=\"https://reasonml.github.io/docs/en/what-and-why.html\">第一个 React 原型没有在 JavaScript 中实现,而是在 Standard ML中实现</a>,这并不奇怪。</p>\n<p>但是,随着原型开始成熟,作者决定将其移植到 JavaScript 并从那里继续,因为没有成熟的 JavaScript 转换器,也因为当时世界还没有准备好接受这种非主流的编程语言和风格。</p>\n<p>因此,React作为与JavaScript编程语言相关的技术而广受欢迎。</p>\n<p>尽管在 <a href=\"https://www.imaginarycloud.com/blog/a-javascript-ecosystem-overview/\">JavaScript 生态系统</a>中取得了这些成功,但有些人开始觉得幕后还有其他相关项目正在发生 - 例如 Redux,<a href=\"https://www.imaginarycloud.com/blog/elm-javascript-reinvented-1-overview/\">Elm</a> 和 Purescript,开始受到欢迎,从而推动社区的思维更接近 React 最初函数式和静态类型的根源。</p>\n<p>这使得 Facebook 相信将 React 本身拉近根本是可行和方便的。如果他们没有那么多已经完成的工作,他们不会这么做。</p>\n<h4>Bucklescript</h4>\n<p>一些公司正在开发这样的任务关键用户界面, 使用动态或渐进式语言可能会造成无法承受的损失。</p>\n<p><a href=\"https://www.bloomberg.com/\">Bloomberg</a> 就是这样的公司之一,对 Bloomberg 而言,<a href=\"https://www.techatbloomberg.com/blog/release-1-7-story-behind-bucklescript/\">张宏波正在尝试 JavaScript 运行时</a>,他意识到将 OCaml 编译器移植到 JavaScript 并在浏览器上运行并不困难。</p>\n<pre><code class=\"language-ocaml\">\n(* Bucklescript / O'Caml 中的阶乘实现 *)\n\nlet rec factorial n = \n if n <= 0 then\n 1\n else\n n * fact(n - 1)\n\n</code></pre>\n<p>现实情况是,<em><strong>OCaml编译器已经非常模块化</strong></em>,并且用JavaScript生成的后端替换它的本地代码生成的后端并不是很难。有了这样的后端,甚至可以将OCaml编译器编译为JavaScript,从而***<a href=\"https://bucklescript.github.io/bucklescript-playground/#Quick_Sort\">自行托管Bucklescript编译器</a>***并在浏览器中运行它。</p>\n<p>Bucklescript 诞生并且更好,它由 Bloomberg 以开源软件的形式发布。</p>\n<pre><code class=\"language-ocaml\">(* Bucklescript / O'Caml 中的 FizzBuzz 实现 *)\n\nlet fizzbuzz i = \n match i mod 3, i mod 5 with\n | 0, 0 -> "FizzBuzz"\n | 0, _ -> "Fizz"\n | _, 0 -> "Buzz"\n | _ -> string_of_int i\n\nlet _ = for i = 1 to 100 do\n print_endline(fizzbuzz i)\ndone\n\n</code></pre>\n<p>需要注意的是,原始的 OCaml 编译器已经由 <a href=\"https://en.wikipedia.org/wiki/French_Institute_for_Research_in_Computer_Science_and_Automation\">Institut National de Recherche en Informatique et Automatique(INRIA)</a>进行了数十年的开发和优化,并且它是用于如此严重的类型检查语言的最快编译器之一。</p>\n<h2>ReasonML</h2>\n<p>所以,<em><strong>如果 Facebook 打算让静态类型的 React 生态系统被打入,Bucklescript 肯定是一个很好的候选</strong></em>,因为他们似乎相信 JavaScript 以其流行的花括号语法对 React 的成功负有主要责任。</p>\n<pre><code class=\"language-ocaml\">\n// ReasonML 中的阶乘实现\n\nlet rec factorial = (x) => \n if(x <= 0) {\n 1;\n } else {\n x * factorial(x - 1);\n };\n\n</code></pre>\n<p>然而,他们并不足够简单地将 Bucklescript 与其 OCaml 语法相结合。他们相当保守 OCaml 的语义; Bucklescript 后端和尽可能多的 JavaScript 语法。</p>\n<p><em><strong>为了保持 JavaScript 语法,他们创建了一个额外的解析器</strong></em>,处理一种叫做 ReasonML 的新语言,它简直就是 OCaml,带有类似 JavaScript 的花括号语法。</p>\n<pre><code class=\"language-ocaml\">\n// ReasonML 中的 FizzBuzz 实现\n\nlet fizzbuzz = (i) =>\n switch ([i mod 3, i mod 5]) {\n | [0, 0] => "FizzBuzz"\n | [0, _] => "Fizz"\n | [_, 0] => "Buzz"\n | _ => string_of_int(i)\n };\n\n for(i in 1 to 100) {\n print_endline(fizzbuzz(i));\n };\n\n</code></pre>\n<p><em><strong>其结果与 JavaScript 非常相似</strong></em>,以至于某些 JavaScript 代码可以被编译器直接处理,就好像它是 ReasonML 一样,立即享受静态类型编译器带来的好处,而无需更改任何代码。</p>\n<pre><code class=\"language-ocaml\">\n// 有效的 ReasonML 和 Javascript 代码\n\nlet add = (a, b) => a + b;\nadd(4, 6);\n\n</code></pre>\n<h2>ReasonReact</h2>\n<p>除了语言和编译器本身的工作之外,Facebook 还致力于开发围绕其 React 框架的 ReasonML 包装以及一些附加功能。</p>\n<p>它被称为 <a href=\"https://reasonml.github.io/reason-react/\">Reason React</a> 并且已经开发,因此可以很<a href=\"https://reasonml.github.io/reason-react/docs/en/reason-using-js.html\">容易地将 JavaScript React 组件</a>与 Reason 组件在同一个 ReactJS 或 Reason 应用程序中混合使用。</p>\n<p>应该注意的是,[Reason React] 不仅仅是 React 的包装,还提供了外部函数库,如 <a href=\"https://redux.js.org/introduction\">Redux</a> 和 <a href=\"https://facebook.github.io/immutable-js/\">Immutable</a>。</p>\n<h2>Redux 有什么用?</h2>\n<p>*** Redux 是一名在 React 项目中非常流行的状态管理器***。 简而言之,它允许将应用程序域逻辑组织为一组组合的 reducer 函数,这些函数旨在表示应用程序的状态应该如何转换为外部事件(如用户交互)。</p>\n<p><em><strong>使用 ReasonML 时,我们不再需要 Redux。</strong></em> ReasonReact 无状态组件已经提供了 reducer 中构建的概念,旨在解决 Redux 用于解决的问题。</p>\n<pre><code class=\"language-ocaml\">/* \n * ReasonReact 中的简单递增计数器 \n * 尝试:http://bit.ly/counter-button-sample \n*/\n\ntype state = {count: int};\n\ntype action = \n | Click;\n\nlet component = ReasonReact.reducerComponent("Counter");\n\nmodule Counter = {\n let make = _children => {\n ...component,\n\n initialState: () => {count: 0},\n\n reducer: (action, state) => \n switch (action) {\n | Click => ReasonReact.Update({count: state.count + 1})\n },\n \n render: self => {\n let message = \n "Clicked"\n ++ string_of_int(self.state.count)\n ++ " times(s)";\n <div>\n <button onClick=(_event => self.send(Click))>\n (ReasonReact.string(message))\n </button>\n </div>;\n },\n };\n};\n\n\n</code></pre>\n<h2>如何不可变?</h2>\n<p>以前由 Immutable 提供的功能是在语言级别实现的。 ReasonML(和 OCaml )操作默认是不可变的,因此避免了使用外部库的认知和性能开销。</p>\n<h2>如何将 Reason 与 elm 进行比较?</h2>\n<p>前一段时间,我写了一系列关于 <a href=\"https://www.imaginarycloud.com/blog/elm-javascript-reinvented-1-overview/\">elm 语言 的文章</a>,他们彼此没有什么不同。</p>\n<p>分析它们之间的深度差异超出了本文的预期范围,但总而言之,<em><strong>它们源于对函数纯度的不同立场以及两个项目的不同成熟度水平</strong></em>。</p>\n<p>下面你可以找到他们的特征如何匹配的表格摘要:</p>\n<p>共同的特征:</p>\n<ul>\n<li>函数式编程;</li>\n<li>编译成 JavaScript ;</li>\n<li>安全;</li>\n<li>短反馈回路;</li>\n<li>易于测试和反应堆;</li>\n<li>全覆盖,推断的静态类型。</li>\n</ul>\n<p>差异:</p>\n<p>特性 | Reason | Elm\n------------ | ------------- | -------------\n函数纯度 | 不纯粹 | 纯粹\n语法 | 基于Javascript的 - 可选 ML 为基础 | 更简洁;基于 ML 的语法\nJS 交互 | 更简单 - 不太安全 | 安全 - 更多的模板\n测试难易度 | 由于最终缺乏函数纯度,因此可能有些代码用 Reason 可能更难以测试 | 由于其函数纯度,始终易于测试\nReact 兼容 | 是 | 否\n处理JS的副作用 | 通过编写命令式代码很容易处理副作用 | 有时很难优雅地处理\n多态性 | 参数化和 OO 式 Ad-hoc | 参数型和行型\n编译速度 | 非常快 | 较慢\n目标平台 | Javscript、OCaml Bytecode; Native code(AMD; INTEl ; ARM & PowerPC) | Javascript</p>\n<p>行业 / 学术支持 | Facebook;Blooberg; INRIA | Evan Czaplicki(作者);Prezi NoRedInk</p>\n<h2>编译为本地代码</h2>\n<p>正如您在上面的表格中注意到的那样,它提到了 ReasonML 可以编译为本地代码。这可以通过将 ReasonML语法层与剩余的原始 OCaml 编译器(包括原始本机代码后端)一起使用来完成。</p>\n<p><em><strong>这里有很多潜力</strong></em>,最终允许在后端和前端之间共享原因代码,并将后端编译为本机代码。</p>\n<h2>现实世界的 Reason</h2>\n<p>ReasonML 的旗舰应用程序是 Facebook Messenger,最初是一个使用 ReactJS 的应用程序,已逐步迁移到 ReasonML。此外,Reason 文档页面中列出了大量<a href=\"https://reasonml.github.io/en/users-of-reason.html\">其他项目和公司</a>。</p>\n<h2>结论</h2>\n<p><em><strong>ReasonML似乎是对 Elm 框架所探讨的相同概念的一种新尝试</strong></em>。即便如此,从营销和技术观点来看,这个项目及其支持者所采取的选择似乎更有希望。</p>\n<p>虽然 Elm 看起来是一个建立在创新理念基础上的美丽原型,但 <em><strong>ReasonML 似乎是企业级的实现</strong></em>,恰到好处地站在巨人的肩膀上,并吸引主流的品味。</p>\n","author":"lanqy"},{"title":"BuckleScript 绑定技巧","created":"2018/05/25","link":"2018/05/25/bucklescript-binding-tips","description":"BuckleScript 绑定技巧","content":"<h1>BuckleScript 绑定技巧</h1>\n<p>译自: https://jwheatley.co/bucklescript-binding-tips/</p>\n<blockquote>\n<p>这是一组适用于当前或未来项目的简单绑定提示,减少了对试验 / 错误的需求,并减少了深度文档搜索解决与下面示例相关的问题。</p>\n</blockquote>\n<h2>保持 React.js 中的 js</h2>\n<pre><code class=\"language-ocaml\">module Container = {\n [@bs.module "./styled/Container"]\n external js : ReasonReact.reactClass = "default";\n let make = children =>\n ReasonReact.wrapJsForReason(\n ~reactClass=js,\n ~props=Js.Obj.empty(),\n children\n );\n};\n</code></pre>\n<p>我将外部 js 命名为如果我需要将 React 组件的规范传递到 Reason 中使用的基于 JS 的库,我可以使用 <code><ComponentName>.js</code> 来访问它。</p>\n<p>例如:</p>\n<pre><code class=\"language-ocaml\">open Bindings.ReactRouter;\nlet component = ReasonReact.statelessComponent("App");\n\nlet make = (_children) => {\n ...component,\n render: (_self) =>\n <Router>\n <div>\n <Header />\n </div>\n <Switch>\n <Route exact=true path="/" component=Home.js />\n <Route component=NoMatch.js />\n </Switch>\n <Footer />\n </div>\n </Router>\n}\n</code></pre>\n<h2>属性</h2>\n<p>这是 Khoa Nguyen 关于使用 BuckleScript 对象“特殊创建函数”绑定 ReasonReact 属性的“<a href=\"https://khoanguyen.me/writing-reason-react-bindings-the-right-way/\">正确方法</a>”。</p>\n<h2>使 ReasonReact 组件对 JS 更友好</h2>\n<pre><code class=\"language-ocaml\">let component = ReasonReact.statelessComponent("App");\n\nlet make = _children => {\n ...component,\n render: self => <div />\n};\n\nlet default = ReasonReact.wrapReasonForJs(~component, _jsProps => make([||]));\n</code></pre>\n<p>使用为 JS 封装的组件创建“默认”变量,您可以通过它的 BuckleScript 输出文件名简单地导入它,而不用担心命名的导出。例如: 用 <code>import App from './App.bs'</code>; 代替 <code>import { AppWrapped } from './App.bs'</code>;</p>\n<h2>非代码导入</h2>\n<pre><code class=\"language-ocaml\">type assetT;\n[@bs.module] external twitterSvg : assetT = "../assets/svg/twitter.svg";\n</code></pre>\n<p>我个人不会假设在 JS 端会输出什么类型(int,string 等)。我只是给它一个泛型类型,让 Webpack 或 Metro Bundler 处理它,但它通常会。</p>\n<h2>全局</h2>\n<pre><code class=\"language-ocaml\">[@bs.val] external setInterval : (unit => unit, int) => int = "setInterval";\n[@bs.val] external clearInterval : int => unit = "clearInterval";\n</code></pre>\n<p>要与 BuckleScript 绑定的全局 JS 变量是 <code>bs.val</code>,而不是 <code>bs.module</code>。</p>\n<h2>函数重载</h2>\n<pre><code class=\"language-ocaml\">module Date = {\n type t;\n [@bs.new] external fromValue: float => t = "Date";\n [@bs.new] external fromString: string => t = "Date";\n};\n\nlet date1 = Date.fromValue(107849354.);\nlet date2 = Date.fromString("1995-12-17T03:24:00");\n</code></pre>\n<p>如果你绑定的 JS 函数采用不同类型的参数,你可以使用不同类型的不同名称进行多次绑定,以表示你将传递给它的东西。</p>\n<pre><code class=\"language-ocaml\">module Date = {\n type t;\n [@bs.new] external make : ([@bs.unwrap] [ | `Value(float) | `String(string)]) => t = "Date";\n};\n\nlet date1 = Date.make(`Value(107849354.));\nlet date2 = Date.make(`String("1995-12-17T03:24:00"));\n</code></pre>\n<p>你也可以使用 <code>bs.unwrap</code> 来“重载”你的函数类型。</p>\n","author":"lanqy"},{"title":"傻瓜式类型安全绑定从 JS 到 Reason","created":"2018/05/25","link":"2018/05/25/externals-js-ffi-reason","description":"傻瓜式类型安全绑定从 JS 到 Reason","content":"<h1>傻瓜式类型安全绑定从 JS 到 Reason。</h1>\n<p>译自: http://blog.klipse.tech/reason/2017/10/17/externals-js-ffi-reason.html</p>\n<p>有时,我们需要能够从我们的 Reason 代码中访问 JS 代码。这是所谓的 Javascript 互操作性的一部分。</p>\n<p>更具体地说,这叫做 FFI (外部函数接口)。FFI 是一种机制,用一种编程语言编写的程序可以调用例程或使用另一种编程语言编写的服务。</p>\n<p>在 Reason 的背景下,为了访问 JS 代码,我们必须从 JS 到 Reason 构建类型安全的绑定。为此,我们必须编写类型声明。</p>\n<p>Reason 的 FFI 非常强大,但这种能力是有代价的。了解它的工作原理有点复杂。</p>\n<p>本文的目的是以简单的方式公开 Reason 的 FFI 的主要功能,以帮助您克服语法的困难。</p>\n<p><img src=\"/images/burn-out.jpg\" alt=\"烧脑\" /></p>\n<h2>绑定到简单的 JS 函数值</h2>\n<p>我们来看看一些例子:</p>\n<p>以下是我们如何使 <code>Math.sqrt</code> 函数可以访问我们的 Reason 代码:</p>\n<pre><code class=\"language-ocaml\">[@bs.val] external sqrt : float => float = "Math.sqrt";\nlet a = sqrt (2.0);\n\n/* 1.4142135623730951 */\n</code></pre>\n<p>当指定的名称与原始名称完全相同时,我们可以将名称留空:</p>\n<pre><code class=\"language-ocaml\">[@bs.val] external encodeURIComponent : string => string = "";\nlet a = encodeURIComponent("Hello World\\n");\n\n/* Hello%20World%0A */\n</code></pre>\n<h2>绑定到 JavaScript 构造函数:bs.new</h2>\n<p><code>bs.new</code> 用于创建 JavaScript 对象。</p>\n<pre><code class=\"language-ocaml\">type date;\n[@bs.new] external create_date : unit => date = "Date";\nlet date = create_date ();\n/* Fri May 25 2018 14:32:19 GMT+0800 (中国标准时间) */\n</code></pre>\n<h2>绑定到方法:bs.send 和 bs.send.pipe</h2>\n<p><code>bs.send</code> 允许我们调用一个 JS 对象的方法。例如,这是我们如何绑定 <code>dom.getElementById</code></p>\n<p><code>dom</code> 是 <code>DOM</code> 的抽象类型,<code>element</code> 是元素的抽象类型</p>\n<pre><code class=\"language-ocaml\">type dom;\n[@bs.val] external dom : dom = "document";\ntype element;\n[@bs.send] external get_by_id : dom => string => element = "getElementById";\n\nlet a = get_by_id(dom, "klipse");\n</code></pre>\n<p>输出</p>\n<pre><code class=\"language-javascript\">// Generated by BUCKLESCRIPT VERSION 1.9.2, PLEASE EDIT WITH CARE\n'use strict';\n\nvar a = document.getElementById("klipse");\n\nexports.a = a;\n/* a Not a pure module */\n</code></pre>\n<p><code>bs.send.pipe</code> 与 <code>bs.send</code> 相似,除了第一个参数,即对象,放在最后一个参数的位置,以帮助用户以链式编写:</p>\n<pre><code class=\"language-ocaml\">[@bs.send.pipe : array('a)] external map : ([@bs] ('a => 'b)) => array('b) = "";\n\nlet test (arr) =\n arr\n |> map ([@bs] (fun (x) => x + 1))\n |> map ([@bs] (fun (x) => x * 4));\n\nlet a = test([|1,2,3|]);\n\n/* 8,12,16 */\n</code></pre>\n<p>如果您对 Ocaml / Reason 管道运算符还不熟悉,则这里是经过转换的 js 代码:</p>\n<pre><code class=\"language-ocaml\">[@bs.send.pipe : array('a)] external map : ([@bs] ('a => 'b)) => array('b) = "";\n\nlet test (arr) =\n arr\n |> map ([@bs] (fun (x) => x + 1))\n |> map ([@bs] (fun (x) => x * 4));\n</code></pre>\n<p>转换成:</p>\n<pre><code class=\"language-javascript\">// Generated by BUCKLESCRIPT VERSION 1.9.2, PLEASE EDIT WITH CARE\n'use strict';\n\n\nfunction test(arr) {\nreturn arr.map((function (x) {\nreturn x + 1 | 0;\n})).map((function (x) {\nreturn (x << 2);\n}));\n}\n\nexports.test = test;\n/* No side effect */\n</code></pre>\n<blockquote>\n<p>如果您对回调中的[@bs]属性感兴趣,请参阅<a href=\"https://bucklescript.github.io/bucklescript/Manual.html#_binding_to_callbacks_high_order_function\">绑定到回调函数(高阶函数)</a>。</p>\n</blockquote>\n<p>绑定到动态密钥访问/设置:<code>bs.set_index</code> 和 <code>bs.get_index</code></p>\n<p>以下是我们如何对 JavaScript 属性进行动态访问:</p>\n<pre><code class=\"language-ocaml\">type js_array;\n[@bs.new] external create : int => js_array = "Int32Array";\n[@bs.get_index] external get : js_array => int => int = "";\n[@bs.set_index] external set : js_array => int => int => unit = "";\n\nlet i32arr = create(3);\nset(i32arr, 0, 42);\nlet a = get(i32arr, 0);\n\n/* 42 */\n</code></pre>\n<h2>绑定到 Getter / Setter:bs.get,bs.set。</h2>\n<p>该属性有助于获取和设置 JavaScript 对象的属性。</p>\n<p>让我们在 javascript klipse 片段中定义一个 javascript 对象:</p>\n<pre><code class=\"language-javascript\">var bob = {"name": "Bob", "age": 32};\n</code></pre>\n<p>现在,下面是我们如何创建 setter 和 getter 到这个 javascript 对象(谢谢 @yawaramin,来自 Reason Discord!):</p>\n<pre><code class=\"language-ocaml\">type person;\n[@bs.val] external bob : person = "";\n[@bs.get] external get_age : person => int = "age";\n[@bs.set] external set_age : (person, int) => unit = "age";\n\nlet () = {\n let bobAge = get_age(bob);\n set_age(bob, bobAge + 1)\n}\n\n/* 33 */\n</code></pre>\n<p>只是为了好玩,看一下如何简单和干净的转换的js代码:</p>\n<pre><code class=\"language-ocaml\">type person;\n[@bs.val] external bob : person = "";\n[@bs.get] external get_age : person => int = "age";\n[@bs.set] external set_age : (person, int) => unit = "age";\n\nlet () = {\n let bobAge = get_age(bob);\n set_age(bob, bobAge + 1)\n};\n</code></pre>\n<p>转成:</p>\n<pre><code class=\"language-javascript\">// Generated by BUCKLESCRIPT VERSION 1.9.2, PLEASE EDIT WITH CARE\n'use strict';\n\n\nvar bobAge = bob.age;\n\nbob.age = bobAge + 1 | 0;\n\n/* bobAge Not a pure module */\n</code></pre>\n<h2>拼接调用约定:bs.splice</h2>\n<p>在 JS 中,有一个函数采用可变参数是很常见的。 BuckleScript 支持类型同质可变参数。我们传递一个数组,而不是传递可变数量的参数:</p>\n<pre><code class=\"language-ocaml\">[@bs.val] [@bs.splice] external max : array(int) => int = "Math.max";\n\nmax([|10, 12, 99|]);\n\n/* 99 */\n</code></pre>\n<h2>绑定到来自模块的值:bs.module</h2>\n<p>我们可以绑定到 js 模块的值:</p>\n<pre><code class=\"language-ocaml\">[@bs.module "x"] external add : (int, int) => int = "add";\n\nlet f = add(3, 4);\n</code></pre>\n<p>转成:</p>\n<pre><code class=\"language-javascript\">// Generated by BUCKLESCRIPT VERSION 1.9.2, PLEASE EDIT WITH CARE\n'use strict';\n\nvar X = require("x");\n\nvar f = X.add(3, 4);\n\nexports.f = f;\n/* f Not a pure module */\n</code></pre>\n<p>我们甚至可以提示编译器为模块生成一个更好的名称:</p>\n<pre><code class=\"language-ocaml\">[@bs.module ("x", "coolx")] external add : (int, int) => int = "add";\n\nlet f = add(3, 4);\n</code></pre>\n<p>转成:</p>\n<pre><code class=\"language-javascript\">// Generated by BUCKLESCRIPT VERSION 1.9.2, PLEASE EDIT WITH CARE\n'use strict';\n\nvar CoolX = require("x");\n\nvar f = CoolX.add(3, 4);\n\nexports.f = f;\n/* f Not a pure module */\n</code></pre>\n<p>Bucklescript FFI 还有许多其他高级功能。您可以阅读有关优秀的官方 <a href=\"https://bucklescript.github.io/bucklescript/Manual.html#_ffi\">BuckleScript手册</a>。</p>\n<p>注意到使用 FFI,在 <code>ReasonReact</code> 项目中访问 React.js 组件非常简单,因为它在<a href=\"https://reasonml.github.io/reason-react/docs/en/interop.html\">这里</a>已经解释了。</p>\n","author":"lanqy"},{"title":"Reason 中的多态性与普通变体","created":"2018/05/25","link":"2018/05/25/polymorphic-variants","description":"Reason 中的多态性与普通变体","content":"<h1>Reason 中的多态性与普通变体</h1>\n<p>译自: http://blog.klipse.tech/reason/2018/03/12/blog-reason-types.html</p>\n<h2>介绍</h2>\n<p>变体是 Reasonml 最酷的特性之一。</p>\n<p>在<a href=\"https://reasonml.github.io/docs/en/variant.html\">官方文档</a>中,他们提到了变体的限制(也称为普通变体):</p>\n<blockquote>\n<p>一个函数不能接受由两个不同变体共享的任意构造函数。</p>\n</blockquote>\n<p>他们还提到,使用多态变体可以克服这个限制。</p>\n<p>本文的目的是揭示普通变体的局限性,并看看多态性变体如何克服这一限制。我们希望我们带着狗和郁金香带来的例子会让这篇文章的阅读有些愉快。</p>\n<p><img src=\"/images/dog_tulip.jpg\" alt=\"狗和郁金香\" /></p>\n<h2>普通变体 - 简要回顾</h2>\n<p>假设你有一个 <code>animal</code> 变种</p>\n<pre><code class=\"language-ocaml\"># type animal =\n| Dog\n| Cat\n\ntype animal = Dog | Cat\n</code></pre>\n<p>并且你想写一个函数来将 <code>animal</code> 字符串化。</p>\n<pre><code class=\"language-ocaml\"># let string_of_animal = x =>\nswitch (x) {\n | Dog => "dog"\n | Cat => "cat"\n};\n\nval string_of_animal : animal => string = <fun>\n</code></pre>\n<p>现在,一只 <code>Dog</code> 是一只 “狗” ,一只 <code>Cat</code> 是一只“猫”:</p>\n<pre><code class=\"language-ocaml\"># "The " ++ string_of_animal(Dog) ++ " bites the " ++ string_of_animal(Cat);\n- : string = "The dog bites the cat"\n</code></pre>\n<p>到现在为止还挺好。</p>\n<p>现在让我们对鲜花做同样的事情:</p>\n<pre><code class=\"language-ocaml\"># type flower =\n| Rose\n| Tulip;\n\ntype flower = Rose | Tulip\n\n# let string_of_flower = x =>\nswitch (x) {\n |Rose => "rose"\n |Tulip => "tulip"\n};\n\nval string_of_flower : flower => string = <fun>\n\n# let a = "The " ++ string_of_flower(Rose) ++ " is more beautiful than the " ++ string_of_flower(Tulip);\n\nval a : string = "The rose is more beautiful than the tulip"\n</code></pre>\n<h2>变体的局限性</h2>\n<p>现在如果你尝试写一个函数使花和动物字符串化会发生什么?</p>\n<pre><code class=\"language-ocaml\"># let string_of_flower_or_animal = x =>\nswitch (x) {\n |Rose => "rose"\n |Tulip => "tulip"\n |Dog => "dog"\n |Cat => "cat"\n};\n\nFile "", line 5, characters 4-7:\nError: This variant pattern is expected to have type flower\nThe constructor Dog does not belong to type flower\n/* 文件“”,第5行,字符4-7:\n错误:这种变体模式预计会有花类型\n构造函数Dog不属于花的类型 */\n</code></pre>\n<p>构造函数 <code>Dog</code> 不属于 <code>flower</code> 类型,在这种情况下,ocaml 不会立即创建 <code>flower_or_animal</code> 类型!</p>\n<p>普通变体的另一个限制是,你不能在列表或数组中混合 <code>animal</code> 和 <code>flower</code> 类型元素:</p>\n<pre><code class=\"language-ocaml\"># let a = [Dog, Cat, Rose, Tulip];\n\nFile "", line 1, characters 19-23:\nError: This variant expression is expected to have type animal\nThe constructor Rose does not belong to type animal\n\n/* 文件“”,第1行,字符19-23:\n错误:预计此变体表达式具有 Dog 类型\n构造函数 Rose 不属于 Dog 类型 */\n</code></pre>\n<p>欢迎来到多态变体的世界!</p>\n<h2>多态变体</h2>\n<p>在语法上,多态变体通过反向撇号区别于普通变体:</p>\n<pre><code class=\"language-ocaml\"># let myDog = `Dog;\nval myDog : [> `Dog] = `Dog\n</code></pre>\n<p>请注意,与普通变体不同,多态变体可以在没有显式类型声明的情况下使用。他们的类型是自动推断的。</p>\n<p>当然,它也适用于参数化的变体:</p>\n<pre><code class=\"language-ocaml\"># let myNumber = `Int(4);\nval myNumber : [> `Int of int ] = `Int 4\n</code></pre>\n<p>现在,让我们看看如何使用多态类型来编写我们的 <code>string_of_flower_or_animal</code> 函数:</p>\n<pre><code class=\"language-ocaml\"># let string_of_flower_or_animal = x =>\nswitch (x) {\n |`Rose => "rose"\n |`Tulip => "tulip"\n |`Dog => "dog"\n |`Cat => "cat"\n};\n\nval string_of_flower_or_animal : [< `Cat | `Dog | `Rose | `Tulip ] => string = <fun>\n</code></pre>\n<p>请注意,系统已经自动推断函数参数的类型:它是[< `Cat | `Dog| `Rose | `Tulip ]。你可能想知道 <code><</code> 符号 的含义是什么。</p>\n<p>在回答这个问题之前,让我们看看多态变体如何让我们在列表中混合不同类型的元素:</p>\n<pre><code class=\"language-ocaml\"># let myNatrue = [`Dog, `Cat, `Rose, `Tulip];\nval myNatrue : [> `Cat | `Dog | `Rose | `Tulip] list = [`Dog; `Cat; `Rose; `Tulip]\n</code></pre>\n<p>现在,列表的类型是:<code>[> \\</code>Cat | `Dog | `Rose | `Tulip] list 。</p>\n<h2>上限和下限</h2>\n<p>现在是时候解释多态变体中 <code><</code> 和 <code>></code> 的含义了。</p>\n<p><code>></code> 在变体类型的开头标记类型 a 是<strong>开放的</strong>以便与其他变体类型组合。我们可以解读类型 [> `Cat | `Dog | `Rose | `Tulip ] 为描述一种变体,其标签包括 `Cat,`Dog,`Rose和 `Tulip,但也可能包含更多标签。</p>\n<p>换句话说,你可以大致翻译 <code>></code> 来表示:“这些标签或更多”。</p>\n<p>事实上,我们可以连接动物列表和花名单:</p>\n<pre><code class=\"language-ocaml\">let myAnimals = [`Dog, `Cat];\nlet myFlowers = [`Rose, `Tulip];\nlet myThings = List.concat([myAnimals, myFlowers]);\n\nval myAnimals : [> `Cat | `Dog] list = [`Dog; `Cat]\nval myFlowers : [> `Rose | `Tulip] list = [`Rose; `Tulip]\nval myThings : [> `Cat | `Dog | `Rose | `Tulip] list = [`Dog; `Cat; `Rose; `Tulip]\n</code></pre>\n<p><code><</code> 变体类型的开始部分表示 “这些标签或更少”。例如,在我们上面定义的 <code>string_of_flower_or_animal</code> 函数中,参数被推断为类型 [< `Cat | `Dog | `Rose| `Tulip]。</p>\n<p>事实上,这个函数没有办法处理具有除 `Cat,`Dog,`Rose 和 `Tulip 之外的标签的值。</p>\n<h2>结论</h2>\n<p>你现在可能会问自己为什么不总是使用多态变体。</p>\n<p>答案是多态变体的灵活性是有代价的。</p>\n<ul>\n<li>它们比普通变体更复杂</li>\n<li>他们不太可能捕捉那些普通变体的错误 - 正是由于它们允许的灵活性</li>\n<li>它们比普通变体重一点,性能较差</li>\n</ul>\n<p>请务必阅读 Real World Ocaml 的 <a href=\"https://realworldocaml.org/v1/en/html/variants.html#polymorphic-variants\">这一章</a>,深入了解普通和多态变体。在本章最后,他们详细解释了多态变体相对于普通变体的优点和缺点。</p>\n","author":"lanqy"},{"title":"Webpack 给初学者","created":"2018/05/25","link":"2018/05/25/webpack-for-beginners","description":"Webpack 给初学者","content":"<h1>Webpack 给初学者</h1>\n<h3>Webpack 是什么?</h3>\n<p>Webpack是一个模块打包和压缩工具</p>\n<h3>什么是模块打包?</h3>\n<p>模块打包就是把多个Javascript文件打包和压缩成一个单独的Javascript文件</p>\n<h3>怎样使用?</h3>\n<p>开始创建一个项目</p>\n<pre><code class=\"language-shell\">$ mkdir try-webpack && cd try-webpack && mkdir src && npm init -y\n$ touch index.html && touch src/main.js && touch webpack.config.js\n</code></pre>\n<p>如果你是Windows用户,可以通过<a href=\"https://git-scm.com/downloads\" target=\"_blank\">git bash</a>运行上面的命令。现在我们开始全局安装webpack:</p>\n<pre><code class=\"language-javascript\">$ npm install -g webpack\n</code></pre>\n<p>我们通过一个简单的配置文件开始我们所有的前端项目,打开<code>webpack.config.js</code>文件,复制下面的代码粘贴到<code>webpack.config.js</code>中</p>\n<pre><code class=\"language-javascript\">module.exports = {\n devtool: 'cheap-module-eval-source-map',\n entry: "./src/main.js",\n output: {\n path: __dirname + "/dist",\n filename: "bundle.js"\n }\n};\n</code></pre>\n<p>这个文件告诉webpack我们的应用入口点在哪里,以及在哪个目录输出打包后的代码,通过命令行运行<code>webpack</code>,在dist目录下就会生成bundle.js文件。到目前为止,我们到应用是相当无聊的,我是<a href=\"http://mithril.js.org/\" target=\"_blank\">Mithril.js</a>的忠实粉丝,因此我们通过<a href=\"http://mithril.js.org/\" target=\"_blank\">Mithril.js</a>来做一些东西,首先通过npm来安装这个库。</p>\n<pre><code class=\"language-javascript\">$ npm install --save mithril\n</code></pre>\n<p>现在打开<code>src/main.js</code>输入如下代码:</p>\n<pre><code class=\"language-javascript\">var m = require('mithril');\n\nvar app = {\n view: function() {\n return m('div', 'hello world!')\n }\n}\n\nm.mount(document.getElementById('app'), app)\n</code></pre>\n<p>打开index.html,输入如下代码(请记得引入打包后的bundle.js文件):</p>\n<pre><code class=\"language-html\"><!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <title>Webpack Tutorial</title>\n</head>\n<body>\n <div id="app"></div>\n <script src="dist/bundle.js"></script>\n</body>\n</html>\n</code></pre>\n<p>接着通过命令行运行<code>webpack</code>生成新的包(bundle.js文件),现在在浏览器中打开index.html,你将看到<code>hello world!</code> 。\n如果你做到来这一步,那么恭喜你!你已经理解基本的webpack了。<br/></p>\n<p>让我们来稍微重构一下我们的app,我们来本地化我们的应用程序,这个要求我们把所有文本放在一个集中的地方,创建一个新的文件<code>src/resources.json</code>,这个文件里包含如下的JSON:</p>\n<pre><code class=\"language-json\">{\n "en-US": {\n "HELLO_WORLD": "hello world!"\n }\n}\n</code></pre>\n<p>回到<code>src/main.js</code>文件,通过json文件加载我们的信息(修改该文件为):</p>\n<pre><code class=\"language-javascript\">var m = require('mithril');\nvar resources = require('./resources.json')\n\nvar app = {\n view: function() {\n return m('div', resources['en-US'].HELLO_WORLD)\n }\n}\n\nm.mount(document.getElementById('app'), app)\n</code></pre>\n<br/>\n如果你迫不及待的想在命令行运行`webpack`,你将会看到类似于如下的错误信息\n<pre><code class=\"language-javascript\">ERROR in ./src/main.js\nModule not found: Error: Cannot resolve 'file' or 'directory' ./resources.json in /../../try-webpack/src\n @ ./src/main.js 2:16-43\n</code></pre>\n<p>这个是loaders(加载器)报的错误,加载器是非常方便的工具,它可以使我们处理不同类型的内容。\n<br/>\n加载器可以用于转换ES2015成ES5,转换React JSX,编译TypeScript,加载Handlebars模版,加载和执行CSS,编译CSS预处理SASS,等等。有数以百计的加载器为不同的目的而诞生,现在我们只需要关心<code>json-loader</code>这个加载器。\n<br/>\n同样,我们通过npm来安装<code>json-loader</code>:</p>\n<pre><code class=\"language-shell\">$ npm install --save-dev json-loader\n</code></pre>\n<p>我们需要修改一下我们的<code>webpack.config.js</code>文件,引入我们的加载器,具体如下:</p>\n<pre><code class=\"language-javascript\">module.exports = {\n devtool: 'cheap-module-eval-source-map',\n entry: "./src/main.js",\n output: {\n path: __dirname + "/dist",\n filename: "bundle.js"\n },\n module: {\n loaders: [\n { test: /\\.json$/, loader: 'json-loader' }\n ]\n }\n};\n</code></pre>\n<p>再一次运行<code>webpack</code>,你的应用应该可以启动和运行了,如果你厌倦了每次输入WebPack,你也可以运行<code>webpack -w</code>来激活文件自动检测功能,这样当任何文件修改时自动重新打包。</p>\n<br/>\n让我们来做最后一件事,您可能已经注意到了生成包是相当大的,对于这样一个简单的应用程序来说,多出来的文件大小时由于`webpack.config.js`文件中`devtool:cheap-module-eval-source-map`这行造成的。这行为我们包生成源码地图(source maps),并映射到原始文件,方便查看源码和调试。当我们发布生产时,我们必须确保没有这行代码,怎样去掉这行取决于你自己,但是现在我们仅仅手工删除这一行代码。让我们也安装本地的webpack,这样我们才可以使用UglifyJsPlugin插件。\n<br/>\n安装本地webpack\n<pre><code class=\"language-shell\">$ npm install --save-dev webpack\n</code></pre>\n<p>当我们准备把项目发布生成时,我们需要压缩打包的代码,Webpack可以帮你完成,让我们最后一次修改<code>webpack.config.js</code>文件:</p>\n<pre><code class=\"language-javascript\">var webpack = require('webpack');\n\nmodule.exports = {\n entry: "./src/main.js",\n output: {\n path: __dirname + "/dist",\n filename: "bundle.js"\n },\n module: {\n loaders: [\n { test: /\\.json$/, loader: 'json-loader' }\n ]\n },\n plugins: [\n new webpack.optimize.UglifyJsPlugin({\n compress: {\n warnings: false\n }\n })\n ]\n};\n</code></pre>\n<p>再一次运行 <code>webpack</code> 生成发布生成的压缩文件(bundle.js),如果以上你漏掉来哪一步,请在github上查看<a href=\"https://github.com/rwhitmire/webpack-demo\">webpack-demo</a>,或者在Twitter上随时联系作者<a href=\"https://twitter.com/ry_js\">@ry_js</a>。</p>\n<p>注:本文翻译自 <a href=\"http://rwhitmire.com/2016/04/09/webpack-for-beginners.html\">Webpack for Beginners</a>,已经在Twitter上征得原作者的同意。另外为了方便理解,我加上来一些我自己的理解(不是直译),第一次尝试翻译,有不对的地方欢迎指正,谢谢!:)</p>\n","author":"lanqy"},{"title":"Reason 基础知识","created":"2018/05/23","link":"2018/05/23/reason-basics","description":"Reason 基础知识","content":"<h1>Reason 基础知识</h1>\n<p>译自: https://github.com/parkerziegler/reason-basics</p>\n<blockquote>\n<p>人们学习 Reason 编程语言的存储库。</p>\n</blockquote>\n<h2>目的</h2>\n<p>此存储库是 Reason 社区的新手通过简单示例了解该语言的基础知识的地方。你在这里看到的大部分内容都是从<a href=\"https://reasonml.github.io/docs/en/overview.html\">文档</a>中压缩或修改的。文档是一个很好的资源,并为语言背后的设计决策提供了高层次的解释。但是,对于初次使用程序员和首次使用 Reason 的用户来说,他们会感到有点压倒性。这个存储库希望能够简化一些语言,并提供非常清晰,简洁的例子来帮助你了解和使用 Reason。如果您发现任何令人困惑,过于复杂或错误的事情,请<a href=\"https://github.com/parkerziegler/reason-basics/issues\">提交问题</a>或<a href=\"https://github.com/parkerziegler/reason-basics/pulls\">创建拉取请求</a>。</p>\n<h2>安装</h2>\n<p>遵循相同的标准步骤克隆和本地安装此存储库:</p>\n<pre><code class=\"language-ocaml\">git clone https://github.com/parkerziegler/reason-basics.git\ncd reason-basics\nyarn install\n</code></pre>\n<h2>运行代码</h2>\n<p>该存储库的源文件当前位于 src/index.re 中。要将 Reason 代码编译为 BuckleScript,然后编译为 JS:</p>\n<pre><code class=\"language-ocaml\">yarn run build\n</code></pre>\n<p>编译该文件并在保存时监视更改:</p>\n<pre><code class=\"language-ocaml\">yarn run start\n</code></pre>\n<p>运行代码并查看终端中的输出:</p>\n<pre><code class=\"language-ocaml\">node src/index.bs.js\n</code></pre>\n<p>这期望您的计算机上安装了Node @ 6或更高版本。</p>\n<h2>编辑器</h2>\n<p>如果使用 vscode,则可以使用 <code>Cmd + Shift + B</code>(在 macOS 上)或 <code>Windows + Shift + B</code>(在 Windows 上)自动构建。</p>\n<h2>扩展</h2>\n<p>如果您使用 vscode,我建议安装 <a href=\"https://marketplace.visualstudio.com/items?itemName=freebroccolo.reasonml\">OCaml 和 Reason IDE</a> 以获得令人敬畏的 Reason 编码体验。</p>\n<h2>让我们学习 Reason</h2>\n<blockquote>\n<p>注意:这些内容与index.re中的内容相同,只是为了您的欣赏而写在 Markdown 中。</p>\n</blockquote>\n<p>请注意,此存储库使用 Reason 的 v3 语法。</p>\n<pre><code class=\"language-ocaml\">/* 这里是 Reason 中的注释. */\n</code></pre>\n<p>要在控制台打印,我们可以使用 BuckleScript 的 JS 模块。</p>\n<pre><code class=\"language-ocaml\">Js.log("Hello, BuckleScript and Reason! Luv BuckleScript");\n</code></pre>\n<p>我们也可以使用 Reason 的原生 print_* 模块。</p>\n<pre><code class=\"language-ocaml\">print_endline("Hello, BuckleScript and Reason! Luv Reason");\n</code></pre>\n<p>请注意,print_endline 只能打印字符串。要打印数据结构,请使用 Js.log 。未来在语言上对此的支持即将到来。</p>\n<p>要打印其他类型,您可以使用 Reason 的类型强制方法或 print_* 模块。</p>\n<pre><code class=\"language-ocaml\">print_endline(string_of_int(1));\nprint_endline(string_of_bool(true));\n</code></pre>\n<p>如果使用 print_* 方法,则需要使用 print_newline() 来获取新行。 print_endline() 为你添加新行。</p>\n<pre><code class=\"language-ocaml\">print_int(1);\nprint_newline();\nprint_float(1.0);\nprint_newline();\n</code></pre>\n<h3>变量</h3>\n<p>Reason 中的变量使用 let 定义。 没有 JS 中 const 的概念。</p>\n<pre><code class=\"language-ocaml\">let myVar = "myVar";\nlet myInt = 1;\n</code></pre>\n<h3>字符和字符串</h3>\n<p>Reason 区分字符( 用单引号 )和字符串( 用双引号 )。</p>\n<pre><code class=\"language-ocaml\">let x = 'x';\nlet y = 'y';\n</code></pre>\n<p>模式匹配字符的示例。</p>\n<pre><code class=\"language-ocaml\">let isXY = char: bool =>\n switch (char) {\n | 'x'\n | 'y'=> true\n | _ => false\n };\n</code></pre>\n<p>要将字符转换为字符串,请使用 String.make,将 1 作为第一个参数传递。</p>\n<pre><code class=\"language-ocaml\">let stringFromChar: string = String.make(1, x) ++ String.make(1, y);\nprint_endline(stringFromChar);\n</code></pre>\n<p>字符串使用 ++ 运算符连接。</p>\n<pre><code class=\"language-ocaml\">let greeting = "Hello";\nlet space = " ";\nlet name = "P-Doo";\nlet exclamation = "!";\n\nprint_endline(greeting ++ space ++ name ++ exclamation);\n</code></pre>\n<p>我们可以使用标准库中的 String 方法( Reason 的内置方法)对字符串进行操作。</p>\n<pre><code class=\"language-ocaml\">let whitespaceString = " _trim me_ ";\nlet trimmedString = String.trim(whitespaceString);\n\nprint_endline(trimedString);\n\nlet atString =\n String.map(\n c =>\n switch (c) {\n |' ' => '@'\n | _ => c\n },\n whitespaceString\n );\nprint_endline(atString);\n</code></pre>\n<p>字符串中的特殊字符需要使用 <code>\\</code> 转义。</p>\n<pre><code class=\"language-ocaml\">let slash = "\\\\";\nprint_endline(slash);\n</code></pre>\n<p>原因还支持多行字符串,类似于 JS 模板文字。他们使用 <code>{|</code> 和 <code>|}</code> 分隔。</p>\n<pre><code class=\"language-ocaml\">let multilineString = {|Hello\nReasonable\nFolks!|};\n\nprint_endline(multilineString);\n</code></pre>\n<p>字符串方法在多行字符串上工作相同。例如,要将多行字符串转换为单行字符串:</p>\n<pre><code class=\"language-ocaml\">let singlelineString =\n String.map(\n c =>\n swith (c) {\n | '\\n' => ' '\n | _ => c\n },\n multilineString\n );\n\nprint_endline(singlelineString);\n</code></pre>\n<p>要在多行字符串中插入变量,请用 <code>j| 和 </code>|j` 包围字符串。</p>\n<pre><code class=\"language-ocaml\">let style = "background-color: papayawhip";\nlet cssStyle = {j|$style|j};\n\nprint_endline(cssStyle);\n</code></pre>\n<p>要在 Reason 中的字符串中使用 unicode 字符,请使用 <code>js| |js</code> (译者注:或者 <code>j| |j</code>)。</p>\n<pre><code class=\"language-ocaml\">let unicodeString = {js|••∆∆••|js};\n\nprint_endline(unicodeString);\n</code></pre>\n<p>获取字符串长度和截取字符串:</p>\n<pre><code class=\"language-ocaml\">let background = "background-color: aquamarine";\nlet strLength: int = String.length(background);\n\nprint_endline(string_of_int(strLength));\n\nlet subStr: string =\n String.sub(background, 0, String.index(background, '-'));\n\nprint_endline(subStr);\n</code></pre>\n<h3>条件语句</h3>\n<p>条件语句的工作原理与 JS 类似。然而,如果没有 <code>else</code> 的 <code>if</code> 语句,则将其计算为 <code>else{()}</code>,即 <code>unit</code> 类型。</p>\n<pre><code class=\"language-ocaml\">let displayGreeting = true;\n\nif (displayGreeting) {\n let message = "Enjoying my ridiculous commentary yet?";\n print_endline(message);\n}\n</code></pre>\n<p>条件是对其正文内容进行评估的——不需要返回语句。</p>\n<pre><code class=\"language-ocaml\">let good = true;\nlet content =\n if (good){"This tweet is good."} else {\n "This tweet is bad. please make it better."\n };\n\nprint_endline(content);\n</code></pre>\n<p>在上面的块中,必须有一个 <code>else</code> 分支对字符串进行评估。如果没有,内容将被分配到 <code>unit</code> 类型,尽管它的目的是作为一个字符串。这会产生编译器错误。还有对三元运算符的支持。</p>\n<pre><code class=\"language-ocaml\">let retweet = good ? "Most certainly." : "Eh, don't think so.";\nprint_endline(retweet);\n</code></pre>\n<h3>作用域</h3>\n<p>变量默认为块作用域。让绑定可以使用 <code>{}</code> 创建匿名作用域。</p>\n<pre><code class=\"language-ocaml\">let anonymousScope = {\n let name = "Parker";\n let company = "Formidable";\n let place = "Seattle";\n print_endline({j|$name works at $company in $place.|j});\n}\n</code></pre>\n<p><code>nam</code> , <code>company</code> 和 <code>place</code> 在这里是无法访问的,因为它们是由上面创建的匿名范围持有的。试图在范围之外访问它们会导致错误!</p>\n<h3>类型!</h3>\n<p>Reason 由 OCaml 顶尖的系统支持。我们可以明确地输入变量,虽然这不是必需的。Reason 往往会为我们推断类型。</p>\n<pre><code class=\"language-ocaml\">let petalLength: int = 5;\n</code></pre>\n<p>我们也可以使用别名类型。</p>\n<pre><code class=\"language-ocaml\">type sepalLength = int;\n</code></pre>\n<p>然后使用它们!</p>\n<pre><code class=\"language-ocaml\">let sepalLength: sepalLength = 20;\n</code></pre>\n<p>我们通过注解参数来输入函数返回值。</p>\n<pre><code class=\"language-ocaml\">let flowerLength = (petal: int, sepal: sepalLength): int => petal + sepal;\nprint_endline(string_of_in(flowerLength(petalLength, sepalLength)));\n</code></pre>\n<h3>布尔</h3>\n<p>Reason 中的布尔比较类似于 JS 。 <code>===</code> 表示引用相等,而 <code>==</code> 表示结构相等。小心使用 <code>==</code>,Reason 会提醒你这个!</p>\n<pre><code class=\"language-ocaml\">let myTuple = ("Parkie", "is", 1);\nlet compareBool = tuple: bool => tuple === myTuple;\n\nprint_endline(string_of_bool(compareBool(myTuple)));\n</code></pre>\n<p>这条线会产生多态比较(可能不安全)。</p>\n<pre><code class=\"language-ocaml\">print_endline(string_of_bool(('M', 23) == ('M', 23)));\n</code></pre>\n<p>该行不会产生警告。</p>\n<pre><code class=\"language-ocaml\">print_endline(string_of_bool(('M', 23) === ('M', 23)));\n</code></pre>\n<h3>整数和浮点数</h3>\n<p>Reason 有整数和浮点数的概念,而不仅仅是 JS 数字。</p>\n<pre><code class=\"language-ocaml\">let githubStars: int = 9;\nlet squareInt = (num: int): int => num * num;\n\nprint_endline(string_of_int(squareInt(githubStars)));\n</code></pre>\n<p>要使用整数,请使用 Reason's Pervasives 模块中的方法。这些已经在范围内。</p>\n<pre><code class=\"language-ocaml\">let start: int = 1;\n+ start; /* 一元添加 */\n- start; /* 一元相减 */\nlet remainder = 20 mod 5;\n\nprint_endline(string_of_int(remainder));\n</code></pre>\n<p>浮点数具有唯一的操作数语法,附加 . 到 <code>+, -, *, /</code> 操作符上。</p>\n<pre><code class=\"language-ocaml\">let pi: float = 3.1415926;\nlet circleArea = (radius: float): float => pi *. radius *. radius;\n\nprint_endline(string_of_float(circleArea(20.0)));\n\nlet radius = sqrt(circleArea(20.0)) /. pi;\n\nprint_endline(string_of_float(radius));\n\n</code></pre>\n<h3>元组</h3>\n<p>元组在创建时是不可变的、有序的、有限的,并且是异构类型的(尽管它们可以是相同类型的)。Tuple 类型只是模仿它们所代表的元组的形状!</p>\n<pre><code class=\"language-ocaml\">let myTuple: (char, string, int, string) = ('A', "wonderful", 100, "tuple");\n</code></pre>\n<p>有一些特殊的方法可以获得长度为 2 的元组的元素元素。这些元素可以在 Pervasives 模块中使用。</p>\n<pre><code class=\"language-ocaml\">let twoTuple: (string, string) = ("It", "Me");\nlet first: string = fst(twoTuple);\nlet second: string = snd(twoTuple);\n\nprint_endline(first);\nprint_endline(second);\n</code></pre>\n<p>大多数元组元素都是使用解构来访问的。</p>\n<pre><code class=\"language-ocaml\">let (_, _, third: int, _) = myTuple;\nprint_endline(string_of_int(third));\n</code></pre>\n<p>元组对于模式匹配多个参数特别有用。</p>\n<pre><code class=\"language-ocaml\">let rotate = (x, y) =>\n switch (x, y) {\n | (180, (-180)) => print_endline({j|Rotate $x, $y|j})\n | (90, (-90)) => print_endline({j|Turn $x, $y|j})\n | (_, _) => print_endline("Hold steady!")\n };\n\nrotate(180, -180);\nrotate(90, -90);\nrotate(50, 70);\n</code></pre>\n<h3>记录</h3>\n<p>记录类似于 JS 对象,但更轻,默认情况下不可变,固定在字段名称和类型中,速度更快,键入更严格。记录类型是必需的 - 如果不写类型,编译器会报错!请务必使用 mutable 关键字预先设置可变属性。</p>\n<pre><code class=\"language-ocaml\">type team = {\n name: string,\n mutable rank: int,\n average: float,\n};\n\nlet redSox: team = {\n name: "Red Sox",\n rank: 1,\n average: 0.326\n};\n\n</code></pre>\n<p>记录的 <code>keys</code> 通过点符号访问。</p>\n<pre><code class=\"language-ocaml\">print_endline(redSox.name);\nprint_endline(string_of_float(redSox.average));\n</code></pre>\n<p>使用扩展操作符可以从旧记录中创建新的记录。但是,扩展不能添加新字段,因为记录受到类型限制。</p>\n<pre><code class=\"language-ocaml\">let redSoxUpdate = {...redSox, average: 0.418};\nprint_endline(string_of_float(redSoxUpdate.average));\n</code></pre>\n<p>现有的记录可以使用 <code>=</code> 更新可变字段。</p>\n<pre><code class=\"language-ocaml\">redSox.rank = redSox.rank + 1;\nprint_endline(string_of_int(redSox.rank));\n</code></pre>\n<p>在 Reason 中记录也有双关语,类似于ES6对象简写语法。如果值和键匹配,这允许您只提供关键名称。</p>\n<pre><code class=\"language-ocaml\">let capital = "Olympia";\nlet population = 6000000;\n\ntype state = {\n capital: string,\n population: int,\n};\n\nlet washington: state = {\n capital,\n population\n};\n</code></pre>\n<p>你也可以用类型双关!</p>\n<pre><code class=\"language-ocaml\">type place = {\n state,\n team,\n};\n\nlet seattle: place = {\n state: washington,\n team: {\n name: "Mariners",\n rank: 3,\n average: 0.298,\n },\n};\n</code></pre>\n<p>尽管记录与 JS 对象有些类似,但您必须更加认识它们的类型。将数据记录用于不改变形状的数据。记录被编译为具有数组索引访问权限的 JS 数组,使其快速。更改记录的类型还可以帮助标记需要更新数据结构的位置,从而使调试变得更加简单!</p>\n<p>如果你对使用 JS 本地对象感兴趣,Reason 提供了一个简写语法。这涉及用双引号(<code>""</code>)来包装 <code>key</code> 名称。</p>\n<pre><code class=\"language-ocaml\">type jsObject = {.\n "response": {.\n "data": {.\n "starCount": int,\n "watchers": int\n },\n "code": int\n }\n};\n\nlet jsObject:jsObject = {\n "response": {\n "data": {\n "starCount": 9,\n "watchers": 2\n },\n "code": 200\n }\n};\n</code></pre>\n<p>要访问字段,请使用 <code>##</code> 符号。</p>\n<pre><code class=\"language-ocaml\">let starCount: int = jsObject##reponse##data##starCount;\nprint_endline(string_of_int(starCount));\n</code></pre>\n<h3>变种</h3>\n<p>变体是 Reason 中唯一的数据结构。他们让我们表达这种或那种关系。</p>\n<pre><code class=\"language-ocaml\">type tweetQuality =\n | Dope /* 这些被称为变体的构造函数或标签。 */\n | Sweet\n | NotBad\n | AF\n</code></pre>\n<p>变体通常与 Reason 的 switch 语句一起用于模式匹配。</p>\n<pre><code class=\"language-ocaml\">let tweetStatus = status: string =>\n switch (status) {\n | Dope => "That was a dope tweet!"\n | Sweet => "Pretty sweet tweet!"\n | NotBad => "Not great, but not bad!"\n | AF => "Pretty af tweet my friend!"\n };\n\nprint_endline(tweetStatus(AF));\n</code></pre>\n<p>变体需要明确的定义。通过调用它们所在的模块来导入它们。</p>\n<pre><code class=\"language-ocaml\">let team: Team.seattleVariant = Mariners;\n</code></pre>\n<p>变体构造函数也可以带参数。查看 Team.re 中的 seattleVariant,它具有以下形状:</p>\n<pre><code class=\"language-ocaml\">type seattleVariant =\n | Mariners(player)\n | Sonics(player, year)\n | Seahawks\n | Sounders;\n</code></pre>\n<p>这看起来很像函数参数!我们可以在模式匹配中使用这个优势!</p>\n<pre><code class=\"language-ocaml\">open Team;\nlet player: Team.bostonVariant = RedSox("Mookie Betts");\n\nlet namePlayer = arg =>\n switch (arg) {\n | RedSox(name) => {j|You chose $name|j}\n | Celtics(name, year) => year < 2008 ? name : "Big 3"\n | Patriots => "Malcolm Butler"\n | Bruins => "Zdeno Chara"\n };\n\nprint_endline(namePlayer(player));\nprint_endline(namePlayer(Celtics("Larry Bird", 2009)));\nprint_endline(namePlayer(Celtics("Larry Bird",1984)))\n</code></pre>\n<p>独立库为您提供了一些很酷的变体。<code>type option('a) = None | Some('a)</code>;- 这允许你定义可以为 <code>null</code> 或者 <code>undefined</code> 的类型。例如,<code>option(int)</code> 键入一个变量作为可为空的整数。</p>\n<pre><code class=\"language-ocaml\">let isNull = true;\n\nlet possiblyNullInt: option(int) =\n if (isNull) {\n None\n } else {\n Some(5);\n };\n\nlet checkNull = (num: option(int)) => {\n switch (num) {\n | Some(int) => false\n | None => true\n };\n\nprint_endline(string_of_bool(checkNull(possiblyNullInt)));\n}\n</code></pre>\n<h3>列表和数组</h3>\n<p>列表是同类型的,不可变的,并且在预添加项目时很快。列表看起来很像 JS 中的数组。</p>\n<pre><code class=\"language-ocaml\">let fibList: list(int) = [1, 1, 2, 3, 5, 8, 13, 21];\n</code></pre>\n<p>要添加到列表中,请使用延展运算符。这不会改变原始列表。相反,新列表维护一个链接到扩展列表。例如,下面的 fibListHeadZero 与 fibList 共享其元素,使其非常高效。</p>\n<pre><code class=\"language-ocaml\">let fibListHeadZero = [0, ...fibList];\n</code></pre>\n<p>需要注意的是,在 Reason 中不允许使用双延展操作符,即不允许类似这样 [a,...b,...c] 。要访问任意列表项,请使用 List 模块中的 List.nth。</p>\n<pre><code class=\"language-ocaml\">let five = List.nth(fibList, 4);\nprint_endline(string_of_int(five));\n</code></pre>\n<p>要获得列表的长度,使用List.length。</p>\n<pre><code class=\"language-ocaml\">let length: int = List.length(fibList);\nlet lastItem: int = List.nth(fibList, length - 1);\n</code></pre>\n<p>List 模块 附带有用于在列表上操作的附加内置方法。</p>\n<pre><code class=\"language-ocaml\">let reverse = List.rev(fibList);\nlet sum = List.fold_left((acc, el) => acc + el, 0, fibList);\nprint_endline(string_of_int(sum));\n\nlet thriteen = List.find(item => item === 13, fibList);\nprint_endline(string_of_int(thirteen));\nlet aboveTen = List.filter(item => item > 10, fibList);\n\nList.iter(item => print_endline(string_of_int(item)), aboveTen);\n</code></pre>\n<p>数组就像列表,但是对于随机访问和更新是可变的和优化的。它们的大小是固定的,但在 JS 上是灵活的。数组的两端都用 <code>[|</code> 和 <code>|]</code> 表示。</p>\n<pre><code class=\"language-ocaml\">let fibArray: array(int) = [|1, 1, 2, 3, 5, 8, 13, 21|];\n</code></pre>\n<p>数组访问和更新类似于 JS。</p>\n<pre><code class=\"language-ocaml\">let length: int = Array.length(fibArray);\nlet lastItem: int = fibArray[length - 1]\nfibArray[2] = 500;\n</code></pre>\n<p>您也可以使用标准库中的 <code>Array.get</code> 和 <code>Array.set</code>。</p>\n<pre><code class=\"language-ocaml\">fibArray[2] = 1000;\nprint_endline(string_of_int(fibArray[2]));\n</code></pre>\n<p>要将数组转换为列表,请在 ArrayLabels 模块中使用 <code>.to_list</code> 函数。</p>\n<pre><code class=\"language-ocaml\">let fibArrayAsList: list(int) = ArrayLabels.to_list(fibArray);\nlet fibListAsArray: Array(int) = ArrayLabels.of_list(fibArrayAsList);\n</code></pre>\n<p>Reason 还支持多维数组。前两个参数指定数组的维数,而第三个参数提供填充数组的初始值。</p>\n<pre><code class=\"language-ocaml\">let multiDemArray = Array.make_matrix(2, 2, "Initial.");\nJs.log(multiDemArray);\n</code></pre>\n<h3>函数</h3>\n<p>函数使用 => 声明。单行函数是允许的。多行函数应该被 <code>{}</code> 包围。在理由中,所有函数都有参数。如果没有显式参数传递,我们传递 <code>()</code>,unit 类型。</p>\n<pre><code class=\"language-ocaml\">let noArg = () : unit => print_endline("This is unit!");\nlet add = x => x + x;\nlet square = x => x * x;\n</code></pre>\n<p>管道操作符的预览!</p>\n<pre><code class=\"language-ocaml\">let addAndSquare = x => x |> add |> square;\n\nprint_endline(string_of_int(addAndSquare(4)));\n</code></pre>\n<p>Reason 也有标签参数的概念。由于 Reason 支持 currying(柯里化),因此我们可以使用带标签的参数以任意顺序指定参数。</p>\n<pre><code class=\"language-ocaml\">let concatStringInt = (~int: int, ~str: string) =>\n string_of_int(int) ++ " " ++ str;\n\nprint_endline(concatStringInt(~str="is an int.", ~int=50));\n</code></pre>\n<p>您也可以使用标签参数来在函数中使用。</p>\n<pre><code class=\"language-ocaml\">let calcTriangleArea = (~base as b: float, ~height as h: float): float => 0.5 *. b *. h;\nprint_endline(string_of_float(calcTriangleArea(~base=2.0, ~height=7.0)));\n</code></pre>\n<h3>柯里</h3>\n<p>Reason 函数可以自动部分调用。事实上,Reason 中的所有函数都接受一个参数!</p>\n<pre><code class=\"language-ocaml\">let multiply = (x, y) => x * y;\nlet multiplyByFive = multiply(5);\nlet result = multiplyByFive(6);\n\nprint_endline(string_of_int(result));\n</code></pre>\n<p>上面的乘法函数相当于</p>\n<pre><code class=\"language-ocaml\">let multiply = (x, y) => x * y;\n</code></pre>\n<p>OCaml 为我们优化了这一点,以避免不必要的函数分配。</p>\n<h3>可选的标签参数</h3>\n<p>在 Reason 中可以用 <code>=?</code> 创建可选的标签参数。</p>\n<pre><code class=\"language-ocaml\">let sayHello = (~greeting as g, ~name=?, ()) => {\n let person =\n switch (name) {\n | None => ""\n | Some(a) => a\n };\n print_endline(g ++ " " ++ person);\n};\n\nsayHello(~greeting="Marhaba", ());\nsayHello(~greeting="Ahlan ya", ~name="Parker", ());\n</code></pre>\n<p>在函数定义的第三个索引中注意到括号 (),并在上面调用。没有它,Reason 就无法解析函数。<code>greeting</code> 和 <code>name</code> 都可以柯里化和无序运用,所以目前还不清楚 sayHello(~greeting ="Aloha") 是什么意思。OCaml 解析 () 表示可选标记的 arg 被省略了。否则,它会将该函数解析为正在等待要应用的名称的curried函数。</p>\n<pre><code class=\"language-ocaml\">/* 这里我们调用上面定义的实际函数sayHello。 */\nlet actualFunction = sayHello(~greeting="Marhaba", ());\n\n/* 这里我们返回一个可以接受〜name参数的函数。 */\nlet curriedFunction = sayHello(~greeting="Marhaba");\n\ncurriedFunction(~name="Parker", ());\n\n</code></pre>\n<p>有时,您不知道您转发给函数的值是 None 还是 Some(val)。在这种情况下,您可以提供一个明确传递的可选类型。</p>\n<pre><code class=\"language-ocaml\">let possibleName: option(string) = Some("Formidable");\n\nsayHello(~greeting="Hi ya", ~name=?possibleName, ());\n</code></pre>\n<p>如果 possibleName 具有构造函数 None,那么上面的函数仍然可以工作!</p>\n<p>您还可以提供默认值,如 JS。只需在参数定义中使用 <code>=</code> 来指定它们即可。下面,除非通过明确的值,否则 Aloha 将成为问候语的值。</p>\n<pre><code class=\"language-ocaml\">let sayHello = (~greeting="Aloha", ~name=?, ()) => {\n let person =\n switch (name) {\n | None => ""\n | Some(a) => a\n };\n print_endline(greeting ++ " " ++ person);\n};\n\nsayHello();\n</code></pre>\n<h3>递归函数</h3>\n<p>要定义递归函数,请使用 <code>rec</code> 关键字。</p>\n<pre><code class=\"language-ocaml\">let rec factorial = (num: int) =>\n if (num === 0) {\n 1;\n } else {\n num * factorial(num - 1);\n };\n\nprint_endline(string_of_int(factorial(5)));\n</code></pre>\n<h3>相互递归函数</h3>\n<p>函数可以在 Reason 中递归调用对方。使用 <code>and</code> 关键字来实现这一点。下面的示例还预览了我们如何在 Reason 中使用异常来创建自定义错误。</p>\n<pre><code class=\"language-ocaml\">exception FactorialArgument(string);\n\nlet rec factorialEven = (num: int) =>\n if (num === 0) {\n 1;\n } else {\n switch (num mod 2) {\n /* 模式匹配来检查数字是偶数还是奇数。 */\n | 0 => num * factorialOdd(num - 1)\n | 1 =>\n raise(\n FactorialArgument(\n "factorialEven only accepts even-numbered arguments."\n )\n )\n | _ => 1\n };\n }\nand factorialOdd = (num: int) =>\n if (num === 0) {\n 1;\n } else {\n switch (num mod 2) {\n | 0 =>\n raise(\n FactorialArgument(\n "factorialOdd only accepts odd-numbered arguments."\n )\n )\n | 1 => num * factorialEven(num - 1)\n | _ => 1\n };\n };\n\nprint_endline(string_of_int(factorialEven(6)));\nprint_endline(string_of_int(factorialOdd(5)));\n\n</code></pre>\n<p>以下调用会抛出我们的 FactorialArgument 异常,并说 factorialEven 只接受偶数参数。</p>\n<pre><code class=\"language-ocaml\">print_endline(string_of_int(factorialEven(5)));\n</code></pre>\n<p>如果您来自 JS,上述模式的用处就会起作用,因为 Reason / OCaml 不会提升变量或函数声明!考虑如何调用两个函数 a 和 b 来调用另一个函数。这在 JS 中是可行的,因为函数声明被提升到作用域的顶部。既然 Reason 没有提升,相互递归就是解决这个问题的一种技术。</p>\n<h3>更多关于类型</h3>\n<p>您可以在 Reason 中创建参数化类型,以使类型更具表现力。它们像函数一样,接受参数和返回类型。类型参数前缀为 <code>'</code>。</p>\n<pre><code class=\"language-ocaml\">type measurements('a) = ('a, 'a)\ntype measurementsInt = measurements(int);\ntype measurementsString = measurements(string);\n\nlet modalSize: measurements(int) = (150, 300);\nlet modalArea = fst(modalSize) * snd(modalSize);\n\nprint_endline(string_of_int(modalArea));\n\nlet dialogSize: measurements(string) = ("500", "1000");\nlet (w, h) = dialogSize;\nlet dialogDescription = {j| This dialog is $w by $h px |j};\n\nprint_endline(dialogDescription);\n\n</code></pre>\n<p>大多数情况下,Reason 的类型推断将会为您处理参数类型!</p>\n<p>类型也可以使用变体。</p>\n<pre><code class=\"language-ocaml\">type httpResult('a, 'b) =\n | Success('a)\n | Failure('b);\n\ntype payload = {\n data: string,\n code: int,\n}\n</code></pre>\n<p>组合类型 <code>httpResult</code> 需要两个参数,并将它们应用于 <code>Success</code> 和 <code>Failure</code> 构造函数。</p>\n<pre><code class=\"language-ocaml\">let result: httpResult(payload, int) = Success({\n data: "woohoo",\n code: 200\n });\n\nlet errResult: httpResult(payload, int) = Failure(404);\n</code></pre>\n<p>由于 Reason 的类型系统允许使用类型级别的函数,因此不需要太多类型的样板。例如,我们可以使用 <code>list(int)</code> 和 <code>list(string)</code>,而不需要为每个都创建一个新的基本类型,即 listOfInt 和 listOfString。这使得 Reason 的类型系统具有超凡的表现力,同时仍然提供了坚如磐石的系统的脚手架。请享用!</p>\n<h3>相互递归类型</h3>\n<p>类型如函数,可以是相互递归的。</p>\n<pre><code class=\"language-ocaml\">type professor = {courses: list(course)}\nand course = {\n name: string,\n professor,\n};\n</code></pre>\n<h3>解构</h3>\n<p>解构是 Reason 中的常见模式,对于从结构中提取数据非常有用。</p>\n<pre><code class=\"language-ocaml\">let teams = ("Mariners", "Red Sox", "Astros", "Twins");\nlet (ms, bosox, stros, twins) = teams;\n\nprint_endline(\n {j|$ms, $bosox, $stros, $twins === Parkie-Doo's playoff picks.|j}\n );\n</code></pre>\n<p>以下是如何解构 Reason 记录。它看起来很像 JS 中的对象解构。</p>\n<pre><code class=\"language-ocaml\">type album = {\n name: string,\n artist: string,\n year: int,\n};\n\nlet myFavoriteAlbum: album = {\n name: "Illinois",\n artist: "Sufjan Stevens",\n year: 2004,\n};\n\nlet {name, artist, year} = myFavoriteAlbum;\n\nprint_endline({j|$artist wrote $name in $year.|j});\n</code></pre>\n<p>当您解构它们的时候也可以给变量起别名,全部在一行中!这与 ES6 解构相似。</p>\n<pre><code class=\"language-ocaml\">let {name: n, artist: a, year: y} = myFavoriteAlbum;\n\nprint_endline({j|$a wrote $n in $y.|j})\n</code></pre>\n<p>你甚至可以解构和别名函数参数!</p>\n<pre><code class=\"language-ocaml\">type exclamation = {\n phrase: string,\n volume: float,\n};\n\nlet exclaim = (~exclamation as {phrase} as exclamation) =>\n/* 您可以访问惊叹号(记录)和 短语属性作为一个解构变量。 */\nprint_endline(\n {j|And lo, Parkie-Doo shouted, $phrase at $exclamation.volume DB.|j}\n );\n\nexclaim(~exclamation={phrase: "Breathtaking, this Reason!", volume: 120.7});\n</code></pre>\n<h3>模式匹配</h3>\n<p>模式匹配是将数据与一组值相匹配的好方法。模式匹配最好与变体一起使用 - 何时这样做,我们可以从类型系统获得很好的全面帮助,从而检查不匹配的案例。</p>\n<pre><code class=\"language-ocaml\">type victory =\n | NailBiter(int)\n | BlowOut(int)\n | OT(string, int);\n\nlet myVictory: victory = OT("8:03", 1);\n</code></pre>\n<p>模式匹配允许我们解构变体,分别处理每个案例。要查看编译器警告你一个不匹配的情况,请尝试在下面注释掉 BlowOut。</p>\n<pre><code class=\"language-ocaml\">let myOTVictory =\n switch (myVictory) {\n | NailBiter(margin) => {j|yeesh, close game. Nice win by $margin.|j}\n | BlowOut(margin) => {j|Damn, what a blowout. $margin run is impressive|j}\n | OT(time, margin) => {j|It took $time to win by $margin. But a win's a win.|j}\n };\n\nprint_endline(myOTVictory);\n</code></pre>\n<p>我们也可以用其他数据结构来切换其他情况。例如,一个 <code>array(int)</code>。</p>\n<pre><code class=\"language-ocaml\">let arr = [|500, 600|];\n\nlet handleArray = (array: array(int)) =>\n switch (array) {\n | [|500, 600|] => print_endline("This is a very specific case.")\n | [|500, _|] => print_endline("You have two items in this array, and the first is 500.")\n | [|_, _|] => print_endline("You have two items in this array.")\n | _ => print_endline("This is the default.")\n };\n\nhandleArray(arr);\nhandleArray([|500, 601|]);\nhandleArray([|101, 102|]);\nhandleArray([|1s|]);\n</code></pre>\n<p>你甚至可以模式匹配一组结果到一个特定的结果。例如,让我们将服务器上的错误映射到特定的结果。</p>\n<pre><code class=\"language-ocaml\">type httpResultWithCode =\n | Success(int, list(string))\n | Failure(int);\n\nlet handleResult = (res: httpResultWithCode) =>\n switch (res) {\n | Success(200, data) =>\n let f = (acc, el) => acc ++ " " ++ el;\n /* 我们在这里使用 fold_left 来连接字符串。 fold_left 类似于 reduce,但用于列表! */\n let resString = ListLabels.fold_left(~f, ~init="", data);\n print_endline({j|data: $resString|j});\n | Failure(500)\n | Failure(502) => print_endline("Server error.")\n | Failure(404) => print_endline("Not found.")\n | _ => print_endline("we don't know what happened, sorry!")\n };\n\nhandleResult(Failure(500));\nhandleResult(Failure(501));\nhandleResult(Success(200, ["You", "Rock"]));\nhandleResult(Success(201, ["You", "Are", "Still", "Great"]));\n</code></pre>\n<p>您也可以使用 <code>when</code> 子句来检查案例的特定条件。这就像在你的模式匹配逻辑中添加一点点 <code>if</code> 语法糖。扩展我们上面的例子:</p>\n<pre><code class=\"language-ocaml\">let isServerError = err => err === 500;\nlet isBadGateway = err => err === 502;\n\nlet handleResult = (res: httpResultWithCode) =>\n switch (res) {\n | Success(200, data) =>\n let f = (acc, el) => acc ++ " " ++ el;\n let resString = ListLabels.fold_left(~f, ~init="", data);\n print_endline({j|data: $resString|j});\n | Failure(errCode) when isServerError(errCode) =>\n print_endline("Server error.")\n | Failure(errCode) when isBadGateway(errCode) =>\n print_endline("Bad gateway. Getaway? Who knows?")\n | Failure(404) => print_endline("Not found.")\n | _ => print_endline("we don't know what happened, sorry!")\n };\n\nhandleResult(Failure(500));\nhandleResult(Failure(502));\n</code></pre>\n<p>您也可以嵌套模式,这意味着我们可以对数据结构的嵌套属性进行模式匹配。</p>\n<pre><code class=\"language-ocaml\">type composer = {\n name: string,\n concertos: int,\n};\n\nlet evaluateComposer = (composer: composer) =>\n switch (composer) {\n | {name: "Beethoven" | "Mozart" | "Debussy"} => "What high class. How fancy!"\n | composer when composer.concertos <= 7 => "Not too bad, but nothing special."\n | _ => "Just another composer"\n };\n\nprint_endline(evaluateComposer({name: "Debussy", concertos: 57}));\nprint_endline(evaluateComposer({name: "Jerry", concertos: 7}));\n</code></pre>\n<h3>变动</h3>\n<p><code>let</code> 绑定默认情况下是不可变的。如果你需要改变一个 <code>let</code> 绑定,使用 <code>ref</code> 关键字的包起它的值。</p>\n<pre><code class=\"language-ocaml\">let mutableVar = ref("mutable");\n</code></pre>\n<p>要访问它,请使用 <code>^</code> 运算符。</p>\n<pre><code class=\"language-ocaml\">let mutableReference = mutableVar^;\n</code></pre>\n<p>要重新分配一个可变变量,请使用 <code>:=</code> 运算符。</p>\n<pre><code class=\"language-ocaml\">mutableVar := "mutated";\nprint_endline(mutableVar^);\n</code></pre>\n<p>然而,你也可以简单地通过用另一个let绑定来映射它们来改变变量。</p>\n<pre><code class=\"language-ocaml\">let shodow = "First I am this!";\n\nprint_endline(shadow);\n\nlet shadow = "But now I've been shadowed!";\n\nprint_endline(shadow);\n</code></pre>\n<h3>命令式循环</h3>\n<p>for 循环在 Reason 中迭代从一个开始直到包括一个结束值。</p>\n<pre><code class=\"language-ocaml\">let repeatCapitalize = (~time: int, ~str: string) => {\n let result = ref("");\n for (time in 1 to times) {\n switch (time mod 2) {\n | 0 => result := result^ ++ String.capitalize(str)\n | 1 => result := result^ ++ str\n | _ => result := result^ ++ str\n };\n };\n result^;\n};\n\nprint_endline(repeatCapitalize(~time=5, ~str="reason"));\n</code></pre>\n<p>您也可以使用 <code>downto</code> 操作符从一个数字开始迭代。</p>\n<pre><code class=\"language-ocaml\">let factorialSum = (~num: int): int => {\n let result = ref(0);\n for (n in num downto 0) {\n result := result^ + n;\n };\n result^;\n};\n\nprint_endline(string_of_int(factorialSum(~num=5)));\n</code></pre>\n<p>总的来说,<code>Array</code> 和 <code>List</code> 方法可能会让你更接近你想要的东西,而不是 for 循环,但是它们很好知道。您也可以像在 JS 中那样使用 while 循环。</p>\n<pre><code class=\"language-ocaml\">let count = ref(1);\n\nwhile (count < ref(5)) {\n print_endline("We are while looping!");\n count := count^ + 1;\n};\n</code></pre>\n<p>使用上面的可变绑定来跳出循环。Reason 没有像 JS 这样的 <code>break</code> 关键字的概念。</p>\n<h3>JSX</h3>\n<p>Reason 本身支持 JSX 。调用如下:</p>\n<pre><code class=\"language-ocaml\"><div foo={bar}> child1 child2 </div>\n</code></pre>\n<p>变成:</p>\n<pre><code class=\"language-ocaml\">([@JSX] div(~foo=bar, ~children=[child1, child2], ()));\n</code></pre>\n<p>[@JSX] 语法将一个函数标记为想要格式化为 JSX。这在任何 Reason 库中都是允许的,而不仅仅是 ReasonReact,来利用JSX。</p>\n<h3>外部对象</h3>\n<p>外部是理性与其他语言(通常是 JS )的交互方式。使用外部最常用的方法之一是加载模块。例如,让我们从这个 repo 中的 findS 模块加载 findS 函数。</p>\n<pre><code class=\"language-ocaml\">[@bs.module "./findS"] external findS: string => int = "";\n\nlet twos = findS("strings");\n\nprint_endline(string_of_int(twos));\n</code></pre>\n<p>优雅!我们从 Reason 中调用了一个 JS 函数!上面的注释允许我们从 findS 模块中键入函数 findS,并返回一个类型安全引用到该模块。让我们看看我们如何访问一个作用域函数,比如 Math.random。</p>\n<pre><code class=\"language-ocaml\">[@bs.val] [@bs.scope "Math"] external random : unit => float = "random";\n[@bs.val] [@bs.scope "Math"] external floor : float => int = "floor";\n</code></pre>\n<p>在上面的定义中,我们说在 Math 作用域中有一个方法叫做 <code>random</code>。它将 unit 类型作为参数并返回一个浮点数。我们将它别名命名为 <code>random</code> ,以便在 Reason 中使用。现在我们可以使用它!</p>\n<pre><code class=\"language-ocaml\">let getRandomInt = (~max as m: float) =>\n floor(random() *. float_of_int(floor(m)));\n\nprint_endline(string_of_int(getRandomInt(~max=100.0)));\n</code></pre>\n<h3>对象</h3>\n<p>大多数情况下,您将使用记录来存储名称和值。但有时候,你会想要一个对象。请注意,Reason 中的对象与 JS 对象不同。使用 <code>pub</code> 关键字将对象的公共值前缀。 <code>val</code> 可用于定义不可从外部访问的对象上的值。 <code>pri</code> 可以定义私有方法。</p>\n<pre><code class=\"language-ocaml\">let anUntypeReasonObject = {pub city = "Burlington"; val state = "Vermont"};\n\nprint_endline(anUntypeReasonObject#city);\n</code></pre>\n<p>Reason 对象不需要类型定义。如果我们确定它们,则该对象必须具有所提供的类型的形状。</p>\n<pre><code class=\"language-ocaml\">type locale = {\n .\n city: string,\n state: string,\n population: int,\n};\n\nlet burlington: locale = {\n pub city = "Burlington";\n pub state = "Vermont";\n pub population = 56000\n};\n\nprint_endline(string_of_int(burlington#population));\n</code></pre>\n<p>两个点(称为elision)表示此对象已打开,并且可以具有除原始类型属性之外的属性。开放对象是多态的,需要一个类型参数。</p>\n<pre><code class=\"language-ocaml\">type localeOpen('a) = {.. getplace: unit => string} as 'a;\n</code></pre>\n<p>Reason 中的对象有一个 <code>this</code> 上下文,它指向对象本身。</p>\n<pre><code class=\"language-ocaml\">let vt: localeOpen({. getPlace: unit => string}) = {\n val address = "100 Church St";\n val city = "Burlington";\n val zipCode = "05712";\n pub getPlace = () =>\n address ++ ", " ++ city ++ " " ++ zipCode ++ ". " ++ this#addOn();\n pri addOn = () => "Didn't ya know?"\n};\n\nprint_endline(vt#getPlace());\n</code></pre>\n<p>大多数情况下,您将使用记录或 JS 对象来实现您在 JS 中使用的内容。Reason 有一个很好的定义 JS 对象的语法。这涉及到将对象键包装在 <code>""</code> 中并使用 <code>##</code> 进行访问。</p>\n<pre><code class=\"language-ocaml\">let reason = {"color": "orange", "language": "reason", "users": 5000};\nprint_endline(reason##language);\n</code></pre>\n<p>改变 JS 对象涉及让 BuckleScript 知道我们有一个可变的键 - 值对。为此,请使用 <code>@bs.set</code> 注释类型。然后,使用 <code>#=</code> 改变它。</p>\n<pre><code class=\"language-ocaml\">type language = {\n .\n [@bs.set] "color": string,\n "language": string,\n "users": int,\n};\n\n[@bs.module "./findS"] external myJSObject : language = "";\n\nprint_endline(myJSObject##language);\n\nmyJSObject##color#="orange";\n\nprint_endline(myJSObject##color);\n</code></pre>\n<h3>模块</h3>\n<p>Reason 中的模块可以包含类型定义,<code>let</code> 绑定,嵌套模块,几乎任何东西。使用 module 关键字来创建一个。</p>\n<pre><code class=\"language-ocaml\">module Earth = {\n let continents = [|\n "Africa",\n "Antarctica",\n "Asia",\n "Australia",\n "Europe",\n "North America",\n "South America",\n |];\n\n let pickContinent = (idx: int) => continents[idx];\n};\n\nlet aussie = Earth.pickContinent(3);\n\nprint_endline(aussie);\n</code></pre>\n<p>我们也可以访问嵌套模块!查看 <code>Team</code> 模块,它有一个嵌套的 <code>Boston</code> 模块。</p>\n<pre><code class=\"language-ocaml\">print_endline(Team.Boston.team);\n</code></pre>\n<p>虽然 Reason 有很好的模块推理 - 没有 <code>import</code> 概念! - 使用 open 关键字明确地打开模块可能很有用。这将模块纳入作用域内。我们可以在本地为一个特定的函数打开一个模块,如下所示:</p>\n<pre><code class=\"language-ocaml\">let fact =\n Team.Boston.(\n switch (team) {\n | "Red Sox" => "The Red Sox are DOPE."\n | _ => "Eh, don't really care."\n }\n );\n\nprint_endline(fact);\n</code></pre>\n<p>全局打开对于在另一个模块中获取所有内容非常有用。但是要小心,如果模块中有共享名称的成员,这可能会引入不必要的命名冲突/阴影。</p>\n<pre><code class=\"language-ocaml\">open Team.Boston;\n</code></pre>\n<p>模块也可以相互扩展,在传统的 OOP 语言中履行继承或混合的角色。</p>\n<pre><code class=\"language-ocaml\">module ExtendedBoston = {\n include Team.Boston;\n let basketball = "Celtics";\n};\n\nprint_endline(ExtendedBoston.team);\n\nprint_endline(ExtendedBoston.basketball);\n</code></pre>\n<h3>Promises</h3>\n<p>Reason 中的 Promises 通过使用 Js.Promise.* 方法的 BuckleScript 处理。注意。<code>.</code> 作为第一个参数传递到 <code>resolve</code> 。这可以让 BuckleScript 知道这是一个非柯里化函数。这是在 BuckleScript 2.2.2 中引入的。如果使用旧版本的 bs-platform ,则可以使用 <code>[@bs] resolve(100)</code> 获得相同的效果。</p>\n<pre><code class=\"language-ocaml\">let promise = Js.Promise.make((~resolve, ~reject as _) => resolve(. 100));\n\nexception Failure(string);\nlet failedPromise = Js.Promise.make((~resolve as _, ~reject) => reject(. Failure("Rejected!")));\n\npromise\n|> Js.Promise.then_(res => {\n Js.log(res);\n Js.Promise.resolve(res);\n})\n|> Js.Promise.then_(res => {\n Js.log("That's all folks!");\n Js.Promise.resolve(res - 100);\n})\n|> Js.Promise.catch(err => {\n Js.log2("Failure!!", err);\n Js.Promise.resolve(-1);\n});\n\nfailedPromise\n|> Js.Promise.then_(res => {\n Js.log(res)\n Js.Promise.resolve(res);\n})\n|> Js.Promise.then_(res => {\n Js.Promise.resolve(res - 100);\n})\n|> Js.Promise.catch(err => {\n Js.log2("Failure!!", err);\n Js.Promise.resolve(-1);\n});\n</code></pre>\n<h3>异常</h3>\n<p>异常是一种特殊的变体,在 Reason 中不常使用。</p>\n<p>下面的函数展示了如何使用 <code>raise</code> 关键字使用 <code>Not_found</code>、<code>exception</code>。</p>\n<pre><code class=\"language-ocaml\">let aFunkyList = ["Parliament", "Funkadelic", "George Clinton"];\n if (List.exists(item => item ==="Stevie Wonder", aFunkyList)) {\n print_endline("Yay Stevie!");\n } else {\n /* 在这里,我们引发 Exception 变体的 Not_found 构造函数。 */\n raise(Not_found);\n }\n</code></pre>\n","author":"lanqy"},{"title":"给 create-react-app 创建的项目,加上热加载功能","created":"2018/05/21","link":"2018/05/21/adding-hot-module-reloading-to-create-react-app","description":"给 create-react-app 创建的项目,加上热加载功能","content":"<h1>给 create-react-app 创建的项目,加上热加载功能</h1>\n<p>修改index.js文件</p>\n<pre><code class=\"language-js\">import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\nimport './index.css';\nconst rootEl = document.getElementById('root');\nReactDOM.render(\n <App />,\n rootEl\n);\nif (module.hot) { //热替换代码\n module.hot.accept('./App', () => {\n const NextApp = require('./App').default;\n ReactDOM.render(\n <NextApp />,\n rootEl\n );\n }); \n}\n</code></pre>\n<p>来自 :https://medium.com/@sheepsteak/adding-hot-module-reloading-to-create-react-app-e053fadf569d#.jn5jammd4</p>\n","author":"lanqy"},{"title":"构建一个基于 Redux 的 react 应用","created":"2018/05/21","link":"2018/05/21/build-redux-react-application","description":"构建一个基于 Redux 的 react 应用","content":"<h1>构建一个基于 Redux 的 react 应用</h1>\n<p>在这篇文章里,我们将深入理解以及学习为什么开发React应用的时候,使用Redux这么重要。我也将一步一步教你如何创建你的第一个Redux应用,包括怎样使用<a href=\"https://github.com/stormpath/stormpath-sdk-react\">Stormpath React SDK </a>于<a href=\"https://stormpath.com/product/authentication/\"> user authentication(用户认证)</a>。当你完成的时候,你将掌握这个知识并运用到你已有到React应用中。</p>\n<p>###Redux是什么?</p>\n<p><a href=\"https://github.com/reactjs/redux\">Redux</a>是一个用于帮助我们管理应用状态到的框架(library)。它的设计源于<a href=\"https://facebook.github.io/react/docs/flux-overview.html\">Flux</a>,但是从编写Flux应用的痛苦中进化而来。如果你写过Flux应用,你将很快注意到Redux可以帮你自动做很多以前你需要手动做的事情,还有,你只有一个单一的状态容器,这是一个很大的优势,这样在你的应用中共享状态和重用代码会变得更加简单。</p>\n<h3>Stores</h3>\n<p>store是一个简单的状态容器,这是存储你应用状态和actions调度和处理的地方。当你开始创建一个Redux应用时,你要想想你的应用程序的数据和状态应该如何存储。这是很重要的,因为Redux推荐一个应用只有一个store,而且由于状态是共享的,因此在开始创建应用的时候,想好这些尤为重要。</p>\n<h3>Actions</h3>\n<p>Actions 是一些用于描述我们怎样改变我们应用状态的对象,你可以这么认为actions就是我们状态树的API。例如,一个添加用户的action可以像这样:</p>\n<pre><code class=\"language-javascript\">{\n type: 'ADD_USER',\n data: {\n name: 'Foo',\n email: 'foo@bar.com',\n password: 'Foobar123_'\n }\n}\n</code></pre>\n<p>为了让代码更清晰,更易于重用,通常是使用一个生成器(方法)生成操作对象,即,上面的例子,我们可以创建一个类似于<code>addUser(name, email, password) </code>的函数来生成对象。如你所见,actions本身什么都不做,一个action仅仅是一个用于描述我们怎样改变我们应用状态的对象。</p>\n<h3>Reducers</h3>\n<p>Actions很酷,但是它们并没有太大的意义。这就是reducers存在的意义。\nReducers是action处理器,用于在store中调度actions以及在状态变化中简化actions操作。如果我们要在store中调度action(ADD_USER),我们将拥有一个reducer调用action(ADD_USER)并添加一个新的用户到我们到应用状态中。</p>\n<h3>创建Redux应用</h3>\n<p>现在,当你了解的基础知识以后,让我们继续创建我们第一个基于Redux的应用程序。<br/>\n为了让事情变得简单,我们将要创建一个todo应用程序,这样我们可以玩转很多Redux的重要概念,而不用过多的关注应用本身。<br/>\n我们考虑一下一个todo应用程序,需要些什么基本的东西。首先一个todo程序,由一个列表组成。其实这个列表包含一些我们可以改变的项目。<br/>\n从应用的状态来看,我们可以这样定义我们的模型(model):</p>\n<pre><code class=\"language-javascript\">{\n todo: {\n items: [\n {\n message: "Finish Redux blog post...",\n completed: false\n }\n ]\n }\n}\n</code></pre>\n<h3>添加 Actions</h3>\n<p>我们要怎样处理我们应用的状态呢?首先,我们添加一个新待办项目到我们状态中去,让我们创建一个action来做这件事:</p>\n<pre><code class=\"language-javascript\">function addTodo(message) {\n return {\n type: 'ADD_TODO',\n message: message,\n completed: false\n };\n}\n</code></pre>\n<p>注意<code>type</code>这个字段,这个必须是唯一的,用于表明action的意图。\ntype字段的约定格式为大写字母,并且每个单词以下划线作为分隔,接下来你将使用这个名字/标示符在你的reducers中来处理具体的actions,并把它们变成可变的状态。<br/>\n一旦我们增加了我们的待办事项,我们肯定希望能够将其标记为已完成状态,我们也可以删除它,可以清空所有的待办项目。<br/>\n因此让我们创建actions来做这些事情:</p>\n<pre><code class=\"language-javascript\">function completeTodo(index) {\n return {\n type: 'COMPLETE_TODO',\n index: index\n };\n}\n\nfunction deleteTodo(index) {\n return {\n type: 'DELETE_TODO',\n index: index\n };\n}\n\nfunction clearTodo() {\n return {\n type: 'CLEAR_TODO'\n };\n}\n</code></pre>\n<p>到目前为止,我们已经定义好我们的actions,让我们继续定义我们的store,如前所述,store是Redux应用的核心,所有的状态(state)、调度器(dispatched actions)、reducers都依赖于它。</p>\n<pre><code class=\"language-javascript\">import { createStore } from 'redux';\n\nvar defaultState = {\n todo: {\n items: []\n }\n};\n\nfunction todoApp(state, action) {\n}\n\nvar store = redux.createStore(todoApp, defaultState);\n</code></pre>\n<h3>添加Reducers</h3>\n<p>现在,我们已经拥有一些actions和一个store。让我们创建我们的第一个reducer,如前所述,reducer仅仅是一个action处理器,拥有处理actions和改变应用的状态(state)。\n让我们先处理我们的<code>ADD_TODO</code> action,如下所示:</p>\n<pre><code class=\"language-javascript\">function todoApp(state, action) {\n switch (action.type) {\n case 'ADD_TODO':\n var newState = Object.assign({}, state);\n\n newState.todo.items.push({\n message: action.message,\n completed: false\n });\n\n return newState;\n\n default:\n return state;\n }\n}\n</code></pre>\n<p>注意,当我们说一个reducer“改变”了应用的状态,如果一个状态需要改变,我们真正要做的是创建一个状态(state)的副本,并修改这个状态的副本。如果状态没有改变,那我们就返回相同的状态。任何情况下,如果你直接修改原始的状态(state),这意味着你将改变状态的历史。<br/>\n现在我们有了第一个我们的action处理器,让我们添加其它的:<br/></p>\n<pre><code class=\"language-javascript\">function todoApp(state, action) {\n switch (action.type) {\n case 'ADD_TODO':\n var newState = Object.assign({}, state);\n\n newState.todo.items.push({\n message: action.message,\n completed: false\n });\n\n return newState;\n\n case 'COMPLETE_TODO':\n var newState = Object.assign({}, state);\n\n newState.todo.items[action.index].completed = true;\n\n return newState;\n\n case 'DELETE_TODO':\n var items = [].concat(state.todo.items);\n\n items.splice(action.index, 1);\n\n return Object.assign({}, state, {\n todo: {\n items: items\n }\n });\n\n case 'CLEAR_TODO':\n return Object.assign({}, state, {\n todo: {\n items: []\n }\n });\n\n default:\n return state;\n }\n}\n</code></pre>\n<p>###结合React UI\n现在,我们已经制定了业务逻辑,让我们写一些UI代码,由于大部分React的知识都跟构建Flux应用类似,我们就不再深入讲解。一下是我们的代码:</p>\n<pre><code class=\"language-js\">import React from 'react';\nimport ReactDOM from 'react-dom';\nimport { createStore } from 'redux';\n\nvar defaultState = {\n todo: {\n items: []\n }\n};\n\n// Add the actions here that we created in the previous steps...\n\nfunction todoApp(state, action) {\n // Add the reducer logic that we added in the previous steps...\n}\n\nvar store = createStore(todoApp, defaultState);\n\nclass AddTodoForm extends React.Component {\n state = {\n message: ''\n };\n\n onFormSubmit(e) {\n e.preventDefault();\n store.dispatch(addTodo(this.state.message));\n this.setState({ message: '' });\n }\n\n onMessageChanged(e) {\n var message = e.target.value;\n this.setState({ message: message });\n }\n\n render() {\n return (\n <form onSubmit={this.onFormSubmit.bind(this)}>\n <input type="text" placeholder="Todo..." onChange={this.onMessageChanged.bind(this)} value={this.state.message} />\n <input type="submit" value="Add" />\n </form>\n );\n }\n}\n\nclass TodoItem extends React.Component {\n onDeleteClick() {\n store.dispatch(deleteTodo(this.props.index));\n }\n\n onCompletedClick() {\n store.dispatch(completeTodo(this.props.index));\n }\n\n render() {\n return (\n <li>\n <a href="#" onClick={this.onCompletedClick.bind(this)} style=>{this.props.message.trim()}</a>&nbsp;\n <a href="#" onClick={this.onDeleteClick.bind(this)} style=>[x]</a>\n </li>\n );\n }\n}\n\nclass TodoList extends React.Component {\n state = {\n items: []\n };\n\n componentWillMount() {\n store.subscribe(() => {\n var state = store.getState();\n this.setState({\n items: state.todo.items\n });\n });\n }\n\n render() {\n var items = [];\n\n this.state.items.forEach((item, index) => {\n items.push(<TodoItem\n key={index}\n index={index}\n message={item.message}\n completed={item.completed}\n />);\n });\n\n if (!items.length) {\n return (\n <p>\n <i>Please add something to do.</i>\n </p>\n );\n }\n\n return (\n <ol>{ items }</ol>\n );\n }\n}\n\nReactDOM.render(\n <div>\n <h1>Todo</h1>\n <AddTodoForm />\n <TodoList />\n </div>,\n document.getElementById('container')\n);\n</code></pre>\n<p>如你所见,为Redux应用构建我们的UI并不难,唯一不同的是使用<code>store.subscribe(listener)</code>来监听状态(state)的改变,然后通过<code>store.getState()</code>获取状态。但除此之外,它非常像构建Flux应用。</p>\n<h3>在Stormpath React SDK中支持Redux</h3>\n<p>因为有不少请求为Stormpath React SDK添加Redux支持,因此我们实现了这个功能,如果你想配置SDK来使用Redux,简单配置dispatcher选项并设置几个type和redux指向store,具体如下:</p>\n<pre><code class=\"language-javascript\">function myApp(state, action) {\n return state;\n}\n\nReactStormpath.init({\n dispatcher: {\n type: 'redux',\n store: createStore(myApp)\n }\n});\n</code></pre>\n<p>一旦做到这一点,我们就可以通过Stormpath React SDK拦截和处理我们的actions调度,例如,如果你希望通过用户数据来丰富你的状态,那么简单地处理<code>USER_SET</code>action来添加用户数据到你的状态中。</p>\n<pre><code class=\"language-javascript\">function myApp(state, action) {\n switch (action.type) {\n case 'USER_SET':\n return Object.assign({}, state, {\n user: action.data\n });\n\n default:\n return state;\n }\n}\n</code></pre>\n<p>###总结\n正如你在文章中所看到的一样,构建一个Redux应用是非常简单和直接的。这就像是构建Flux应用一样,仅仅是概念上的差异,并且手动写的代码变得更少。希望你们喜欢这个教程,并且发现未来你可以使用它。如果有任何问题,请参考github上的<a href=\"https://github.com/typerandom/stormpath-react-redux-todo-example-application\" target=\"_blank\">项目</a>。<br/>\n###扩展阅读\n想要学习更多关于给React应用添加用户认证,请看以下教程:</p>\n<ul>\n<li><a href=\"https://stormpath.com/blog/build-a-react-app-with-user-authentication/\">Tutorial: Build a React.js Application With User Authentication</a></li>\n<li><a href=\"https://stormpath.com/blog/react-sdk-custom-forms/\">Custom Login and Registration Forms</a></li>\n</ul>\n<p>注:本文翻译自<a href=\"https://stormpath.com/blog/build-a-redux-powered-react-application/\" target=\"_blank\">Let's Build a Redux Powered React Application</a>,如有翻译不对的地方请指正。谢谢!</p>\n","author":"lanqy"},{"title":"使用 Gruntjs 压缩合并前端静态资源(图片、JavaScript 和 CSS )","created":"2018/05/21","link":"2018/05/21/grunt-example","description":"使用 Gruntjs 压缩合并前端静态资源(图片、JavaScript 和 CSS )","content":"<h1>使用 Gruntjs 压缩合并前端静态资源(图片、JavaScript 和 CSS )</h1>\n<h3>安装环境:</h3>\n<h4>Nodejs:https://nodejs.org/en/ 安装最新版本</h4>\n<h4>进入目录(cd projectName)</h4>\n<h4>新建package.json 内容如下:</h4>\n<pre><code class=\"language-js\">{\n "name": "my-project-name",\n "version": "0.1.0",\n "devDependencies": {\n "grunt": "^0.4.5",\n "grunt-contrib-concat": "^1.0.0",\n "grunt-contrib-cssmin": "^0.12.3",\n "grunt-contrib-imagemin": "^1.0.0",\n "grunt-contrib-jshint": "^0.12.0",\n "grunt-contrib-nodeunit": "~0.4.1",\n "grunt-contrib-uglify": "^0.5.1"\n }\n}\n</code></pre>\n<h4>命令行执行 <code>npm install </code></h4>\n<h4>接着安装grunt命令行工具 <code> npm install -g grunt-cli</code></h4>\n<h4>接着配置Gruntfile.js文件</h4>\n<pre><code class=\"language-js\">module.exports = function(grunt) {\n\n grunt.initConfig({\n\n //our JSHint options\n jshint: {\n all: ['main.js'] //files to lint\n },\n //our concat options\n concat: {\n options: {\n separator: ';' //separates scripts\n },\n dist: {\n src: ['html5/javascripts/jquery.min.js', 'html5/javascripts/dialog.js', 'html5/javascripts/utils.js','html5/javascripts/limit.js','html5/javascripts/pageScrollAjax.js','html5/javascripts/app.js','html5/javascripts/tabs.js','html5/javascripts/swiper.min.js'], //需要合并的文件,注意顺序\n dest: 'html5/javascripts/build.js' //合并后生成的文件\n }\n },\n\n //压缩js\n uglify: {\n js: {\n files: {\n 'html5/javascripts/build.min.js': ['html5/javascripts/build.js'] //合并后压缩\n }\n }\n },\n cssmin: {\n options: {\n keepSpecialComments: 0\n },\n compress: {\n files: { // css压缩app.min.css为压缩后生成的文件,后面的数组为需要合并压缩的文件(同样注意顺序)\n 'html5/stylesheets/app.min.css': [\n "html5/stylesheets/common.css",\n "html5/stylesheets/swiper.min.css",\n "html5/stylesheets/dialog.css",\n "html5/stylesheets/app.css",\n "html5/stylesheets/new.css",\n "html5/stylesheets/app-type.css"\n ]\n }\n }\n },\n\n imagemin: {\n /* 压缩图片大小 */\n dist: {\n options: {\n optimizationLevel: 3 //定义 PNG 图片优化水平\n },\n files: [{\n expand: true,\n cwd: 'images/html5',\n src: ['**/*.{png,jpg,jpeg}'], // 优化 img 目录下所有 png/jpg/jpeg 图片\n dest: 'images/html5' // 优化后的图片保存位置,覆盖旧图片,并且不作提示(建议新建一个目录)\n }]\n }\n }\n\n });\n //加载任务\n grunt.loadNpmTasks('grunt-contrib-jshint');\n grunt.loadNpmTasks('grunt-contrib-concat');\n grunt.loadNpmTasks('grunt-contrib-uglify');\n grunt.loadNpmTasks('grunt-contrib-cssmin');\n grunt.loadNpmTasks('grunt-contrib-imagemin');\n\n // default tasks to run\n // grunt.registerTask('default', ['jshint', 'concat', 'uglify']);\n grunt.registerTask('development', ['jshint']);\n grunt.registerTask('production', ['jshint', 'concat', 'uglify','imagemin','cssmin']);\n }\n\n\n</code></pre>\n<p>1.检查js语法<code>grunt jshint</code></p>\n<p>2.合并js <code>grunt concat</code></p>\n<p>3.压缩js<code>grunt uglify</code></p>\n<p>4.压缩css<code>grunt imagemin</code></p>\n<p>5.压缩图片<code>grunt cssmin</code></p>\n<p>(注意js压缩之前要先合并,也就是第一步和第二部有先后顺序)</p>\n<p>6.批量处理 <code>grunt production</code></p>\n<h3>相关链接:</h3>\n<h4>Gruntjs 批量无损压缩图片大小:</h4>\n<h4>https://www.zfanw.com/blog/gruntjs-optimize-image-size-loseless.html</h4>\n<h4>前端js和css的压缩合并之grunt:</h4>\n<h4>http://www.haorooms.com/post/qd_grunt_cssjs</h4>\n<h4>grunt官网:</h4>\n<h4>http://gruntjs.com/getting-started</h4>\n","author":"lanqy"},{"title":"通过代数数据类型和模式匹配进行语言比较:Koka,Rust,Haxe,Swift,Elm,PureScript,Haskell,OCaml,ReasonML,Kotlin,Scala,Dotty,Ruby,TypeScript","created":"2018/05/21","link":"2018/05/21/language-comparison","description":"通过代数数据类型和模式匹配进行语言比较:Koka,Rust,Haxe,Swift,Elm,PureScript,Haskell,OCaml,ReasonML,Kotlin,Scala,Dotty,Ruby,TypeScript","content":"<h1>通过代数数据类型和模式匹配进行语言比较:Koka,Rust,Haxe,Swift,Elm,PureScript,Haskell,OCaml,ReasonML,Kotlin,Scala,Dotty,Ruby,TypeScript</h1>\n<h2>代数数据类型</h2>\n<h3>Koka</h3>\n<pre><code class=\"language-haskell\">type color {\n Red\n Green\n Blue\n Rgb( r : int, g : int, b: int )\n}\n</code></pre>\n<p>或</p>\n<pre><code class=\"language-haskell\">type color {\n Red; Green; Blue; Rgb( r : int, g : int, b: int )\n}\n</code></pre>\n<h3>rust</h3>\n<pre><code class=\"language-rust\">enum Color {\n Red,\n Green,\n Blue,\n Rgb { r: u8, g: u8, b: u8 }\n}\n</code></pre>\n<h3>Haxe</h3>\n<pre><code class=\"language-haxe\">enum Color {\n Red;\n Green;\n Blue;\n Rgb(r: Int, g: Int, b: Int);\n}\n</code></pre>\n<h3>swift</h3>\n<pre><code class=\"language-swift\">enum Color {\n case Red, Green, Blue, Rgb(r: Int, g: Int, b: Int)\n}\n</code></pre>\n<h3>Elm</h3>\n<pre><code class=\"language-elm\">type Color = Red | Green | Blue | Rgb { r: Int, g: Int, b: Int }\n</code></pre>\n<h3>PureScript</h3>\n<pre><code class=\"language-haskell\">data Color = Red | Blue | Green | Rgb{ r :: Int, g :: Int, b :: Int }\n</code></pre>\n<h3>Haskell</h3>\n<pre><code class=\"language-haskell\">data Color = Red | Green | Blue | Rgb {r :: Int, g :: Int, b :: Int}\n</code></pre>\n<h3>OCaml</h3>\n<pre><code class=\"language-ocaml\">type rgb = { r: int; g: int; b: int }\ntype color = Red | Green | Blue | Rgb of rgb\n</code></pre>\n<h3>ReasonML</h3>\n<pre><code class=\"language-ocaml\">type rgb = { r: int, g: int, b: int };\ntype color =\n | Red\n | Green\n | Blue\n | Rgb(rgb);\n</code></pre>\n<h3>Kotlin</h3>\n<pre><code class=\"language-kotlin\">sealed class Color {\n object Red: Color()\n object Green: Color()\n object Blue: Color()\n class Rgb(val r: Int,val g: Int,val b: Int): Color()\n}\n</code></pre>\n<h3>Scala</h3>\n<pre><code class=\"language-scala\">sealed trait Color\n\nfinal case object Red extends Color\nfinal case object Green extends Color\nfinal case object Blue extends Color\nfinal case class Rgb(r: Int, g: Int, b: Int) extends Color\n</code></pre>\n<h3>Dotty</h3>\n<pre><code class=\"language-scala\">enum Color {\n case Red\n case Green\n case Blue\n case Rgb(r: Int, g: Int, b: Int)\n}\n</code></pre>\n<h3>TypeScript</h3>\n<pre><code class=\"language-typescript\">type Color = Red | Green | Blue | Rgb;\nconst enum ColorKind {\n Red = "Red",\n Green = "Green",\n Blue = "Blue",\n Rgb = "Rgb"\n}\n\ninterface Red {\n kind: ColorKind.Red;\n}\n\ninterface Green {\n kind: ColorKind.Green;\n}\n\ninterface Blue {\n kind: ColorKind.Blue;\n}\n\ninterface Rgb {\n kind: ColorKind.Rgb;\n r: number;\n g: number;\n b: number;\n}\n</code></pre>\n<h3>Ruby</h3>\n<pre><code class=\"language-ruby\">module Color\n Red = 1\n Green = 2\n Blue = 3\n Rgb = Struct.new(:r, :g, :b)\nend\n</code></pre>\n<h2>感想</h2>\n<h3>符号</h3>\n<ol>\n<li>Koka, Rust, Haxe, Elm: 非常好</li>\n<li>Dotty, Swift, PureScript, Haskell, OCaml: 比较好</li>\n<li>Ruby: 一般</li>\n<li>Kotlin: 很难写</li>\n<li>Scala: 写起来很难</li>\n</ol>\n<h2>模式匹配</h2>\n<h3>Koka</h3>\n<pre><code class=\"language-haskell\">\nmatch(color) {\n Red -> "#FF0000"\n Green -> "#00FF00"\n Blue -> "#0000FF"\n Rgb(r,g,b) -> "#" + showHex(r,2) + showHex(g,2) + showHex(b,2)\n}\n</code></pre>\n<h3>Rust</h3>\n<pre><code class=\"language-rust\">match color {\n Color::Red => "#FF0000".to_string(),\n Color::Green => "#00FF00".to_string(),\n Color::Blue => "#0000FF".to_string(),\n Color::Rgb{r, g, b} => format!("#{:02X}{:02X}{:02X}", r, g, b),\n}\n</code></pre>\n<h3>Haxe</h3>\n<pre><code class=\"language-haxe\">switch( color ) {\n case Red: "#FF0000";\n case Green: "#00FF00";\n case Blue: "#0000FF";\n case Rgb(r, g, b): "#"+ StringTools.hex(r,2) + StringTools.hex(g,2) + StringTools.hex(b,2);\n}\n</code></pre>\n<h3>Swift</h3>\n<pre><code class=\"language-swift\">switch color {\n case .Red:\n return "#FF0000"\n case .Green:\n return "#00FF00"\n case .Blue:\n return "#0000FF"\n case let .Rgb(r, g, b):\n return String(format:"#%02X%02X%02X", r, g, b)\n}\n</code></pre>\n<h3>Elm</h3>\n<pre><code class=\"language-elm\">case color of\n Red -> "#FF0000"\n Green -> "#00FF00"\n Blue -> "#0000FF"\n Rgb {r, g, b} -> String.concat ["#", (toHex r), (toHex g), (toHex b)]\n</code></pre>\n<h3>PureScript</h3>\n<pre><code class=\"language-haskell\">case color of\n Red -> "#FF0000"\n Green -> "#00FF00"\n Blue -> "#0000FF"\n Rgb { r, g, b } -> "#" <> toHex r <> toHex g <> toHex b \n</code></pre>\n<h3>Haskell</h3>\n<pre><code class=\"language-haskell\">case color of\n Red -> "#FF0000"\n Green -> "#00FF00"\n Blue -> "#0000FF"\n Rgb r g b -> printf "#%02X%02X%02X" r g b\n</code></pre>\n<h3>OCaml</h3>\n<pre><code class=\"language-ocaml\">match color with\n Red -> "#FF0000"\n | Green -> "#00FF00"\n | Blue -> "#0000FF"\n | Rgb {r; g; b} -> Printf.sprintf "#%02X%02X%02X" r g b;;\n</code></pre>\n<h3>ReasonML</h3>\n<pre><code class=\"language-ocaml\">switch (color) {\n | Red => "#FF000"\n | Green => "#00FF00"\n | Blue => "#0000FF"\n | Rgb{r, g, b} => Format.sprintf("#%02X%02X%02X", r, g, b)\n };\n</code></pre>\n<h3>Kotlin</h3>\n<pre><code class=\"language-kotlin\">when ( color ) {\n Color.Red -> "#FF0000"\n Color.Green -> "#00FF00"\n Color.Blue -> "#0000FF"\n is Color.Rgb -> "#%02X%02X%02X".format(color.r, color.g, color.b)\n}\n</code></pre>\n<h3>Scala</h3>\n<pre><code class=\"language-scala\">color match {\n case Red =>"#FF0000"\n case Green =>"#00FF00"\n case Blue =>"#0000FF"\n case Rgb(r, g, b) => "#%02X%02X%02X".format(r, g, b)\n}\n</code></pre>\n<h3>Dotty</h3>\n<pre><code class=\"language-scala\">color match {\n case Color.Red => "#FF0000"\n case Color.Green => "#00FF00"\n case Color.Blue => "#0000FF"\n case Color.Rgb(r, g, b) => "#%02X%02X%02X".format(r, g, b)\n}\n</code></pre>\n<h3>TypeScript</h3>\n<pre><code class=\"language-typescript\">switch (color.kind) {\n case ColorKind.Red: return "#FF0000";\n case ColorKind.Green: return "#00FF00";\n case ColorKind.Blue: return "#0000FF";\n case ColorKind.Rgb: return "#" + [color.r, color.g, color.b].map((v) => v.toString(16)).join('').toUpperCase();\n default:\n const _exhaustiveCheck: never = color;\n return _exhaustiveCheck;\n}\n</code></pre>\n<h3>Ruby</h3>\n<pre><code class=\"language-ruby\">case color\nwhen Color::Red; "#FF000"\nwhen Color::Green; "#00FF00"\nwhen Color::Blue; "#0000FF"\nwhen Color::Rgb; "#%02X%02X%02X" % [color.r, color.g, color.b]\nend\n</code></pre>\n<h2>感想</h2>\n<h3>符号</h3>\n<ol>\n<li>Koka, Rust, Elm, PureScript, Haskell: 非常好</li>\n<li>OCaml, Kotlin: 好</li>\n<li>Haxe, Scala, Dotty, Ruby: 一般</li>\n<li>TypeScript, Swift: 很难写</li>\n</ol>\n<h2>编译器的默认行为</h2>\n<ol>\n<li>Rust,Haxe,Swift,Kotlin,Elm,PureScript:如果未覆盖该模式,则会出现编译错误</li>\n<li>Scala,Dotty,OCaml,ReasonML:即使不包含模式,编译也会在没有警告的情况下传递</li>\n<li>Koka,Haskell:即使不包含模式,编译也会在没有警告的情况下通过</li>\n<li>Ruby:外部</li>\n</ol>\n<p>来自: https://qiita.com/xmeta/items/91dfb24fa87c3a9f5993</p>\n","author":"lanqy"},{"title":"用 Javascript 编写一个编译器","created":"2018/05/21","link":"2018/05/21/make-a-compiler-with-javascript","description":"用 Javascript 编写一个编译器","content":"<h1>用 Javascript 编写一个编译器</h1>\n<p>译自: https://medium.com/@kosamari/how-to-be-a-compiler-make-a-compiler-with-javascript-4a8a13d473b4#.evrubxdub</p>\n<p>在布鲁克林 布什威克的一个美好的星期天,我在本地书店发现了一本John Maeda写的“数字设计”的书,这本书里一步一步的教DBN编程语言--一种90年代末在麻省理工学院媒体实验室诞生的语言,旨在以可视化的方式介绍计算机编程概念。\n<img src=\"/images/dbn.png\" /></p>\n<p>DNB 代码示例 来自 http://dbn.media.mit.edu/introduction.html</p>\n<p>我迫切地想让SVG来实现DBN并运行它在浏览器在2016年将是一个有趣的项目,而不是安装Java环境执行原始DBN源代码。</p>\n<p>我想我需要些一个DBN到SVG的编译器,所以编写一个编译器的任务已经开始。 “写一个编译器”听起来像很多计算机科学...但我从来没有接触过这方面,我可以写一个编译器吗?</p>\n<h3>让我们先试着做一个编译器</h3>\n<p>编译器是一种采用一段代码并将其转换为其他代码的机制。 让我们将简单的DBN代码编译成一个物理图。</p>\n<p>在这个DBN代码中有3个命令,“Paper”定义纸张的颜色,“Pen”定义笔的颜色,“Line”画一条线。 颜色参数100表示CSS中的100%黑色或rgb(0%,0%,0%)。 在DBN中产生的图像总是灰度的。 在DBN中,纸张始终为100×100,线宽始终为1,并且线由起点的x y坐标和从左下角计数的终止点定义。</p>\n<p>让我们试着成为一个编译器自己。 停在这里,抓住一张纸和一支钢笔,尝试编译以下代码作为绘图。</p>\n<pre><code class=\"language-js\">Paper 0\nPen 100\nLine 0 50 100 50\n</code></pre>\n<p>你在中间从左侧到右侧画了一条黑线吗? 恭喜! 你刚刚实现了一个编译器。</p>\n<img src=\"/images/1-aDJskliFHSIIfYhr8aN3UA.png\" />\n<p style=\"color:gray; font-size: 80%; text-align: center;\">编译结果</p>\n<h3>编译器是怎样工作的?</h3>\n<p>让我们来看看刚才在编译器中发生了什么。</p>\n<h4>1.词汇分析(标记化)</h4>\n<p>我们做的第一件事是用空格分隔每个关键字(称为tokens)。 当我们分离单词时,我们还为每个标记分配了原始类型,如“word”或“number”。</p>\n<img src=\"/images/1-lM4hjuI28Dodn-DfnXQu4A.png\" />\n<h4>2.解析(语法分析)</h4>\n<p>一旦一个文本块被分成tokens,我们就经历了每一个文本,并试图找到tokens之间的关系。\n在这种情况下,我们将与command关键字相关联的数字分组在一起。 通过这样做,我们开始看到代码的结构。</p>\n<img src=\"/images/1-Masaunh04PyclWIGhztHmg.png\" />\n<h4>3.转换</h4>\n<p>一旦我们通过解析分析语法,我们将结构转换为适合最终结果的结构。 在这个案例中,我们将绘制一个图像,所以我们将把它一步一步转换为人类的语言。</p>\n<img src=\"/images/1-ExV6vUNKZ4-IpG15-CAeFw.png\" alt=\"\" />\n<h4>4.代码生成</h4>\n<p>最后,我们制作一个编译结果,一个绘图。 在这一点上,我们只是按照我们在上一步中绘制的指令。</p>\n<img src=\"/images/1-250m-6zI6slTBirOxHX7kw.png\" alt=\"\" />\n<p>这是编译器做的!</p>\n<p>我们做的图是编译结果(当你编译C代码时像.exe文件)。 我们可以把这张图纸传递给任何人或任何设备(扫描仪,相机等)来“运行”,每个人(或设备)都会在中间看到一条黑线。</p>\n<h3>让我们写一个编译器</h3>\n<p>现在我们知道编译器如何工作,让我们在JavaScript中做一个。 此编译器使用DBN代码并将其转换为SVG代码。</p>\n<h4>1.Lexer方法</h4>\n<p>正如我们可以将英语句子“I have a pen”分割为[I,have,a,pen],词法分析器将一个代码字符串拆分成小的有意义的块(token)。 在DBN中,每个token由空格分隔,并分类为“word”或“number”。</p>\n<pre><code class=\"language-js\">function lexer (code) {\n return code.split(/\\s+/)\n .filter(function (t) { return t.length > 0 })\n .map(function (t) {\n return isNaN(t)\n ? {type: 'word', value: t}\n : {type: 'number', value: t}\n })\n}\n</code></pre>\n<pre><code class=\"language-js\">输入: "Paper 100"\n输出:[\n { type: "word", value: "Paper" }, { type: "number", value: 100 }\n]\n</code></pre>\n<h4>2.Parser方法</h4>\n<p>解析器通过每个标记,找到语法信息,并构建一个称为AST(抽象语法树)的对象。 你可以把AST看作我们代码的映射 - 一种理解一段代码结构的方法。\n在我们的代码中,有2种语法类型“NumberLiteral”和“CallExpression”。 NumberLiteral表示该值是一个数字。 它用作CallExpression的参数。</p>\n<pre><code class=\"language-js\">function parser (tokens) {\n var AST = {\n type: 'Drawing',\n body: []\n }\n // extract a token at a time as current_token. Loop until we are out of tokens.\n while (tokens.length > 0){\n var current_token = tokens.shift()\n\n // Since number token does not do anything by it self, we only analyze syntax when we find a word.\n if (current_token.type === 'word') {\n switch (current_token.value) {\n case 'Paper' :\n var expression = {\n type: 'CallExpression',\n name: 'Paper',\n arguments: []\n }\n // if current token is CallExpression of type Paper, next token should be color argument\n var argument = tokens.shift()\n if(argument.type === 'number') {\n expression.arguments.push({ // add argument information to expression object\n type: 'NumberLiteral',\n value: argument.value\n })\n AST.body.push(expression) // push the expression object to body of our AST\n } else {\n throw 'Paper command must be followed by a number.'\n }\n break\n case 'Pen' :\n ...\n case 'Line':\n ...\n }\n }\n }\n return AST\n}\n</code></pre>\n<pre><code class=\"language-js\">输入: [\n { type: "word", value: "Paper" }, { type: "number", value: 100 }\n]\n输出: {\n "type": "Drawing",\n "body": [{\n "type": "CallExpression",\n "name": "Paper",\n "arguments": [{ "type": "NumberLiteral", "value": "100" }]\n }]\n}\n</code></pre>\n<h4>3. Transformer 方法</h4>\n<p>我们在前面的步骤中创建的AST很好地描述代码中发生了什么,但是它没有用于创建SVG文件。\n例如。 “纸张”是一个只存在于DBN范例中的概念。 在SVG中,我们可以使用<rect>元素来表示一个Paper。 变换函数将AST转换为另一个支持SVG的AST。</p>\n<pre><code class=\"language-js\">function transformer (ast) {\n var svg_ast = {\n tag : 'svg',\n attr: {\n width: 100, height: 100, viewBox: '0 0 100 100',\n xmlns: 'http://www.w3.org/2000/svg', version: '1.1'\n },\n body:[]\n }\n\n var pen_color = 100 // default pen color is black\n\n // Extract a call expression at a time as `node`. Loop until we are out of expressions in body.\n while (ast.body.length > 0) {\n var node = ast.body.shift()\n switch (node.name) {\n case 'Paper' :\n var paper_color = 100 - node.arguments[0].value\n svg_ast.body.push({ // add rect element information to svg_ast's body\n tag : 'rect',\n attr : {\n x: 0, y: 0,\n width: 100, height:100,\n fill: 'rgb(' + paper_color + '%,' + paper_color + '%,' + paper_color + '%)'\n }\n })\n break\n case 'Pen':\n pen_color = 100 - node.arguments[0].value // keep current pen color in `pen_color` variable\n break\n case 'Line':\n ...\n }\n }\n return svg_ast\n }\n</code></pre>\n<pre><code class=\"language-js\">输入: {\n "type": "Drawing",\n "body": [{\n "type": "CallExpression",\n "name": "Paper",\n "arguments": [{ "type": "NumberLiteral", "value": "100" }]\n }]\n}\n输出: {\n "tag": "svg",\n "attr": {\n "width": 100,\n "height": 100,\n "viewBox": "0 0 100 100",\n "xmlns": "http://www.w3.org/2000/svg",\n "version": "1.1"\n },\n "body": [{\n "tag": "rect",\n "attr": {\n "x": 0,\n "y": 0,\n "width": 100,\n "height": 100,\n "fill": "rgb(0%, 0%, 0%)"\n }\n }]\n}\n</code></pre>\n<h4>4. Generator 函数</h4>\n<p>作为这个编译器的最后一步,generator函数创建基于我们在上一步中创建的新AST的SVG代码。</p>\n<pre><code class=\"language-js\">function generator (svg_ast) {\n\n // create attributes string out of attr object\n // { "width": 100, "height": 100 } becomes 'width="100" height="100"'\n function createAttrString (attr) {\n return Object.keys(attr).map(function (key){\n return key + '="' + attr[key] + '"'\n }).join(' ')\n }\n\n // top node is always <svg>. Create attributes string for svg tag\n var svg_attr = createAttrString(svg_ast.attr)\n\n // for each elements in the body of svg_ast, generate svg tag\n var elements = svg_ast.body.map(function (node) {\n return '<' + node.tag + ' ' + createAttrString(node.attr) + '></' + node.tag + '>'\n }).join('\\n\\t')\n\n // wrap with open and close svg tag to complete SVG code\n return '<svg '+ svg_attr +'>\\n' + elements + '\\n</svg>'\n}\n</code></pre>\n<pre><code class=\"language-js\">输入: {\n "tag": "svg",\n "attr": {\n "width": 100,\n "height": 100,\n "viewBox": "0 0 100 100",\n "xmlns": "http://www.w3.org/2000/svg",\n "version": "1.1"\n },\n "body": [{\n "tag": "rect",\n "attr": {\n "x": 0,\n "y": 0,\n "width": 100,\n "height": 100,\n "fill": "rgb(0%, 0%, 0%)"\n }\n }]\n}\n输出:\n<svg width="100" height="100" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">\n <rect x="0" y="0" width="100" height="100" fill="rgb(0%, 0%, 0%)">\n </rect>\n</svg>\n</code></pre>\n<h4>5.整合所有代码在一起</h4>\n<p>让我们称这个编译器为“sbn编译器”(SVG由数字编译器)。\n我们使用词法分析器,解析器,变换器和生成器方法创建一个sbn对象。 然后添加一个“编译”方法来调用链中的所有4个方法。\n我们现在可以将代码字符串传递给编译方法并获取SVG。</p>\n<pre><code class=\"language-js\">var sbn = {}\nsbn.VERSION = '0.0.1'\nsbn.lexer = lexer\nsbn.parser = parser\nsbn.transformer = transformer\nsbn.generator = generator\n\nsbn.compile = function (code) {\n return this.generator(this.transformer(this.parser(this.lexer(code))))\n}\n\n// call sbn compiler\nvar code = 'Paper 0 Pen 100 Line 0 50 100 50'\nvar svg = sbn.compile(code)\ndocument.body.innerHTML = svg\n</code></pre>\n<p>我做了一个<a href=\"https://kosamari.github.io/sbn/\">交互式演示</a>,显示此编译器中的每个步骤的结果。 sbn编译器的代码发布在<a href=\"https://github.com/kosamari/sbn\">github</a>上。 我现在在编译器中添加更多的功能。 如果你想看我们在这篇文章中做的基本编译器,请查看<a href=\"https://github.com/kosamari/sbn/tree/simple\">简单的分支</a>。</p>\n<img src=\"/images/1-7ADpMcLo1VOnW4-fF2vjDg.png\" />\nhttps://kosamari.github.io/sbn/\n<h3>编译器应不应该使用递归和遍历等?</h3>\n<p>是的,这些都是精彩的技术来构建一个编译器,但这并不意味着你必须优先采取这种方法。</p>\n<p>我开始为DBN编程语言的一个小子集编译器,一个非常有限的小功能集。 从那时起,我扩展了范围,现在规划添加功能,如变量,代码块和循环到这个编译器。 在这一点上使用这些技术是一个好主意,但这不是开始的要求。</p>\n<h3>写编译器是很棒的一件事</h3>\n<p>你可以通过自己的编译器做什么? 也许你可能想在西班牙语中制作新的类似JavaScript的语言...español脚本如何?</p>\n<pre><code class=\"language-js\">// ES (español script)\nfunción () {\n si (verdadero) {\n return «¡Hola!»\n }\n}\n</code></pre>\n<p>任何事情都是有可能的,这里有人使用<a href=\"http://www.emojicode.org/\">Emoji(表情符号代码)</a>和<a href=\"http://www.dangermouse.net/esoteric/piet.html\">彩色图像(Piet编程语言)</a>开发变成语言。</p>\n<h2>学习编写编译器中学到了什么?</h2>\n<p>编译器很有趣,但最重要的是,它教会了我很多关于软件开发。 这里有几个我在学习编译器时学到的东西。\n<img src=\"/images/1-AREFc7UVIAu_YIgk46EwaA.png\" /></p>\n<p>我怎么想象自己做一个编译器</p>\n<h4>1.不熟悉的东西没关系</h4>\n<p>就像我们的词法分析程序,你不需要一开始就知道它。如果你不真正理解一段代码或技术,可以直接说“这我知道”,并且继续下一个步骤。不要有压力,最终你会知道。</p>\n<h4>2.尽量友好提示错误信息</h4>\n<p>解析器的作用是遵循规则并检查是否按照这些规则输入内容。 所以很多时候错误发生时,尝试发送有用的消息和提示语。 很容易说“它不工作的方式”(像“ILLEGAL Token”或“undefined is not a function” 的错误JavaScript),但相反,尽可能地告诉用户发生了什么。</p>\n<p>这也适用于团队沟通。 当有人被困在一个问题,而不是说“是的,不工作”,也许你可以开始说“我会google关键字,如___和___。”或“我建议阅读这个页面上的文档” 需要为他们做工作,但你当然可以帮助他们通过提供一点点帮助更好更快地完成工作。</p>\n<p>Elm是一个拥抱<a href=\"http://elm-lang.org/blog/compiler-errors-for-humans\">这种方法</a>的编程语言。 他们在他们的错误信息中写了“也许你想试试这个?</p>\n<h4>3.上下文是一切</h4>\n<p>最后,就像我们的变换器将一种类型的AST转换为另一种更适合的最终结果,一切都是上下文特定的。</p>\n<p>没有一个完美的方式来做事情。 不要因为这个事情它是流行的或你以前做过才去做,先考虑上下文。 对一个用户工作的事情可能是另一个用户的灾难。</p>\n<p>此外,欣赏这些转换器做的工作。 你可能知道你的团队中的转换器 - 一个真正善于弥合差距的人。 转换器的这些工作可能不直接创造代码,但它是生产优质产品的一个重要的工作。</p>\n<p>希望你喜欢这篇文章,也希望我说服你开发一个编译器是一件很棒的事情!</p>\n<p>这是我在哥伦比亚的JSConf哥伦比亚2016年在哥伦比亚麦德林举行的演讲的摘录。 如果你想了解该演讲,请在这里<a href=\"http://kosamari.com/presentation/jsconfcolombia-2016/#0\">查看幻灯片</a>。</p>\n","author":"lanqy"},{"title":"Linux 下重新启动 Nginx web 服务器","created":"2018/05/21","link":"2018/05/21/nginx-linux-restart","description":"Linux 下重新启动 Nginx web 服务器","content":"<h1>Linux 下重新启动 Nginx web 服务器</h1>\n<p>译自 https://www.cyberciti.biz/faq/nginx-linux-restart/</p>\n<p>如何使用命令行选项在 Linux 或 Unix 操作系统下重新启动 nginx Web 服务器? 要重新启动 nginx Web 服务器,请以 root 用户身份使用以下任一命令。打开终端或使用 ssh 登录到远程服务器。</p>\n<h3>Debian / Ubuntu / RHEL / CentOS Linux</h3>\n<p>使用以下命令:</p>\n<pre><code class=\"language-nginx\">/etc/init.d/nginx restart\n</code></pre>\n<p>或者</p>\n<pre><code class=\"language-nginx\">/etc/init.d/nginx reload\n</code></pre>\n<p>或者</p>\n<pre><code class=\"language-nginx\">servive nginx restart\n</code></pre>\n<p>或者</p>\n<pre><code class=\"language-nginx\">service nginx reload\n</code></pre>\n<p>或者如果您使用基于 systemd 的 Linux 发行版:</p>\n<pre><code class=\"language-nginx\">sudo systemctl restart nginx\n</code></pre>\n<p>或者</p>\n<pre><code class=\"language-nginx\">sudo systemctl reload nginx\n</code></pre>\n<p>查看状态</p>\n<pre><code class=\"language-nginx\">service nginx status\n</code></pre>\n<p>或者</p>\n<pre><code class=\"language-nginx\">sudo systemctl status nginx\n</code></pre>\n<p>但是,推荐方法如下。这应该适用于任何 Linux 发行版或类 Unix 操作系统:</p>\n<pre><code class=\"language-nginx\">nginx -s reload\n</code></pre>\n<p>或者</p>\n<pre><code class=\"language-nginx\">/path/to/full/nginx -s reload\n</code></pre>\n<h4>如果 nginx 编译并从源代码安装</h4>\n<p>如果 nginx 二进制文件安装在 /usr/local/nginx/sbin/nginx 中,请输入:</p>\n<pre><code class=\"language-nginx\">/usr/local/nginx/sbin/nginx -s reload\n</code></pre>\n","author":"lanqy"},{"title":"一步一步教你创建 react redux 应用","created":"2018/05/21","link":"2018/05/21/step-by-step-react-redux","description":"一步一步教你创建 react redux 应用","content":"<h1>一步一步教你创建 react redux 应用</h1>\n<p>Redux 已经成为构建React应用的标配,有大量的实例展示它是怎样做到的,但是react-Redux 应用有很多部分,例如:“Reducers”, “Actions”, “Action Creators”, “State”, “Middleware” 等等),这些都具有颠覆性的。</p>\n<p>当我开始学习它的时候,我找不到任何资料说明“构建react-Redux应用的时候,应该先构建哪个部分”或者一般怎样构建react-redux应用,因此我通过几个实例和文章来教你怎样一步步构建react-redux应用。\n<br/></p>\n<p>注:我通过“模拟”以保持较高水平和不至于太杂乱。我将使用经典的<a href=\"https://github.com/reactjs/redux/tree/master/examples/todos\">Todo list app</a>作为基础来创建任何react应用。如果你的应用有多个(screens)界面,只需要重复这个步骤到你到界面中即可。</p>\n<h3>为什么要用Redux?</h3>\n<p>React—一个JS库,帮助我们将我们的应用拆分为多个组件,但是没有具体说明如何保持数据(<code>state</code>)的轨迹以及如何正常处理我们的所有事件(<code>Actions</code>)。</p>\n<p>Redux—一个很受欢迎的库,为react提供一个可以轻松地保持数据(<code>state</code>)和事件(<code>actions</code>)。</p>\n<blockquote>\n<p>实际上,Redux允许我们以我们的方式创建React应用,然后委托所有的状态(state)和事件(actions)到Redux上。\n顺便说一个.一个简单到Todo应用,有八个步骤。理论上,早期的框架构建Todo应用很简单,但是做真实的应用比较难(这里指的是维护)。而React Redux做Todo应用就显得比较繁琐,但是真实的应用就比较方便(这里指的是维护)</p>\n</blockquote>\n<p>让我们开始吧!</p>\n<h3>第一步-写一个详细的模拟</h3>\n<p>这个模拟应该包含所有的数据和视觉效果(例如TodoItem的删除线,一个“All”的过滤器),</p>\n<p>第一步:模拟每个界面,</p>\n<p>怎样模拟:</p>\n<p>1、越详细越好,</p>\n<p>2、画出所有的数据和视觉效果(例如item删除线)。</p>\n<p>如下图:</p>\n<img src=\"/images/1-RtA4NF2PI__vcarQgXEEBg.png\" />\n<h3>把应用划分成为组件</h3>\n<p>尝试将应用划分为基于每个组件的用途的组件块。</p>\n<p>我们有三个组件“<code>AddTodo</code>”, “<code>TodoList</code>” and “<code>Filter</code>”组件。</p>\n<p>第二步:划分应用成为组件:</p>\n<p>怎样划分:基于它们的用途来划分。</p>\n<p>我们的应用有三个主要的“目的”:</p>\n<p>1、添加一个新的Todo 项目(AddTodo组件)</p>\n<p>2、展示Todo列表(TodoList组件)</p>\n<p>3、过滤展示Todos(Filter组件)</p>\n<p>如下图:</p>\n<img src=\"/images/1-AfzFa8zO_dQOmuSHL7bEww.png\" />\n<h3>Redux术语:“<code>Actions</code>”和“<code>States</code>”</h3>\n<h5>每个组件做这两件事情:</h5>\n<p>1、根据一些数据渲染DOM,这些数据,我们称之为状态(<code>state</code>)。</p>\n<p>2、监听用户和事件并发送到一个JS方法,我们称之为操作(<code>actions</code>)。</p>\n<h3>第三步-列出每个组件的<code>States</code>和<code>Actions</code></h3>\n<p>我们通过第二步来仔细看看每一个组件,并列出它们每个的<code>States</code>和<code>Actions</code>。</p>\n<p>我们有三个组件,分别为“<code>AddTodo</code>”, “<code>TodoList</code>” and “<code>Filter</code>”组件,让我们列出它们每个的<code>States</code>和<code>Actions</code>。</p>\n<ul>\n<li>3.1 AddTodo 组件的<code>State</code> 和 <code>Actions</code></li>\n</ul>\n<p>这个部分,我们没有任何状态(<code>state</code>),这个组件不依赖任何数据或状态来改变外观,但是它需要让其它组件知道,当用户创建一个新的Todo的时候。让我们叫这个action为<code>ADD_TODO</code></p>\n<p>AddTodo组件\nState:\n1.(一个简单的输入框和一个按钮,不依赖于任何数据来展示)\nActions(events):\n1.AddTodo组件允许我们创建一个新的Todo项目通过监听DOM事件和从输入框获取数据。这个事件映射到JSON对象,我们称之为Action。</p>\n<p>在这个案例中,我们可以通过创建一个JSON对象来描述我们的AddTodo action,JSON如下:</p>\n<pre><code class=\"language-javascript\"> {\n type:'ADD_TODO'\n payload:{\n data:'Learn Redux',\n id:1,\n completed:false\n }\n }\n\n</code></pre>\n<p>如下图:</p>\n<img src=\"/images/1-OrfmEw_gPw5kQ3ZO5AjzEQ.png\" />\n<ul>\n<li>3.2 TodoList 组件的<code>State</code> 和 <code>Actions</code></li>\n</ul>\n<p>TodoList组件需要一个数组来展示数据,因此它需要一个状态(<code>state</code>),我们称之为Todos(数组)。它也需要知道哪一个过滤器被触发,以相应的显示或隐藏Todo 项目,因此还需要一个状态(<code>state</code>),让我们称之为<code>VisibilityFilter</code>(布尔值)。</p>\n<p>进一步,它允许我们切换Todo项目为已完成或未完成状态。我们也需要让其它组件知道切换这个状态。我们把这个action叫做“<code>TOGGLE_TODO</code>”</p>\n<p>TodoList组件:</p>\n<p>State:</p>\n<p>1、Todos数组</p>\n<p>2、Visibility filter(显示过滤器),这个告诉那种类型的Todo项目需要显示或者隐藏。注:这个来自于“Filter”组件。</p>\n<p>Actions(events):</p>\n<p>TodoList组件只有一个action,它允许用户切换Todo项目的完成状态,当用户点击到每个Todo项目时,它需要告诉其它到组件和数据库等切换Todo的状态。</p>\n<p>我们需要通过JSON对象来描述我们的ToggleTodo action,JSON如下:</p>\n<p><code>{ type:"TOGGLE_TODO" payload:{ id:<todoItem's id > } }</code></p>\n<p>如下图:</p>\n<img src=\"/images/1-waJ9BucAT9qW_sHcqNOB_Q.png\" />\n<ul>\n<li>3.3 Filter 组件的<code>State</code> 和 <code>Actions</code></li>\n</ul>\n<p>Filter组件可以是一个链接或者一个简单的文本来渲染,但它依赖于是否激活,我们把这个状态(<code>state</code>)称之为“<code>CurrentFilter</code>”。</p>\n<p>Filter组件依然需要告诉其它组件用户什么时候点击它,我们把这个操作(<code>action</code>)称之为“<code>SET_VIBILITY_FILTER</code>”。</p>\n<p>Filter组件:</p>\n<p>State(data):</p>\n<p>1.CurrentFilter-一个字符串,拥有告诉哪个过滤器需要显示,哪个链接可点击,例如“Active”和“completed”</p>\n<p>Actions(events):</p>\n<p>Filter组件也只有一个操作(action),它只是监听用户点击过滤器点链接并告诉其它组件哪个链接已经被点击。</p>\n<p>我们通过一个JSON对象来描述<code>SET_VIBILITY_FILTER</code>操作(action),JSON如下:</p>\n<p>{\ntype:"SET_VIBILITY_FILTER"\npayload:{\nfilter:"Completed"\n}\n}</p>\n<p>如下图:</p>\n<img src=\"/images/1-3G9ycPVPovZPu35rGW3FLw.png\" />\n<h3>Redux术语“<code>Action Creators</code>”</h3>\n<p><code>Action Creators</code>是一些简单的方法用于从DOM事件中接受数据,格式化为标准的JSON“<code>Action</code>”。对象并返回这个对象(又称<code>Action</code>),这个帮助我们正规化我们的数据的样子。</p>\n<p>进一步,它允许未来任何组件发送到这些Actions到其它组件去(又成为<code>dispatch</code>[调度])。</p>\n<h3>第四步,为每个<code>Action</code>创建<code>Action Creators</code></h3>\n<p>我们统计一共有三个<code>Action</code>:<code>ADD_TODO, TOGGLE_TODO and SET_VISIBILITY_FILTER</code>。让我们为它们每个创建<code>Action</code>:</p>\n<pre><code class=\"language-javascript\">//1. 从AddTodo文本域获取文本并返回正确的“Action” JSON对象发送到其它到组件中。(Takes the text from AddTodo field and returns proper “Action” JSON to send to other components.)\nexport const addTodo = (text) => {\n return {\n type: ‘ADD_TODO’,\n id: nextTodoId++,\n text, //<--ES6. same as text:text, in ES5(这个是ES6的写法,相当于ES5的text:text)\n completed: false //<-- initially this is set to false (completed字段初始化为false)\n }\n}\n\n//2. 获取filter 字符串并返回正确的“Action” JSON对象发送到其它组件中。(Takes filter string and returns proper “Action” JSON object to send to other components.)\nexport const setVisibilityFilter = (filter) => {\n return {\n type: ‘SET_VISIBILITY_FILTER’,\n filter\n }\n}\n\n//3. 获取Todo 项目的id并返回正确的“Action” JSON对象发送到其它组件中。(Takes Todo item’s id and returns proper “Action” JSON object to send to other components.)\nexport const toggleTodo = (id) => {\n return {\n type: ‘TOGGLE_TODO’,\n id\n }\n}\n</code></pre>\n<h3>Redux术语“<code>Reducers</code>”</h3>\n<p><code>Reducers</code>是一些方法,用于从<code>redux</code>和“<code>action</code>” JSON对象中获取状态(<code>state</code>)并返回新的状态(<code>state</code>)存回Redux中。</p>\n<p>1、当用户操作时,Reducers方法会被“<code>Container</code>”触发[这句好难翻啊,估计翻不对](Reducer functions are called by the “Container” containers when there is a user action)。</p>\n<p>2、如果<code>reducer```改变了状态(</code>state`),Redux传递新的状态(new state)到每个组件并且React重新渲染每个组件。</p>\n<pre><code class=\"language-javascript\">//(For example the below function takes Redux’ state(an array of previous todos), and returns a **new** array of todos(new state) w/ the new Todo added if action’s type is “ADD_TODO”.)\n// 例如下面这个方法获取Redux 状态(一个之前的todos数组),并返回一个新的todos数组(new state),当action's的类型为“ADD_TODO”时,一个新的Todo就会被添加\nconst todo = (state = [], action) => {\n switch (action.type) {\n case ‘ADD_TODO’:\n return\n […state,{id: action.id, text: action.text, completed:false}];\n }\n</code></pre>\n<h3>第五步,为每个<code>Action</code>写<code>Reducers</code></h3>\n<p>注:为简洁起见,一些代码已经被简化了,另外我简单当展示一下<code>SET_VISIBILITY_FILTER</code>、<code>ADD_TODO</code> 和 <code>TOGGLE_TODO</code> ,代码如下:</p>\n<pre><code class=\"language-javascript\">const todo = (state, action) => {\n switch (action.type) {\n case ‘ADD_TODO’:\n return […state,{id: action.id, text: action.text,\n completed:false}]\n\n case ‘TOGGLE_TODO’:\n return state.map(todo =>\n if (todo.id !== action.id) {\n return todo\n }\n return Object.assign({},\n todo, {completed: !todo.completed})\n )\n\n case ‘SET_VISIBILITY_FILTER’: {\n return action.filter\n }\n\n default:\n return state\n }\n}\n</code></pre>\n<h3>Redux术语“<code>Presentational</code>”和“<code>Container</code>”组件</h3>\n<p>把React和Redux的逻辑放在每个组件里是很混乱的,因此Redux建议创建一个假的仅仅包含组件的展示型组件和一个父级包装组件就做容器型组件负责Redux的<code>Actions</code>调度等等。</p>\n<p>然后父级容器将数据传递到展示型组件,事件处理替展示型组件处理React,如下图:</p>\n<img src=\"/images/1-inU9OmAFSDYKFm8pstsCDw.png\" />\n<p>说明:黄色点线代表展示型组件,黑色点线表示容器型组件。</p>\n<p>###第六步-实现所有的展示型组件</p>\n<p>现在是时候实现我们所有的(3个)展示型组件了</p>\n<p>6.1 实现AddTodoForm组件</p>\n<pre><code>AddTodoForm组件:\n这个组件渲染一个输入框和一个按钮,它接收一个onSubmit回调方法(来自父级容器组件)当用户提交一个新的Todo时,它发送和传递todo文本到onSubmit方法中\n</code></pre>\n<pre><code class=\"language-javascript\">let AddTodoForm = ({onSubmit}) => {\n let input\n return(\n <div>\n <form onSubmit={e=>{onSubmit(input.value)}}>\n <input ref={node = >{input = node}} />\n <button type="submit">Add Todo</button>\n </form>\n </div>\n )\n}\nexport default AddTodoForm\n</code></pre>\n<p>如下图:</p>\n<img src=\"/images/1-WlASUkXRWSGaFZdZJXXiRQ.png\" />\n<p>6.2 实现TodoList组件</p>\n<p>TodoList组件:</p>\n<p>这个组件渲染一个Todo列表,它接受<code>Todos</code>数组和一个<code>onTodoClick</code>回调方法(来自父级容器组件)。当用户点击每个Todo项目时,它发送Todo项</p>\n<p>目的id到<code>onTodoClick</code>方法中并切换这个被点击的项目的状态。</p>\n<pre><code class=\"language-javascript\">const TodoList = ({todos,onTodoClick})=>{\nif(todos.length === 0)\n return <div>Add Todos</div>\nreturn <ul>\n{todos.map(todo=>\n <Todo key={todo.id}{...todo} onClick={()=>onTodoClick(todo.id)} />\n)}\n</ul>\n}\n\nexport default TodoList\n</code></pre>\n<p>如下图:</p>\n<img src=\"/images/1-u1CX5abgafgbt3x-IgJ36A.png\" />\n<p>6.2 实现Link组件</p>\n<p>Link组件:</p>\n<p>这个组件渲染个别链接,分别有三个链接,它接收“active”布尔值并渲染一个文本或者链接,它接收children属性来显示链接的名称,它也接收一</p>\n<p>个onClick回调方法,当链接被点击的时候会被调用。</p>\n<pre><code class=\"language-javascript\">const Link = ({active,children,onClick}) = >{\n if(active){\n return <span>{children}</span>\n }\n return <a href="#" onClick={e =>{onClick()}}>{children}</a>\n}\nexport default Link\n</code></pre>\n<p>如下图:\n<img src=\"/images/1-5WQbEnAhRP6fmfiCjOeS4A.png\" /></p>\n<p>注意:在实际的代码中,Link展示组件被包含在<code>FilterLink</code>容器组件中,进而3个FilterLink组件显示在Footer展示组件中。</p>\n<h3>第七步-为部分或者全部展示组件创建容器组件</h3>\n<p>最后,为每个组件创建Redux链接</p>\n<p>7.1创建容器组件(AddTodo组件)</p>\n<p><a href=\"https://github.com/rajaraodv/redux/blob/master/examples/todos/containers/AddTodo.js\">最后实际的代码在这里</a></p>\n<img src=\"/images/1-ElBMxdAUzuVJ343uAlCVmA.png\" />\n<p>7.2创建容器组件(TodoList组件)</p>\n<p><a href=\"https://github.com/rajaraodv/redux/blob/master/examples/todos/containers/VisibleTodoList.js\">最后实际的代码在这里</a></p>\n<img src=\"/images/1-malT38rul36L0Ygbt1JjzA.png\" />\n<p>7.3创建容器组件(Filter组件)</p>\n<p><a href=\"https://github.com/rajaraodv/redux/blob/master/examples/todos/containers/FilterLink.js\">最后实际的代码在这里</a></p>\n<img src=\"/images/1-1Kgo8pIxbLAkuBho6aQQeQ.png\" />\n<p>注意:在实际的代码中,Link展示组件被包含在<code>FilterLink</code>容器组件中,进而3个FilterLink组件排列和显示在Footer展示组件中。</p>\n<p>###第八步-把它们放在一起</p>\n<pre><code class=\"language-javascript\">import React from ‘react’ // ← Main React library\nimport { render } from ‘react-dom’ // ← Main react library\nimport { Provider } from ‘react-redux’ //← Bridge React and Redux\nimport { createStore } from ‘redux’ // ← Main Redux library\nimport todoApp from ‘./reducers’ // ← List of Reducers we created\n//Import all components we created earlier\nimport AddTodo from ‘../containers/AddTodo’\nimport VisibleTodoList from ‘../containers/VisibleTodoList’\nimport Footer from ‘./Footer’ // ← (这个是一个展示型组件,包含三个FilterLink容器组件)This is a presentational component that contains 3 FilterLink Container comp\n//Create Redux Store by passing it the reducers we created earlier.(创建Redux store树,传入reducers)\nlet store = createStore(reducers)\nrender(\n <Provider store={store}> //← (react-redux的提供组件,用于将store注入到所有到子组件中)The Provider component from react-redux injects the store to all the child components\n <div>\n <AddTodo />\n <VisibleTodoList />\n <Footer />\n </div>\n </Provider>,\n document.getElementById(‘root’) //<-- Render to a div w/ id "root"\n)\n</code></pre>\n<p>就是这样!</p>\n<p>注:本文翻译自<a href=\"https://medium.com/@rajaraodv/step-by-step-guide-to-building-react-redux-apps-using-mocks-48ca0f47f9a#.kljg6fuei\">Step by Step Guide To Building React Redux Apps</a>,有翻译不对的地方,欢迎指正,谢谢!</p>\n","author":"lanqy"},{"title":"理解 CSS 特异性的复杂性","created":"2018/05/21","link":"2018/05/21/understanding-the-complexity-of-css-specificity","description":"理解 CSS 特异性的复杂性","content":"<h1>理解 CSS 特异性的复杂性</h1>\n<p>译自 http://cssdude.com/css/css-specificity/</p>\n<p>CSS 特性是层叠样式表中最难理解的概念之一,并且通常应用于 CSS 开发的典型规则不适用于CSS特性。 为了正确编码并缩短所花的时间,您需要仔细考虑并理解您的代码将如何被浏览器解释。 为了确保您的代码可读,您需要完全理解 CSS 特性的工作原理。</p>\n<h4>什么是 CSS 特异性?</h4>\n<p>简单来说,特定性指的是浏览器应用哪些 CSS 规则。 没有正确理解 CSS 的特殊性会导致一些规则在浏览器中不被应用,即使你可能认为它们是正确的。 特定性在层次结构上运行。 这个层次由四个不同的层次组成:</p>\n<ul>\n<li>内联样式 - 内联样式是直接附加到正在设计样式的元素上的样式。 它们是在您的XHTML文档中创建的</li>\n<li>ID-这些 ID 是通常以 <code>#div</code> 表示的页面元素的标识符</li>\n<li>类+属性 - 类和属性使用:.classes 或 [attributes] 进行编码。 伪类通常包含在类和属性级别中</li>\n<li>元素 - 元素和伪元素将所有样式分配给上下文,它们是特定且独特的,允许您自发创建内容</li>\n</ul>\n<p>通常情况下,两个选择器在同一个元素上编写,但具有特殊性,这两个选择器都不适用。 元素呈现更高的特异性将是应用的元素,并将覆盖任何冲突规则或以前应用的规则。 如果这些选择器具有相同的特性值,则添加的最近一个是将应用的那个</p>\n<h4>了解CSS特定级别:</h4>\n<p>嵌入式样式表比其他大多数规则具有更大的特异性。然而,ID 选择器将比属性选择器具有更高的特异性,并且您应该始终致力于始终使用 ID 选择器来提高特异性。类选择器将始终应用于任何元素选择器。通用选择器将具有 0,0,0,0 的特异性</p>\n<p>您可以使用 CSS 特异性计算器来帮助您计算 CSS 特异性,或者您可以应用一些简单的规则来帮助您更好地理解 CSS 特异性。 为了衡量特异性,从 0 开始,为每个内联样式属性添加 1000,对于添加的每个ID属性 100 ,对于添加的每个类或伪类属性10以及添加的任何元素 1。</p>\n<p>您可以通过使用 ID 选择器来制定更具体的规则。当您发现自己遇到 CSS 特定性问题时,请避免使用 !important。这种编码只会覆盖正常的声明并且不会为样式表提供结构。相反,尝试限制包含的选择器或使规则更具体。</p>\n<p>一旦您了解了如何将特异性应用于您的编码,您可以更轻松地确保更好的设计。了解如何通过浏览器解释某些属性,元素和选择器将允许您创建更多功能和正确的设计。</p>\n<table class=\"table table-bordered\">\n<tbody>\n<tr>\n<td>1</td>\n<td>* { }</td>\n<td>0</td>\n</tr>\n<tr class=\"pdd\">\n<td>2</td>\n<td>li { }</td>\n<td>1 ( 一个元素 )</td>\n</tr>\n<tr>\n<td>3</td>\n<td>li:first-line { }</td>\n<td>2 ( 一个元素,一个伪类)</td>\n</tr>\n<tr class=\"pdd\">\n<td>4</td>\n<td>ul li { }</td>\n<td>2 ( 两个元素 )</td>\n</tr>\n<tr>\n<td>5</td>\n<td>ul ol+li { }</td>\n<td>3 ( 三个元素 )</td>\n</tr>\n<tr class=\"pdd\">\n<td>6</td>\n<td>h1 + *[rel=up] { }</td>\n<td>11 ( 一个属性,一个元素 )</td>\n</tr>\n<tr>\n<td>7</td>\n<td>ul ol li.red { }</td>\n<td>13 ( 一个类,三个元素 )</td>\n</tr>\n<tr class=\"pdd\">\n<td>8</td>\n<td>li.red.level { }</td>\n<td>21 ( 两个类,一个元素 )</td>\n</tr>\n<tr>\n<td>9</td>\n<td>style=””</td>\n<td>1000 ( 一个内联样式 )</td>\n</tr>\n<tr class=\"pdd\">\n<td>10</td>\n<td>p { }</td>\n<td>1 (一个 HTML 选择器)</td>\n</tr>\n<tr>\n<td>11</td>\n<td>div p { }</td>\n<td>2 ( 两个 HTML 选择器 )</td>\n</tr>\n<tr class=\"pdd\">\n<td>12</td>\n<td>.sith</td>\n<td>10 ( 一个类选择器 )</td>\n</tr>\n<tr>\n<td>13</td>\n<td>div p.sith { }</td>\n<td>12 ( 两个HTML选择器和一个类选择器 )</td>\n</tr>\n<tr class=\"pdd\">\n<td>14</td>\n<td>#sith</td>\n<td>100 ( 一个id选择器 )</td>\n</tr>\n<tr>\n<td>15</td>\n<td>body #darkside .sith p { }</td>\n<td>112 ( HTML选择器,ID选择器,类选择器,HTML选择器 ; 1+100+10+1 )</td>\n</tr>\n</tbody>\n</table>\n","author":"lanqy"},{"title":"React 生态系统入门第一部分(共三个部分)","created":"2016/07/14","link":"2016/07/14/a-primer-on-the-react-rcosystem-part1","description":"React 生态系统入门第一部分(共三个部分)","content":"<h1>React1 生态系统入门第一部分(共三个部分)</h1>\n<p>译自: http://patternhatch.com/2016/07/06/a-primer-on-the-react-ecosystem-part-1-of-3/</p>\n<ul>\n<li><a href=\"#user-content-update\">更新</a></li>\n<li><a href=\"#user-content-introduction\">介绍</a></li>\n<li><a href=\"#user-content-installment\">这一期中</a></li>\n<li><a href=\"#user-content-prerequisite\">先决条件</a></li>\n<li><a href=\"#user-content-code\">代码</a></li>\n<li><a href=\"#user-content-description\">项目描述</a></li>\n<li><a href=\"#user-content-creation\">创建项目</a></li>\n<li><a href=\"#user-content-webpack\">Webpack</a></li>\n<li><a href=\"#user-content-babel\">Babel</a></li>\n<li><a href=\"#user-content-hot\">热模块更换</a></li>\n<li><a href=\"#user-content-component\">第一个React组件</a></li>\n</ul>\n<h4 id=\"user-content-update\">更新</h4>\n<p>2016.07.06: 第一次发布</p>\n<h4 id=\"user-content-introduction\">介绍</h4>\n<p>过去8个月,我一直在工作中使用React生态系统,我们创建应用来监控我们的交易状态,编辑我们的交易风险检查和控制我们的战略。</p>\n<p>它证明了梦幻般的生态系统,鉴于其组合的性质和平易近人的学习曲线,React几乎已经成为提供一个消费级的企业级用户界面的有趣交付物。</p>\n<p>我想通过一个入门的教程 分享我的学习经验,通过这个入门你将学习到:</p>\n<ul>\n<li>配置开发React应用的环境</li>\n<li>创建React组件并让他们对数据变化做出反应</li>\n<li>通过Redux管理应用的状态</li>\n</ul>\n<p>如果你是第一次接触React生态系统,我希望通过这个入门教程,你将很轻松的开始创建你的React应用</p>\n<h4 id=\"user-content-installment\">在这一期中</h4>\n<p>在第一部分中,我们将会配置一个基本的React开发环境,在文章的末尾,你将拥有一个创建应用和动态重载的开发环境,当你的文件发生改变的时候,我们也会创建我们的的第一个React组件\n</p>\n<h4 id=\"user-content-prerequisite\">先决条件</h4>\n<p>我假设你已经熟悉Node和NPM并且已经安装好这两个工具。你还应该熟悉(Javascript)ES6。你不需要是ES6专家,但是知道主要的新功能如箭头函数和解构赋值。</p>\n<p>写这篇文章的时候,我使用的是Node6.2.0和NPM3.8.9版本</p>\n<h4 id=\"user-content-code\">代码</h4>\n<p>你可以在这里找到第一部分的所有代码,每个部分将有自己的分支</p>\n<h4 id=\"user-content-description\">项目描述</h4>\n<p>我们将要创建一个应用依托于Spotify的后台,我们的应用将允许用户:</p>\n<ul>\n<li>检索艺术家的专辑</li>\n<li>检索给定专辑的曲目</li>\n<li>播放曲目开头30秒</li>\n</ul>\n<p>以下是一个样本:</p>\n<img src=\"/images/mockup.png\" />\n<p>使用Spotify的后台,这样我们可以把更多的注意力集中放在前端开发上。</p>\n<h4 id=\"user-content-creation\">创建项目</h4>\n<p>让我们开始创建我们的项目</p>\n<pre><code class=\"language-js\">mkdir respotify\ncd respotify\nnpm init -y\n</code></pre>\n<p>这个将在我们的根目录下创建一个package.json文件</p>\n<p>下一步,在根目录下创建一个src目录,在src目录中创建一个index.html文件:</p>\n<pre><code class=\"language-html\"><!DOCTYPE html>\n<html>\n <head>\n <title>Respotify</title>\n </head>\n <body>\n <h1>Respotify</h1>\n <div id="user-content-container"></div>\n <script src="/bundle.js"></script>\n </body>\n</html>\n</code></pre>\n<p>index.html是我们应用的入口,其中有两行代码比较有趣:</p>\n<pre><code class=\"language-html\"> <div id="user-content-container"></div>\n <script src="/bundle.js"></script>\n</code></pre>\n<p>我们的React组件将渲染到id为container的div中,这个将会更加清晰,通过这个编文章</p>\n<p>构建的时候,我们要放在一起将所有的JS文件合并成一个文件bundle.js,并且在index.html引用它</p>\n<p>下一步,创建index.js:</p>\n<pre><code class=\"language-js\">console.log("Hello,world!");\n</code></pre>\n<p>index.js是我们的应用程序的主要的Javascript文件,现在,我们仅仅打印一些日志,以确保我们的构建正常工作。</p>\n<p>这个时候,我们的目录结构应该像这样:</p>\n<pre><code class=\"language-js\">respotify\n -src\n --index.html\n --index.js\n --package.json\n</code></pre>\n<h4 id=\"user-content-webpack\">Webpack</h4>\n<p>现在好了,我们将从头开始组建我们的构建环境,你可能听说过很多开发样本,例如<a href=\"https://github.com/kriasoft/react-starter-kit\">这里</a>提供了完整的开发配置环境,虽然这个很有用,但是我觉得至少自己会创建基本的配置,这样你才知道主要的东西是如何工作的</p>\n<p>我们从<a href=\"https://webpack.github.io/\">Webpack</a>开始构建我们的环境,简单地说,Webpack为我们打包静态资源,虽然有很多不同类型的打包工具,但是Webpack提供的一些功能,获得React社区的青睐,我们只是使用其中的一部分。</p>\n<p>让我们开始安装Webpack:</p>\n<pre><code class=\"language-js\">npm install webpack --save-dev\n</code></pre>\n<p>一旦安装完整,检查package.json文件Webpack是否在你的dev依赖。</p>\n<p>我们使用Webpack做两件事:</p>\n- 获取我们的应用程序代码,并从代码中生成静态资源\n- 启动一个开发服务器来服务静态资源\n<p>因为Webpack是配置驱动的,我们就从这里开始,在根目录下创建一个webpack.config.js文件:</p>\n<pre><code class=\"language-js\">const webpack = require('webpack'); // line 1\nconst path = require('path');// line 2\n // line 3\nconst PATHS = { // line 4\n app: './src/index.js', // line 5\n dist: path.join(__dirname, 'dist') // line 6\n}; // line 7\n // line 8\nmodule.exports = { // line 9\n entry: { // line 10\n javascript: PATHS.app // line 11\n }, // line 12\n output: { // line 13\n path: PATHS.dist, // line 14\n publicPath: '/', // line 15\n filename: 'bundle.js' // line 16\n }, // line 17\n devServer: { // line 18\n contentBase: PATHS.dist // line 19\n } // line 20\n}; // line 21\n</code></pre>\n<p>让我们继续</p>\n<p>在webpack.config.js文件顶部,我们引用webpack和path模块,然后为我们的应用程序定义几个常量</p>\n<p>[第9行] 这个是我们Webpack配置的开始</p>\n<p>[第10行] 这里最终成为我们bundle.js文件的<a href=\"https://webpack.github.io/docs/configuration.html#entry\">入口</a>,在这个例子中,我们有一个单独的JS文件(index.js)服务,做为我们的入口文件,Webpack将构建index.js以及任何其他JS文件在index.js中声明依赖的,与依赖那些依赖等,最终合成一个单一的bundle.js文件。想象一下一个依赖折叠成一个节点的图形。</p>\n<p>[第13行] <a href=\"https://webpack.github.io/docs/configuration.html#output\">这里</a>是我们设置的配置文件。在这个例子中,我们告诉Webpack:</p>\n- 输出其编译结果到dist目录下\n- 将输出的文件的网址设置为首页路径\n- 调用其编译的bundle.js结果\n<p>[第18行] 这里我们制定Webpack服务器从dist目录下请求静态资源目录。</p>\n<p>现在尝试在您的终端运行此命令(确保您在您的项目的根目录和节点在您的路径下):</p>\n<pre><code class=\"language-js\">node node_modules/webpack/bin/webpack.js\n</code></pre>\n<p>你将看到已经在根目录下创建一个dist目录,目录中有一个bundle.js文件在里面。</p>\n<p>如果你看看bundle.js,你将看到一些Webpack特定的方法和一个console.log声明在文件底部。</p>\n<p>这个时候,您的项目目录结构看起来应该像这样:</p>\n<pre><code class=\"language-js\">Respotify\n-dist\n --bundle.js\n-node_modules\n-src\n --index.html\n --index.js\n--package.json\n--webpack.config.js\n</code></pre>\n<p>现在我们也需要提供引用bundle.js的index.html文件,这个文件目前不在我们的dist目录下,因此我们需要复制index.html到dist目录下。</p>\n<p>要实现这个,我们需要安装文件加载程序包,安装方式如下:</p>\n<pre><code class=\"language-js\">npm install file-loader --save-dev\n</code></pre>\n<p>我们将修改webpack.config.js文件来引用index.html文件,我们还将包括一个模块对象来指定我们的第一个<a href=\"https://webpack.github.io/docs/loaders.html\">加载程序</a>。本质上,加载器就是加载或预编译运行的文件,在这种情况下,我们使用文件<a href=\"https://github.com/webpack/file-loader\">加载</a>复制index.html来输出(dist)目录,添加以下高亮的行到webpack.config.js文件中:</p>\n<pre><code class=\"language-js\">const webpack = require('webpack');\nconst path = require('path');\n \nconst PATHS = {\n app: './src/index.js',\n html: './src/index.html', // 高亮行\n dist: path.join(__dirname, 'dist')\n};\n \nmodule.exports = {\n entry: {\n javascript: PATHS.app,\n html: PATHS.html // 高亮行\n },\n output: {\n path: PATHS.dist,\n publicPath: '/',\n filename: 'bundle.js'\n },\n devServer: {\n contentBase: PATHS.dist\n },\n // 高亮部分\n module: { \n loaders: [\n {\n test: /\\.html$/,\n loader: "file?name=[name].[ext]"\n }\n ]\n }\n // 高亮部分\n};\n</code></pre>\n<p>现在我们再次运行之前的命令:</p>\n<pre><code class=\"language-js\">node node_modules/webpack/bin/webpack.js\n</code></pre>\n<p>你应该看到index.html已经复制到dist目录项了,dist目录现在应像这样:</p>\n<pre><code class=\"language-js\">dist\n--bundle.js\n--index.html\n</code></pre>\n<p>我们在开发的过程中,我们需要重新编译bundle.js,每次修改代码都要重新在命令行编译Webpack必然很乏味,如果我们要创建一个在我们修改代码的时候可以自动编译和刷新浏览器实时看到效果,我们该怎样做呢?</p>\n<p>这个时候就应该使用<a href=\"https://webpack.github.io/docs/webpack-dev-server.html\">Webpack dev server</a>了,让我们来安装它:</p>\n<pre><code class=\"language-js\">npm install webpack-dev-server --save-dev\n</code></pre>\n<p>一旦安装完成,我们运行一下命令:</p>\n<pre><code class=\"language-js\">node node_modules/webpack-dev-server/bin/webpack-dev-server.js\n</code></pre>\n<p>这将启动开发服务器在:<a href=\"http://localhost:8080/webpack-dev-server/\">http://localhost:8080/webpack-dev-server/</a></p>\n<p>如果您访问这个链接,你应该会看到我们的应用:</p>\n<img src=\"/images/respotify_initial_dev.png\" />\n<p>试着去修改index.html文件中的h1标签并且保存它,webpack开发服务器将自动检测并重新加载。每次重新加载你也会看到控制台输出日志。因此现在我们有一个webpack服务器,服务我们的静态资源和重新加载当有代码有修改的时候。</p>\n<p>让我们完善一下我们的package.json文件,创建一些脚本,以至于我们更方便处理一些事情。</p>\n<p>添加以下高亮行package.json的脚本对象中:</p>\n<pre><code class=\"language-js\">"scripts": {\n "start": "node node_modules/webpack-dev-server/bin/webpack-dev-server.js", // 高亮\n "build": "node node_modules/webpack/bin/webpack.js", // 高亮\n "test": "echo \\"Error: no test specified\\" && exit 1"\n},\n</code></pre>\n<p>如果你没有停止webpack开发服务器,停掉它然后运行用一下命令运行Webpack开发服务:</p>\n<pre><code class=\"language-js\">npm run start\n</code></pre>\n<p>使用一下命令构建:</p>\n<pre><code class=\"language-js\">npm run build\n</code></pre>\n<p>这个时候,我们基本的构建已经完成,下一步,我们将扩大我们的构建过程,允许我们使用最新的ES6功能。</p>\n<h4 id=\"user-content-babel\">Babel</h4>\n<p><a href=\"https://kangax.github.io/compat-table/es6/\">ES6支持</a>各种不同的浏览器,因此这样使用所有的ES6功能,不需要考虑各个浏览器的兼容性呢?一个解决方案就是把ES6代码转成ES5,这正是我们接下来要做的。</p>\n<p>我们将使用<a href=\"http://babeljs.io/\">Babel</a>来做这个事情,我们需要Babel来做两件事:</p>\n<ul>\n<li>转ES6代码到ES5</li>\n<li>转<a href=\"https://facebook.github.io/react/docs/jsx-in-depth.html\">JSX</a>成JavaScript。JSX是React DSL,类似HTML。你将学习它,当你开始React开发的时候。</li>\n</ul>\n<p>开始安装这些Babel包:</p>\n<pre><code class=\"language-js\">npm install babel-core babel-loader babel-preset-es2015 babel-preset-react --save-dev\n</code></pre>\n<p>让我们看看每个包都干嘛的:</p>\n<ul>\n<li><a href=\"https://github.com/babel/babel/tree/master/packages/babel-core\">babel-core</a> Babel核心编译器。</li>\n<li><a href=\"https://github.com/babel/babel-loader\">babel-loader</a> Webpack的Babel加载器。</li>\n<li><a href=\"https://babeljs.io/docs/plugins/preset-es2015/\">preset-es2015</a> 一套ES6转ES5的Babel插件。</li>\n<li><a href=\"https://babeljs.io/docs/plugins/preset-react/\">babel-preset-react</a> 一套JSX转成JS的Babel插件。</li>\n</ul>\n<p>在开始结合Babel之前,让我们来改改index.js,让它包含以下ES6代码,修改之后看起来像这样:</p>\n<pre><code class=\"language-js\">const greeting = (name) => {\n console.log(`Hello, ${name}!`);\n};\ngreeting('world');\n</code></pre>\n<p>现在执行 npm run build命令,看看文件bundle.js底部,你应该可以看到之前定义的原始的ES6方法,没有任何变化,考虑到这一点,让我们结合Babel。</p>\n<p>第一,我们将添加babel-loader到我们的webpack.config.js文件中,回想一下,我们如何使用文件加载器将index.html文件复制到dist目录中,我们现在为Babel添加第二个加载对象,添加一下高亮行到你到module对象中:</p>\n<pre><code class=\"language-js\">module: {\n loaders: [\n {\n test: /\\.html$/,\n loader: "file?name=[name].[ext]"\n },\n //高亮部分\n { \n test: /\\.js$/,\n exclude: /node_modules/,\n loaders: ["babel-loader"]\n }\n //高亮部分\n ]\n}\n\n</code></pre>\n<p>这里,我们要求Webpack应用babel-loader在所有以.js为后缀到并且不在node_modules文件夹中的文件。</p>\n<p>下一步,我们在项目根目录中创建一个.babelrc文件,内容如下:</p>\n<pre><code class=\"language-js\">{\n"presets":["react","es2015"]\n}\n</code></pre>\n<p>这里要求Babel使用前面我们安装的<a href=\" presets\"> presets</a>。</p>\n<p>就这样。</p>\n<p>再一次执行 npm run build 和检查bundle.js,我们的greeting 方法现在应该转成了ES5。</p>\n<p>好,我们几乎已经完成了,下一步我们要做的就是重新加载React组件,当我们做改动的时候,不会丢失状态信息。</p>\n<h4 id=\"user-content-hot\">热模块更换</h4>\n<p>让我们开始安装<a href=\"https://facebook.github.io/react/\">React</a>和<a href=\"https://facebook.github.io/react/docs/top-level-api.html#reactdom\">ReactDOM</a>,以及<a href=\"https://gaearon.github.io/react-hot-loader/\">React Hot Loader</a>。注意我们安装React和ReactDOM作为正常的依赖,而React Hot Loader作为开发依赖。安装方式如下:</p>\n<pre><code class=\"language-js\">npm install react react-dom --save\n</code></pre>\n<pre><code class=\"language-js\">npm install react-hot-loader --save-dev\n</code></pre>\n<p>下一步,我们让webpack使用热更换,打开webpack.config.js修改babel-loader像这样:</p>\n<pre><code class=\"language-js\">loaders:["react-hot","babel-loader"]\n</code></pre>\n<p>重要的是,React-hot被添加为webpack过程从右到左的之前。</p>\n<p>下一步,打开package.json编辑 start npm脚本如下:</p>\n<pre><code class=\"language-js\">"start": "node node_modules/webpack-dev-server/bin/webpack-dev-server.js --hot --inline",\n</code></pre>\n<p>在这一点上,我们进一步加强我们的开发配置重新加载React组件而状态不丢失,但是这个意味着什么呢?要回答这一问题,我们需要创建一个React组件来测试我们的配置。</p>\n<h4 id=\"user-content-component\">我们的第一个React组件</h4>\n<p>在src目录下创建一个greeting.js:</p>\n<pre><code class=\"language-js\">import React from "react"; //第1行\n \nexport default React.createClass({ //第3行\n render: function() {//第4行\n return ( //第5行\n <div className="greeting">\n Hello, {this.props.name}!\n </div>\n ); //第9行\n }\n});\n</code></pre>\n<p>我们刚刚创建我们的第一个React组件,让我们一行一行来看。</p>\n<p>[第一行]在顶部,我们导入React。</p>\n<p>[第三行]我们通过<a href=\"https://facebook.github.io/react/docs/top-level-api.html#react.createclass\">React.createClass方法</a>创建一个React组件类,传入一个描述我们组件的规范对象。</p>\n<p>[第四行]每个React组件都必须有一个render方法,它返回组件的标记。</p>\n<p>[第五-九行]在render方法里,我们返回一个类HTML 包含一个class名称为gretting的div 包裹这一个字符串,字符串本身把Hello和一些引用值连接起来。</p>\n<p>我们将继续深入理解第五-九行,但是现在,让我们先把这个组件在屏幕上展示出来。</p>\n<p>打开index.js修改它如下:</p>\n<pre><code class=\"language-js\">import React from "react";\nimport ReactDOM from "react-dom";\nimport Greeting from './greeting';\n \nReactDOM.render(\n <Greeting name="World"/>,\n document.getElementById('container')\n);\n</code></pre>\n<p>我们再一次从导入React开始,接着我们导入ReactDOM,用于渲染DOM的API接口,我们也导入我们的greeting component。</p>\n<p>当我们调用ReactDOM的render方法时,我们传入两个参数:</p>\n<ul>\n<li>1.我们需要渲染的组件。</li>\n<li>2.我们要把组件渲染到的位置。</li>\n</ul>\n<p>在我们的例子中,我们将渲染我们的greeting组件到一个id为container的div中,这个div在我们的index.html中。</p>\n<p>注意第一个参数是怎么样子的,我们传入greeting组件,它好像是一个HTML元素,它包含了一个键值对,其中键的名字为name,值为world。然后name就成为我们greeting组件的属性。</p>\n<p>大体上,我们可以这么描述index.js:</p>\n<p><strong>渲染greeting组件,把world赋给它的name属性,把这个组件渲染到id为container的div上。</strong></p>\n<p>现在,当我们渲染greeting组件时,我们将接收name属性和包含它的值的字符串,重启开发服务并验证这个组件渲染:</p>\n<img src=\"/images/respotify_first_component.png\" />\n<p>测试它的重载,试着用h1标签包裹字符串,如下:</p>\n<pre><code class=\"language-js\"><h1>Hello, {this.props.name}!</h1>\n</code></pre>\n<p>保存它,你将看到组件局部更新,而不需要全部重新加载。</p>\n<p>现在,让我们退后一步,重新看greeting.js代码片段:</p>\n<pre><code class=\"language-js\"> return (\n <div className="greeting">\n Hello, {this.props.name}!\n </div>\n );\n</code></pre>\n<p>我们返回的标记实际上是JSX,一个React特定的DSL,就像HTML用方便描述树结构。我们的Babel配置会把JSX转换成等效的纯JavaScript。有两个重要的概念要记住:</p>\n<ul>\n<li>JSX不是JavaScript的扩展。</li>\n<li>JSX不是必须的,你可以使用纯JavaScript的调用方式代替。</li>\n</ul>\n<p>要展示第二点,我们使用<a href=\"http://babeljs.io/repl/\"> Babel REPL</a>,在REPL中,确保选中ES2015 和 React preset这两个选项,复制greeting.js的内容到左侧的编辑器中,你会看到转换输出到右侧,一个代码片段类似这样:</p>\n<pre><code class=\"language-js\"><div className="greeting" />\n</code></pre>\n<p>将转换为:</p>\n<pre><code class=\"language-js\">_react2.default.createElement("div", { className: "greeting" });\n</code></pre>\n<p>以上的纯JavaScript你可以直接使用,然而,我强烈推荐使用JSX,这个让你的React组件方便阅读和书写。事实上,它看起来像HTML也有助于减少学习曲线,当然,由于JSX是JavaScript,跟HTML有一些细微的差异,比如JSX的className和HTML的class,我们将深入理解更多JSX的功能,可以去看看Facebook的指南了解更多的东西。</p>\n<p>让我们把注意力放在div中的这行代码:</p>\n<pre><code class=\"language-js\"> Hello, {this.props.name}!\n</code></pre>\n<p>在JSX中,用花括号括起来的是一个JavaScript表达式,因此,在这里,我们访问这个组件的props对象,反过来,在props上访问name属性。</p>\n<p>还记得在index.js中的这一行代码吗?</p>\n<pre><code class=\"language-js\"><Greeting name="World"/>\n</code></pre>\n<p>当你实例化一个组件,你可以传入大量的表示该组件状态的键值对。因此,当我们实例化我们的greeting组件,我们也应该指定一个名为name的状态。这个值的状态叫做“world”。</p>\n<p>在实际的greeting组件中,该状态通过组件的props对象和使用相同的name标签来传递。花括号里的值被计算,并且返回“Hello,world”渲染到浏览器中。</p>\n<p>记住主要的一点,就是props是不可变的。换句话说,一旦状态被传递到一个组件,接受组件就不能再改变它了。这样有助于你的组件更容易理解和调试。以及将状态管理分离到几个模块中。</p>\n<p>试着在index.js中修改name的值。你将看到Webpack检测变化和自动加载,greeting组件将接收到新的状态,并作出响应。</p>\n<p>如果你跟着一起做下来,并且一切都正常工作,那么恭喜你!你刚刚构建了一个基于打包、转换、热更新的React应用开发环境。此外,你还创建了一个React组件,记住,你可以添加更多的环境,比如压缩文件,选择性打包,启用和禁用各种开发工具。但是对于我们而言,这样已经够了。</p>\n<p>在下一部分中,我们将进一步创建更多的React组件来完善我们的Spotify客户端,我们还将添加静态分析来保持我们的代码整洁明了。</p>\n<p>预计下一部分会在这一期发布之后的两个星期内完成。在此之前,如果你有任何意见和建议,请在下面留言或者给我发<a href=\"http://patternhatch.com/about/\">邮件</a>。</p>\n","author":"lanqy"}]