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

2021-04-07 21:10:54 +08:00
 dreamapplehappy

最近业余时间在学习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 的尾闭包语法

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

3281 次点击
所在节点    分享创造
38 条回复
no1xsyzy
2021-04-08 13:18:38 +08:00
@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
2021-04-08 13:24:10 +08:00
@lujjjh 不需要全局,React (不管 hooks ) render 函数其实就是这种想法,你写得还是脏了
只要把后面那个 closure call 出来的结果合并进去就成了
lujjjh
2021-04-08 13:31:52 +08:00
@no1xsyzy 你可能没有看全上下文。我当然知道 closure 返回数组的话很容易处理。

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

你可以看到我的代码里 L23-L28 在使用的时候完全没有手写数组。
no1xsyzy
2021-04-08 14:21:07 +08:00
@lujjjh 都已经动了编译器了,重新发明下宏就行了,不是什么大问题。
lujjjh
2021-04-08 14:53:53 +08:00
@no1xsyzy Talk is cheap,我的核心观点还是在 #16 。像 Svelte 那样动编译器跟动语法是两码事,动语法带来的问题太多了,工具链是问题,方言能不能被大众接受也是问题。React Hooks 实现得这么黑本质上也是为了在 js 的限制下设计出一套相对好用的 DSL,否则完全可以设计成 Vue Composition API 的样子,改动语法可能性就更多了。
no1xsyzy
2021-04-08 15:11:20 +08:00
@lujjjh 我是说主题已经动了编译器了,然后你还束手束脚; React hooks 这点问题也大,都已经 JSX 了其实没必要完全遵守 JS 的约束,有点又当又立的意思在。
宏是 JS 很明显缺失的一环( JavaScript 是一个没能成为 Lisp 的 Lisp 方言),又其实有点大家都不敢抢先做的意思。
lujjjh
2021-04-08 16:12:31 +08:00
@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
2021-04-08 16:51:00 +08:00
@dawn009
尾随闭包不只是一种代码风格,
举个 SwiftUI 的🌰
ScrollView(.vertical) {
VStack(spacing: 10) {
ForEach(0..<100) {
Text("Item \($0)")
.font(.title)
}
}
}

ScrollView(.vertical, content: {
VStack(spacing: 10, content: {
ForEach(0..<100, content: {
Text("Item \($0)")
.font(.title)
})
})
})

两种写法都可以正常运行,但第一种明显更简洁。
Swift 不强制我们使用尾随闭包,只是给我们提供了更灵活的选择,
我们可以完全不用它,我们也可以用它写出表达力更强、更简洁的代码。
dreamapplehappy
2021-04-09 00:03:55 +08:00
@love 我今天搜索了一下 CPS 风格,感觉这两个应该还是有点区别的。对于 CPS 来说,传入的函数是用来获取原来函数的执行结果的,感觉应该是需要对这个结果做一些额外的操作;还有就是这个函数的位置应该也不需要是最后一个。而 Swift 的尾闭包要求是最后一个参数是一个函数,且一般可以不对之前函数的结果做什么操作。不知道我理解的对不对😂。从你的回复中学习到一些新的东西,谢谢回复。关于 CPS 我看的文章是这篇 [CPS 变换与 CPS 变换编译]( https://zhuanlan.zhihu.com/p/22721931)
dreamapplehappy
2021-04-09 00:11:58 +08:00
@codehz 刚才回复错人了😂,回复给你楼上哪位同学了,尴尬;我粘贴过来吧
------
我今天搜索了一下 CPS 风格,感觉这两个应该还是有点区别的。对于 CPS 来说,传入的函数是用来获取原来函数的执行结果的,感觉应该是需要对这个结果做一些额外的操作;还有就是这个函数的位置应该也不需要是最后一个。而 Swift 的尾闭包要求是最后一个参数是一个函数,且一般可以不对之前函数的结果做什么操作。不知道我理解的对不对😂。从你的回复中学习到一些新的东西,谢谢回复
dreamapplehappy
2021-04-09 00:14:40 +08:00
@love 第一个回复,回复错了,是给你楼下的那位同学的,不好意思。
------
对 JavaScript 来说,如果真的有这种语法的支持感觉也还不错。正好在 github 发现了一个类似的提议: https://github.com/samuelgoto/proposal-block-params
dreamapplehappy
2021-04-09 00:17:54 +08:00
@wipbssldo 转换后哪个地方有问题,你说一下我看看😂
dreamapplehappy
2021-04-09 00:33:26 +08:00
@no1xsyzy Ruby 没学习过,不是很了解。不过你说的跟在对象里面定义函数的语法有冲突,这个确实是的;如果真的要在实际中应用的话,可能要换种方式,或者检查一下上下文了。谢谢提醒。
dreamapplehappy
2021-04-09 00:34:34 +08:00
@wobuhuicode 刚开始学习 SwiftUI 的时候确实有点不适应,不过慢慢也就习惯了😂
dreamapplehappy
2021-04-09 00:44:35 +08:00
@lujjjh 首先学了个新的网络词语,“挽尊”,刚开始还不知道是啥意思😂。
这个项目开始的时候确实是想练习一下写一个简单的编译器,也选择了一个我觉得还算有趣的练习方向。如果在实际中使用的话,确实要考虑很多的问题。比较好的解决方案是借助 Babel 进行语法的转换,可以参考: https://lihautan.com/creating-custom-javascript-syntax-with-babel/ 这篇文章。
你的建议很有帮助,再次谢谢你的建议。
codehz
2021-04-09 01:44:45 +08:00
@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
2021-04-09 10:33:49 +08:00
@dreamapplehappy 你这个示例贴反了吧?没看懂,你把一个尾随闭包的写法转换成一般闭包的写法?
ericgui
2021-04-12 01:10:28 +08:00
https://juejin.cn/post/6844904199751221262

这个文章讲的还可以。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/768848

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX