跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 动手写一个简单的编译器:在 JavaScript 中使用 Swift 的尾闭包语法
未分類
29 3 月 2021

动手写一个简单的编译器:在 JavaScript 中使用 Swift 的尾闭包语法

动手写一个简单的编译器:在 JavaScript 中使用 Swift 的尾闭包语法

資深大佬 : dreamapplehappy 19

最近业余时间在学习SwiftUI的过程中发现在SwiftUI中大量使用了尾闭包(Trailing Closure)的语法,觉得挺有趣的。作为一个经常使用JavaScript作为开发语言的前端来说,我忽然想可不可以自己写一个简单的编译器,在JavaScript中使用这种语法呢? 于是就有了这个小项目 js-trailing-closure-toy-compiler ,通过这个编译器我们可以将下面的代码:

a(){} 

转换为:

a(() => {}); 

或者将:

a(1, "hello"){ b, c in     d()     d{}     d(1, "hello")     d(1, "hello"){}     d(1, "hello"){ e, f in         g()     } } 

转换为:

a(1, "hello", (b, c) => {     d();     d(() => {});     d(1, "hello");     d(1, "hello", () => {});     d(1, "hello", (e, f) => {         g()     }) }) 

关于Swift的尾闭包如果你不是很理解,可以参考Swift关于 Closures 的文档

项目的在线演示地址:JavaScript Trailing Closure Toy Compiler

关于项目代码部分的详细解释可以阅读这篇文章:动手写一个简单的编译器:在 JavaScript 中使用 Swift 的尾闭包语法

关于这个小项目大家有什么想法和建议,欢迎在文章下面留言,我们一起交流一下。

大佬有話說 (37)

  • 資深大佬 : dawn009

    主对“尾闭包”这种写法本身有没有什么评价?
    我一直没有体会到它的好处,程序逻辑没有因此变得更清晰,也没有能让你少打几个字。
    最常见的用法,是把 completion handler 放在那个位置写成“尾闭包”的形式,在视觉上暗示“会在函数执行完成后运行闭包内的代码”。但是,任何功能的闭包都可以放在那个位置,并不一定要是 completion handler,我完全可以用同样的方式调用一个“在函数开始之前运行”的闭包。
    把 completion handler 放在尾部更像是一个习惯或是代码风格的问题。我觉得,把一个代码风格的设计直接内置到语言中是不是不太好。

  • 主 資深大佬 : dreamapplehappy

    @dawn009 这是一个好问题,手动给你点个赞。说实话我还没有认真考虑过这个问题,还在刚开始的学习中。不过每种编程语言对一些相同的操作或多或少都会有自己的风格,感觉这很大程度上跟语言的创造者有关系。希望对此有了解的同学可以分享一下~

  • 資深大佬 : love

    对 JS 没什么好处,但是 python 需要啊,python 写起来比 js 难受很多 lambda 只能单行也是一大原因

  • 資深大佬 : codehz

    这不是 cps 风吗,以前某 livescript 都这样玩(

  • 資深大佬 : lujjjh

    没写过 Swift,发现这个语法挺有趣的,我一开始也以为是 CPS 变换,仔细看发现不是。

    搜了下,发现 Kotlin 也有类似的语法 https://kotlinlang.org/docs/lambdas.html#passing-trailing-lambdas

    @dawn009 我的看法是,这种语法主要提供了一种定制 DSL 的能力,比如说你提到的 completion handler 就是一个例子,但也不限于 completion handler,比如可以用来定义 with (something) { … } 的语法(只需要定义一个 with 函数)。表达力强的话甚至可以用来描述 UI,搜了下 SwiftUI 似乎就是这种玩法?

    Kotlin 也有用 trailing lambda 定制 DSL 的例子 https://kotlinlang.org/docs/type-safe-builders.html

  • 資深大佬 : forvtest

    我有点感觉 Swift 这么做是专门为了 SwiftUI 做准备的(参照推出尾闭包的时间节点)

  • 資深大佬 : irytu

    表达力强吧很多时候,不过还是要看具体代码设计

  • 資深大佬 : dawn009

    @lujjjh #5 这只是把大括号写在小括号里面还是外面的区别,好像没有改变表达力?

    head({ … })
    head() { … }
    head { … }

  • 資深大佬 : wipbssldo

    我怎么感觉你这个转换后的不对劲

  • 資深大佬 : no1xsyzy

    这难道不是从 Ruby 借鉴来的语法吗? do |params…|
    body…
    end

    不过提醒一句,你这个语法在 js 里似乎某些情况下可能与现有语法冲突?
    a(){} 似乎和 object 内定义函数的 a: function () {} 的语法糖是一样的,不知是否有区分。

    @love Python 下大概可以用 PyMacro 的 f[] 宏代换一下(实际没试过:
    @f[list(map(_, range(100)))]
    def result(v):
      return v**2

  • 資深大佬 : wobuhuicode

    写 swift 时候最不喜欢的语法之一。

  • 資深大佬 : iyeatse

    @dawn009 如果用来描述 UI 的话,花括号里面的内容可能要超过几十行,这个情况下如果花括号结束之后还需要程序员记得关闭小括号那就很反人类了

  • 資深大佬 : lujjjh

    @dawn009 看怎么理解表达力了,一般来说,语法越灵活,能定制出的 DSL 也越好用( head { … } 显然比 head({ … }) 或者 head(() => …) 更简洁)。

    不过我原文里的意思是,光有 trailing closure / lambda 这个特性是实现不了 html { head { … } } 这种效果的。比如 head 里需要能够访问到 html 里实例化出的对象,才能把自己 append 进 html.children 。除了 trailing closure / lambda 之外,还需要结合其他特性才能定制出这种 DSL 。

  • 資深大佬 : abersheeran

    @love Python 风格就是少用匿名函数。一个语言有一个语言的味道,拿 JS 硬套 Py,不难受就出鬼了。我就从来不拿 Py 那一套硬套 JS 。Py 、JS 我都用的挺爽的。

  • 資深大佬 : Jirajine

    @lujjjh 应该能实现吧,有这个语法直接把 react 包装一下就可以了。

  • 資深大佬 : lujjjh

    挽尊,给这个项目本身一点建议。这个项目用来学习写简单的编译器是没问题的,实用角度来看比较尴尬。

    这个与其说是给 js 增加了 trailing closure 语法,不如说是搞了个能够 transpile 到 js 的 trailing closure language 。

    如果你的想法是在 js 的基础上增加这个语法,那就得考虑很多问题:
    1. 怎么兼容 js 现有的语法?
    2. 这个语法有什么用,是不是还得配合实现 implicit return 、function builders 之类的特性才真的有用?
    3. 怎么兼容 js 的工具链( language server 、eslint……)

    如果还是想在 js 的基础上做一些文章而不是设计一个全新的语言,不妨考虑基于现有的语法创建新的语义。比如远古时代的 Wind.js[1] 在不修改 js 语法的基础上实现 async / await ( CPS 变换);再比如 Svelte[2] 用 label 表示 reactive declarations 。

    [1]: https://github.com/JeffreyZhao/wind/blob/master/samples/async/browser/quick-start.html
    [2]: https://svelte.dev/tutorial/reactive-declarations

  • 資深大佬 : lujjjh

    @Jirajine 有 implicit return 的话可以用高阶函数实现一部分,但是要支持 children 有多个,像是
    html {
    head { … }
    body { … }
    }
    的话,implicit return 做不到,还需要像是 Swift 的 function builders 或者 Kotlin 的 function literals with receivers + 可省略的 self. / this.。

  • 資深大佬 : Jirajine

    @lujjjh 支持多个 children 的话,用 array literal 能不能做到?
    html {
    [head{},
    body{}
    ]
    }
    还有一种思路是用对象成员.连起来
    html{
    head{}
    .body{}
    }
    不过这样可能就要难看一点。

    lambda 里支持 implicit return 肯定是基本的。可以参考 elm,我觉得比较接近。

  • 資深大佬 : lujjjh

    @Jirajine 仔细想了想,单线程语言里可以做到,只不过会像 React hooks 的实现一样黑

  • 資深大佬 : lujjjh

    @Jirajine 能接受数组的话为什么还需要 closure 呢?我写了个我认为比较接近的实现:

    https://gist.github.com/lujjjh/1f10ed514191cd4d13e0057ed23ad6ed

  • 資深大佬 : no1xsyzy

    @Jirajine @lujjjh array literal 实现应当非常直观,大概拿 template string 写是这样
    html = (closure) => `<html>${closure().join(”)}</html>`
    用 render 函数表达的话大概
    html = (closure) => h => h(‘html’, {}, closure().map(render => render(h)))

    也可以添加判断 closure 返回值是单元素还是 array

  • 資深大佬 : no1xsyzy

    @lujjjh 不需要全局,React (不管 hooks ) render 函数其实就是这种想法,你写得还是脏了
    只要把后面那个 closure call 出来的结果合并进去就成了

  • 資深大佬 : lujjjh

    @no1xsyzy 你可能没有看全上下文。我当然知道 closure 返回数组的话很容易处理。

    我思考的问题是怎么避免手写数组,或者说怎么模拟 Swift 的 function builders,从而实现一个更简洁的 HTML DSL 。js 里引入全局的存储应该是唯一解。

    你可以看到我的代码里 L23-L28 在使用的时候完全没有手写数组。

  • 資深大佬 : no1xsyzy

    @lujjjh 都已经动了编译器了,重新发明下宏就行了,不是什么大问题。

  • 資深大佬 : lujjjh

    @no1xsyzy Talk is cheap,我的核心观点还是在 #16 。像 Svelte 那样动编译器跟动语法是两码事,动语法带来的问题太多了,工具链是问题,方言能不能被大众接受也是问题。React Hooks 实现得这么黑本质上也是为了在 js 的限制下设计出一套相对好用的 DSL,否则完全可以设计成 Vue Composition API 的样子,改动语法可能性就更多了。

  • 資深大佬 : no1xsyzy

    @lujjjh 我是说主题已经动了编译器了,然后你还束手束脚; React hooks 这点问题也大,都已经 JSX 了其实没必要完全遵守 JS 的约束,有点又当又立的意思在。
    宏是 JS 很明显缺失的一环( JavaScript 是一个没能成为 Lisp 的 Lisp 方言),又其实有点大家都不敢抢先做的意思。

  • 資深大佬 : lujjjh

    @no1xsyzy 怎么感觉你对 js 这么恨铁不成钢[doge]。jsx 至少还算是 opt-in 的。

    无意引起争论,给 ECMAScript 提 proposal 或者发明一种新语言都没啥问题。只不过在 #16 给出了点基于 js 扩展要考虑的问题和建议而已。至于后面那段实现很脏的代码,也是在跟 @Jirajine #18 探讨 js 实现类似 DSL 的可能性。

    既然又回复了,我就再给这个「在 JavaScript 中使用 Swift 的尾闭包语法」的项目提点建议(如果主的本意是设计一门全新的语言请无视):

    * { b, c in … } 这种语法毕竟是 Swift 的闭包语法,基于 js 扩展用类似 arrow function 的语法更具一致性。
    * 可以在 babel 的基础上魔改,再写个插件,就可以在实际项目中使用了。

    最后也分享个我搞的语言,欢迎交流: https://github.com/lujjjh/gates

  • 資深大佬 : CaffreySun

    @dawn009
    尾随闭包不只是一种代码风格,
    举个 SwiftUI 的

  • 主 資深大佬 : dreamapplehappy

    @love 我今天搜索了一下 CPS 风格,感觉这两个应该还是有点区别的。对于 CPS 来说,传入的函数是用来获取原来函数的执行结果的,感觉应该是需要对这个结果做一些额外的操作;还有就是这个函数的位置应该也不需要是最后一个。而 Swift 的尾闭包要求是最后一个参数是一个函数,且一般可以不对之前函数的结果做什么操作。不知道我理解的对不对

  • 主 資深大佬 : dreamapplehappy

    @codehz 刚才回复错人了

  • 主 資深大佬 : dreamapplehappy

    @love 第一个回复,回复错了,是给你下的那位同学的,不好意思。
    ——
    对 JavaScript 来说,如果真的有这种语法的支持感觉也还不错。正好在 github 发现了一个类似的提议: https://github.com/samuelgoto/proposal-block-params

  • 主 資深大佬 : dreamapplehappy

    @wipbssldo 转换后哪个地方有问题,你说一下我看看

  • 主 資深大佬 : dreamapplehappy

    @no1xsyzy Ruby 没学习过,不是很了解。不过你说的跟在对象里面定义函数的语法有冲突,这个确实是的;如果真的要在实际中应用的话,可能要换种方式,或者检查一下上下文了。谢谢提醒。

  • 主 資深大佬 : dreamapplehappy

    @wobuhuicode 刚开始学习 SwiftUI 的时候确实有点不适应,不过慢慢也就习惯了

  • 主 資深大佬 : dreamapplehappy

    @lujjjh 首先学了个新的网络词语,“挽尊”,刚开始还不知道是啥意思

  • 資深大佬 : codehz

    @dreamapplehappy 但是这并不是我的原意,再加一个风就是为了避免歧义(因为这本来是编译原理的一种优化方法的内部表示,而并非由用户直接使用的语法格式)
    我那句话的重点是 livescript 的转换效果
    b,c <- a 1 2
    console.log b c
    编译成
    a(1, 2, function(b, c){
    return console.log(b(c));
    });
    而你例子里的写法在 livescript 可以写成
    b, c <-! a 1 “hello”
    d!
    do
    防吞空格<~ d
    d 1 “hello”
    do
    防吞空格<- d 1 “hello”
    e, f <-! d 1 “hello”
    g!
    (所以玩 livescript 最后死掉了)

  • 資深大佬 : wipbssldo

    @dreamapplehappy 你这个示例贴反了吧?没看懂,你把一个尾随闭包的写法转换成一般闭包的写法?

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具