(这不是没什么闲钱压根没用过云服务提供的类似服务才折腾 Nginx 嘛
(Nginx 也没整什么花活就全靠默认的配置
(Nodejs 上跑 SocketIO 配置自己写,Rails 的配置抄 Capistrano
(按理讲防 DDoS 挂个 CloudFlare 的免费 CDN 比较靠谱……叭?
(要说的是 slow client 的话,puma 从 4 开始是有处理的(两年前就是了):https://www.schneems.com/2019/06/26/puma-4-new-io-4-your-server/
(unicorn 应该是没有,因为用的是 worker,所以 unicorn 前面必须有额外的层来进行 slow client attack 防护
(虽然这并不改变 puma 前面应该有一个类似 nginx 的事实叭
静态文件服务、TLS(包括 HTTP 重定向,OCSP Stapling, Cipher Suites 的优先级调整)、动态压缩、multipart 合并、还有简单的限流和黑名单自动化来(在一定限度上)防御 DDoS 还是在 Web 服务器上做会舒适一点。
Nginx 生态也不仅仅限于负载均衡嘛。就像 Rate Limit 这块就比 Rack 成熟很多,Ruby 应该是只有一个能用的 Rack 中间件。
至于隔壁 Elixir 社区里面有着不愿意用 Nginx 的风气,多半是因为 Cowboy 早就支持了 HTTP 2,而且性能也挺好。
另一个考虑是,我是不愿意让 Puma 占据 443 端口的,还有别的服务呢。所以就给 Nginx 了,Puma 让它开个 Unix Domain Socket 就行了。(除非用 iptables 搞行为艺术
它这种只是输出了期望得到的 token 的,成熟的 lexer/parser generator 都有的吧,不涉及语义因此其实提示性的用处不太大且时常有迷惑性。加上一个错误 case 就行,然后在此处打印行号列号和期望得到的 token/value 和当前读到的 token。
由于打印期望得到的 token 可能会迷惑使用者,我在做 shell lab 实验的时候就没打印,只是打印列号和当前的错误 token 而已(加上 shell 其实没有什么期望得到的 token,管道、重定向和布尔符号都是可选的,只是做了必须要有一个 WORD 作为命令的特判而已)
如果是希望手动写 Parser,那确实需要自己处理。但实在是太累了,做 HTTP 报文解析这种用一个大 Switch 当状态机还好,还可以进行统计概率上的预测优化。但语言的 Parser 又不追求速度,用 lexer/parser generator 就可以了还严谨好维护。
当然了写 shell 只是为了学习系统调用,完全没有支持函数和控制流命令之类的,复杂度不可相提并论。不过即使是要用 lexer/parser generator 来写一个 DSL 的话我也是不太支持的,因为几乎所有的独立 DSL 都失败了,eDSL 才有鲜活的生命力。对于不可信来源的代码,既要保证图灵完备还要用即有语法和生态,那还是 Shopify/ess 叭。
另一种思路就是写一个受限环境的源代码变换器或是元循环解释器,就是先用 Ruby 写一个 Ruby 的 parser,输出 AST,把 AST 里所有能够动态生成调用的分支干掉(最显著就是 eval)或者一经发现有问题直接报错。如果写元循环解释器这个时候你拿到干净的 AST 就能在树上游走并调用 Ruby 进行解释了(不知道为啥我觉得这样更安全,可能是因为黑名单不如白名单。但考虑到 Ruby 没有好用的模式匹配,处理 Ruby 那么多 case 可能要脱层皮),如果是写源代码变换器的话就是再把 AST 转换回 Ruby,然后 eval。第一步和最后一步已经做好了,叫 ruby_parser 和 ruby2ruby。这俩 Gem 是一个人写的,所以 AST 格式一样。熟悉前端的话,这个套路约等于 JavaScript 界的 Babel。
我记得 JavaScript 界就有人实现了用 JavaScript(运行时)写的受限 JavaScript 解释器,大抵是面向 ES5 的,因为去掉了 eval Function 等等没法完全通过测试用例,但据说生产能用。可惜基本上等于是假开源,没放出来编译器的源码。
如果能做到这一点的话,恭喜你把 Lisp 的宏模型引入了 Ruby 原有的字符串宏的体系中。叫什么好呢,Elixir 吗?(逃
这……还真是有迷惑性。因为 if 分支确实写了,我还以为只是 else 分支和里面的箭头函数忘写了,还分析了半天。
因为在一切皆表达式的语言里,是不会做出只会隐式返回你看到最后一条表达式的值这种蠢事的,而是会确保每一条控制流都会有值。具体来说就是函数中线性控制流的最后一条表达式是 if-else 整体,而 if-else 表达式的值是其内部每条控制流的值,也就是对应的线性控制流中最后一条表达式。
(所以我完全没有想到这种可能性,原来你的 else 和里层箭头函数居然是刻意空开的)
(如果以后出一个 Babel 插件能够让 JavaScript 变成一切皆表达式就好了)
有必要这么复杂吗?在每个分支处都要 return 就行了呗?而你写的不光在 else 没 return,在返回的闭包也没 return(箭头函数如果只需要写一行的话,去掉花括号,整个箭头函数的值就是那一行的值了)
保证返回这不是递归的基本盘嘛?你的第一次 call 能行,是因为走了 if 分支确实返回了那个函数,有这个函数可以用。第二次就不行了,因为不光返回的“延迟执行”函数没有值,而且还走了一边 else,那个分支也没有值。给一个变量赋值为没有值的表达式,那可不就是 undefined 嘛。
所以才问是不是一切皆表达式的语言用习惯了。我看你旁边开的是 Rust,估计就是这么想的,还心想这个很容易看出来啊。
另外我讨厌叫 JavaScript 的函数为闭包,箭头函数是引用捕获而非值捕获,说明闭包是可以说是漏的,充其量算函数一等公民。
还有你的生成器好像会崩啊,怎么只在 if 分支判断了 start <= end。生成器运行次数到了,永远走 else 分支就一定会爆栈的吧?
要写生成器的话不如去看看 Babel 是怎么转换的,一般用 for 循环比较多,递归毕竟还是有深度限制的。
至于直接回答你的问题的话,如果运行到 if 分支就是个普通函数调用,是不会产生递归栈的增长的,而 else 分支才会有,而且由于你的设计是逢偶数便“中断”(其实就是退出递归),递归栈帧最大的深度也就是 1 而已(相对它)。对于 if 分支,上层函数已经返回了函数给你,上层函数 执行结束并释放,指令指针早就恢复到调用者(主入口),已经没它事了 ,在外面执行这个函数有没有值只和这个函数本身有没有值有关。
你会用这种“已经没有 return 可以用了”、“函数会恢复”的描述我都要怀疑你在乱想 CPS 变换了,但不是这个理啊。在这里能在逻辑上恢复之前的进度,是因为你把状态通过引用捕获的箭头函数保存在了箭头函数里,具体而言是箭头函数里某个函数调用的参数列表里。确实很符合无栈协程的样子,生成器本身也是无栈协程的前身或者说一部分,但这不改变语言本身的性质。
另外一提,用参数列表保存状态不该是不可变语言中的常见操作嘛?JavaScript 主流实现连尾递归优化都没有,也不是不可变语言,使用这种感觉好奇怪。
这种情况我称之为 CoffeeScript 用多了(考虑到是 Ruby 社区(不排除用多了其他一切皆表达式的语言
(另外好奇怪欸你的 VSCode 既然还有窗口装饰 (以及 Chromium 似乎早就放弃了尾递归优化?
关注宏的语言,如果在一开始做编译器的时候没有 IDE 的意识的话,后续做 IDE 还真的挺难的。即使是 Java,早期 Eclipse 也得做一个自己的 IDE Aware 的编译器 静态类型/宏是基于 AST 变换语言的可能还要好一些,动态(特指不编译,比如 Elixir 虽然动态但要编译的,宏展开后就好分析)的/字符串拼接或者 Reader 宏那就要难上天了
(所以我 Python 和 Ruby 都用 VSCode
IPython 在执行命令这方面还是猛的,一方面是把很多东西都 Alias 进来了,不用加感叹号也能执行,另一方面是命令的返回不仅仅是其他语言的 REPL 里捕获标准输出作为一个字符串或者 Buffer,还按照 Unix 的习惯分节符给切成了数组(有时候不准,就是一行一个元素),所以有人说 IPython 尝试做 PowerShell 的有类型管道(虽然没多少人 care)。
Ruby3 我印象里是提升了粘贴 IRB 多行代码的效率,以前的话要等好久。至于速度和以前有什么区别还真不太确定。我倒是觉得没有必要强行往 Jupyter 上面凑,那上面虽然列出来 Kernel 的很多,能用的也就是 Python 和 R 的了。至于逻辑的组合,隔壁 Elixir 的作者在搞一个叫 Nx 的东西,借助 Elixir 能够操纵 AST 的宏机制,把 Elixir 代码编译到 Google 的 XLA,这样写个核函数或者激活函数啥的,就能以比较高效的方式在 CPU 或者 GPU 上运行。我觉得这才是比较合理的思路,Ruby 的话因为宏基本上还是基于字符串的,虽然其本身控制流在通用领域比 Python 强不少,但在计算这方面往往还是需要手动请原生外援然后做绑定,像 Elixir 这样直接编译过去应该比较难做吧。
另外 Elixir 就不打算去做适应 Jupyter 的尝试,而是自己用 Phoenix LiveView 写了一个 Livebook,可能也是因为这么做更加符合 BEAM 虚拟机的架构(一个 Notebook 一个 BEAM Process 或 Node),另外用了 LiveView 也好做多人协作。这样的话绘图之类的也是自己从头做起,用的是 Vega Lite,还做了动画的适配。
我也觉得一步跨越到 import maps 有些激进了。隔壁 Phoenix 起码还有个“为了不产生不被 Erlang 掌控的进程”和“减少一个 Nodejs 依赖”的额外理由在,另外 Cowboy 很早很早就支持了 HTTP 2,不打包也没啥事反正能主动 push,甚至于 Phoenix 社区都不太喜欢用 Nginx 当静态服务。
Rails 又没啥工具链的进程洁癖,没有理由为了省依赖去掉 Webpacker 和 Nodejs。或许是觉得 Puma 还停留在 HTTP 1.1,用 No Bundle 方案不太好?
再怎么说 IPython 可是(号称)能当系统 Shell 的东西,有着!开头的系统 Shell 透传和%开头的各种 Magic,甚至还用了 https://www.python.org/dev/peps/pep-0215/ 的字符串嵌入展开,还有着基于变量的强类型管道。
这些概念 Jupyter 是不关心的,虽然我有理由怀疑 Jupyter 的块是为了 IPython 的块魔法开的绿灯。而 IRuby 在我印象里应该就只是把 IRB 或者可选的 Pry 用 ZeroMQ 连接到 Jupyter 而已,没有这些特性也是理所应当的吧。
至于绘图那更是 Python 老家那边更加擅长,把 matplotlib 的 ipympl 后端列为 JupyterLab 的依赖里就是说句话的事情。所以折腾 ML 和统计还是直接用 Python 得了。
各家似乎一直都在去 Webpack 化。隔壁 Phoenix 刚从 Brunch 转向 Webpack 没多久,下个版本 1.6 就已经确定梭哈 esbuild 了;Rails 未来风向更狠,Rails 7 应该是直接用 import maps(看 DHH 最近的动向是这样),还鼓励用 Skypack 这样的 CDN。可怜 Webpacker 还没捂热乎就无了(也不算吧),我还用了 Webpacker 的 Beta 好长一段时间呢。
不是……语言能使机器高性能,和可读不可读,开发者的心智负担高不高有什么关系呢?带有函数式血统的语言,比如 ML 和 Lisp 家族由于使用 Lambda 演算而非图灵机作为模型,性能上可能会比较难以优化,可这也不影响可读性好,心智负担低啊?转过来想汇编性能最高,甚至 Chez Scheme 作者自己写 Intel 的汇编器,可是现在除了搞 JIT 的、搞模拟器的、搞 CPU 的还有搞 BIOS/UEFI Bootstrapping 的,还有谁会平时写汇编嘛?
结构化编辑作为辅助声明式语言的优秀工具,虽然由于场景有限导致发展不大,但我还是一把子支持的。不过你这个感觉要走的路就很长啊,比如为啥要以交通灯和石头剪刀布代表控制流,这个本身就不自洽的情况下当作文本之外的视觉辅助还姑且能用,可是你一开始就把文本符号去掉了,不就白费功夫了吗。
用颜色、字体和字体装饰来修饰 token 的类型就更怪了。别的语言使用 CamelCase、kebab-case、snake_case 及其变种来充当规范,这还不够还需要加上魔符来提示,早期的工程更是大量使用匈牙利命名法。而你这个真的三眼都看不出来是什么东西的。再退一万步讲,色盲色弱怎么办?
循环语义和定义类的结构倒比较合理,其中使用类似表格定义的方式让人很难不想到 COBOL。不过 COBOL 能够那样做的原因是它太简单了,把它认为是商务的 DSL 的不冤枉的。
结构化编辑上,给 Lisp 做自动缩进和加括号的努力 Parinfer 都不温不火,如今仅剩一个 Rust 实现在延续,而 Lisp 的语法已经是很简单的了,我认为可能是最适合发展结构化编辑的编程语言了。
编程语言的可读性我觉得不在于块结构上面,像 Ruby VB Elixir 这种有 end 的、和 C 风格的花括号,都没有什么高下之分,甚至 Python 备受嘲讽的“游标卡尺”能不能作为一个成立的笑话都不一定。真正拉开差距的是其他地方吧:比如 Perl 被认为是只读语言,就是因为写法灵活加上魔符的大量使用,导致回头看的时候很难一眼知道这是在干啥;Rust 使用所有权来保证安全,付出的代价就是些许语法噪声和编码时消耗更多脑力;Go 的生态中大量推崇代码生成器,虽然确实显式但缺乏了对常见模式的抽象;编程语言之外的 CSS 本身设计很好,但早期浮动布局时代的清除浮动等等充斥着反模式,加上至今也不支持文本结构上的“层叠”导致风评被害;所以如果把改造重心放在块结构上面效果没多大用,何况你提出的这个方案能不能称得上改造还有待商榷(比如说石头剪刀布的设想,凭啥石头代表 if?凭啥?说神经病一点不冤)。
另一个是很可能会走 DSL/Low Code 的老路:有很多 DSL 作为使开发人员快速解决领域问题的语言,却怀着扩大疆域到非开发人员的行业人员上,因此为了隐藏基于文本的控制流和逻辑复杂度,使用从 GUI 借来的经验来提升可读性,结果就是:辛辛苦苦造出来的 DSL,行业人员因为没有简化领域的根本问题而不会去使用,开发人员因为过于和领域问题耦合,无法和其余生态配合而不学。所以在这个连 DSL 都只剩下 eDSL,Low Code 基本只在 CRM 和表单生成器发光发热的时代,很难说这样一种没有完全脱离文本,又要强制在所有地方融入基于图形的结构化表征的思路,会产生什么成功。
我的感觉是,要不就完全图形化,像面向少儿编程的 Scratch,或者问卷星;要不就专攻文本,通过完善 IDE(起码要达到当年 SmallTalk 的水平吧)和提升语言/库在控制流上的支持(比如 Elixir 的那些控制流都是宏写出来的,核心只有模式匹配,这样能够很方便的造出新的控制流,甚至实现 Pipe),要么就二者融合,但保证可选(如一个 Business Logic 引擎,既可以让用户拖拖拽拽定义审批流,开发人员又可以对这个审批流进行 Transform 之类的;或者交互式编程中,Mathematica 以及其他领域的 Notebook 就是把图形表示嵌入语言的良好实践)。
有些东西在 Windows 上安装是十分痛苦的,有一半原因就是 Windows 上的编译工具链太大太奇怪。我体验过在 Windows 下难装/难用的东西包括:Ruby Python Nodejs(这三个主要是因为编译构建工具链)、PostgreSQL(知名的难装,甚至还有 Windows 发行版,好像叫 EnterpriseDB?),剩下还有一堆 Haskell、OCaml 等等我试都不敢试,单单是看文档就难装。(OCaml 倒是也装过,被立即劝退)。Rust 本来也是难装的,但和 Python 一样有巨硬砸钱,加上还有 Rustup 所以也还好。
很多东西人家老家就是在 Linux 下,非要在 Windows 下折腾,是得不到幸福的。不少软件也是,人家写明了就不想在 Windows 下跑,通信领域的仿真软件尤是如此,甚至还会指定 Linux 发行版。
所以做 Web 的话如果不是.net 那套,趁早切到 Linux 能省很多时间。看你的描述是开发环境需要在本地自己配,那么想要进一步节省时间,学会 Docker 是很好的选择