本文对比了 TypeScript 与 Nim 在混合使用时的开发细节。重点分析了调试工具、类型系统一致性、方法调用语法(Method call syntax)及内存管理等差异。探讨了 Nim 在类型重载和运算符重载方面的优势,以及其在调试体验和语法便利性上相比 TypeScript 的不足。
对TypeScript与Nim混合开发的几点思考
我过往主要接触JavaScript与ClojureScript,类型系统知识储备不足,Nim是我首次长期接触静态类型语言。此前TypeScript仅被用作类型检查工具。
Nim存在的显著缺陷
JavaScript得益于Google的资金投入,其调试体验确实优越。
至于Nim,尽管多数报错可通过类型信息定位,但缺乏便捷的断点调试工具,常需依赖大量日志输出,且我对其底层机制探索尚浅。
我也尝试过Profiler,其导出内容为文本形式的调用栈与开销占比,清晰度不及Chrome DevTools。
VS Code的使用体验远逊于TypeScript生态,我最终转而使用Sublime。
Nim的echo函数缺乏自动空格处理,令我颇为困扰。
Nim未内置字符串插值,fmt究竟是函数还是宏尚不明确。
通常fmt"balbla {b}"这类语法可实现插值,但便捷性远不及语言原生支持。若字符串中包含{或},还需手动处理,使用时体验不佳。
若要在语言层面实现插值,JavaScript或CoffeeScript的处理方式不会如此繁琐。
Nim类型与运行时的对应关系
TypeScript虽拥有强大的类型系统,但我未启用strict模式,且部分依赖库并非TypeScript编写。
偶尔会遭遇类型声明与实际运行时行为不符的情况,令人费解。
Nim的类型与数据直接对应,除少数边缘情况外,二者保持一致。这意味着修复类型检查报错即解决了代码逻辑错误,让我感受到类型的可靠性。当然,许多静态语言皆如此。
方法调用语法
Nim非面向对象语言,其中的object大致对应C语言的struct,而非对象。
object内部仅包含数据,这种设计令我感到习惯。
然而从代码观感来看,Nim仍偏向支持OOP的语言风格,即大量使用a.b(c)这类方法调用语法,Nim称之为Method call syntax。
我近期才了解到D语言中已存在此特性,Wiki文档中明确提及:
https://en.wikipedia.org/wiki...
该特性对应的Nim代码如下:
type Vector = tuple[x, y: int]
proc add(a, b: Vector): Vector =
(a.x + b.x, a.y + b.y)
let
v1 = (x: -1, y: 4)
v2 = (x: 5, y: -2)
v3 = add(v1, v2)
v4 = v1.add(v2)
v5 = v1.add(v2).add(v1)起初我未加留意,但在将部分代码迁移至TypeScript后,才体会到其灵活性。
在JavaScript中,继承、多态及依赖类结构是实现这些功能的前提,这意味着必须定义class,实例化后使用。
定义class意味着数据不再像JSON那样直观。
我使用OOP不多,但深思后大致理解,动态类型能实现JavaScript这般效果已属不易。
Nim的多态通过编译器判断类型实现,例如前文提到的add可对应多个add实现。
proc add(x: Y, y: Y): Z =
discard
proc add(x: P, y: R): Q =
discard后续遇到o.add(j, k)时,编译器即可根据类型实现多态。
当然,JavaScript通过class也能实现,但这强制将数据操作绑定在一起。
长期受Scheme影响的语言使用者,难以适应class这种臃肿的设计。
在有类型的前提下,此方案中重载自然成立,例如Nim中对类型A定义equality判断的写法如下:
type A = object
x: number
proc `==`(x, y: A): bool =
discard未耦合在一起,意味着我引用的A类型在另一项目中可自行扩展,且基于类型机制,不会修改原始模块,不影响其他引用A的代码。
这一点导致我的代码从Nim转译至TypeScript时颇为头疼,因为定义数据结构访问与判断需重载这些array accessing和equality操作,我一时难以想出TypeScript中的对应方案,只能利用mutable data在运行时强行模拟。
Nim中则很简单,我对[]进行重载后,即可直接ys[index]使用:
proc `[]`[T](xs: MyList[T], idx: number): T =
discard对于iterator场景亦类似,定义iterator后直接编写for..in即可。
iterator在JavaScript中也存在,只是Nim中许多运算符均可自定义重载。
其好处明显,例如重构内部实现后,使用方基本无需调整。
而在JavaScript中,我长久以来习惯直接面对Array与Object。
未接触过Java与C#,但此类玩法与Haskell相似,Haskell从class生成instance时可定义函数,十分灵活。
(具体Haskell type class的高阶玩法尚无法熟练运用。)
不过Nim相比之下,虽简化了语法,但编码中的语法糖十分便利。
正因缺失此功能,导致我对Clojure与TypeScript均感到些许不适。
动态数据的类型处理
转译代码时还发现,由于JSON与EDN极为便利,我在大量代码中直接使用Array和Map表示数据。这并非错误,但在Nim中,这些数据由明确类型表示,意味着Nim中存在明确的判断结构,即explicitly...。
反观我的TypeScript代码,大量使用Array.isArray,以及那个难以言表的typeof x === 'object'的尴尬处境。在习惯类型系统后,回顾起来深感不安,于是自动转向折腾instanceof的玩法以实现对应功能。
当然,JSON或EDN通用地表示各种数据,在通用性方面确实优秀。跨项目调用数据时,这些动态类型作为直接通用的结构,在Nim中通常涉及类型转换,编写略显麻烦。
我尚难衡量哪种方案更优,但对于底层类库,我希望具备明确类型。
内存相关问题
因需编译至C运行,Nim中的数据结构多少涉及内存部分。
好在Nim中指针绝大部分已封装为ref,无需过多操心。
主要感觉是不同数据结构间的性能差异较易体现。
在JavaScript端,JIT常暗中协助优化,自行写出问题不易察觉。
我感觉到若早使用Nim,对算法与性能的模糊感会减轻许多。
能触及内存,也意味着内存管理会遇到问题。我之前遇到似乎在使用macro时,语言内部代码出错导致illegal memory access,令人无助。论坛建议编译Nim编译器本身并打log以获取细节,体验新奇,反正V8我未曾带参数编译过...。
至少目前所用场景无需清晰了解内存布局细节,日后再看吧...。
部分语法细节
Nim中的语法糖较多,带有使用CoffeeScript时的轻快感,例如JavaScript目前难以添加语言级range,而在Nim中直接使用..或..<即可生成range:
for i in 0...<n:
echo i然后if在Nim中虽不如CoffeeScript顺手,但仍是表达式:
let a = if b:
c
elif d:
e此类代码转至TypeScript后立刻变长,我更不愿牺牲可读性使用三元表达式。
当然与CoffeeScript相比,Nim仍需考虑类型,灵活性稍逊。
关于代码格式化,Nim内置nimpretty命令。
我未多用,仅试了一下,速度确实很快。
不过我写代码时已精确管理缩进与空格,再引入似乎没必要,也不如手写灵活。
其他
TypeScript的强大毋庸置疑。为此我对AssemblyScript颇为期待。
但随着Nim带来的这些感受,我也产生了一些疑问。
例如基于WebAssembly,我们未来或许拥有更好的浏览器语言,何为更好?
一方面需兼顾Web应用大量界面处理场景,另一方面需高性能与灵活性,单纯AssemblyScript这般,总觉尚不足够。
以上为TypeScript与Nim交替使用细节分析的详细内容,更多关于TypeScript与Nim使用的资料请关注本站其它相关文章!