
哈Ha!
现在已经有一段时间了,我一直在编写我的游戏框架-这样的灵魂工程。 而且由于灵魂需要选择自己喜欢的东西(在这种情况下,是您喜欢写的东西),所以我的选择落在了尼姆身上。 在本文中,我想专门讨论一下nim,它的功能,优缺点,而gamedev的主题仅设置了我的经验背景-我解决了什么任务,出现了什么困难。
从前,当草更绿,天空更清洁时,我遇到了尼姆。 不,不是那样。 从前,我想开发游戏来编写自己最酷的游戏-我认为很多人都经历了这一过程。 在那些日子里,Unity和Unreal Engine刚刚开始出现在听证会上,并且好像还不是免费的。 我之所以没有使用它们,不是因为贪婪,而是因为我自己写所有东西的愿望,从而完全从零开始,从头开始创建游戏世界。 第一个 零字节。 是的,很长一段时间,是的,这很困难,但是过程本身带来了乐趣-但是幸福还需要什么呢?
带着Straustrup和Qt武装起来,我最大程度地喝了屎,因为,首先,我不是世界上十分了解C ++的10个人中的一个,其次,加号积极地使我的手忙脚乱。 我认为没有理由再重复已经为我写的引人注目的内容:
我如何找到世界上最好的编程语言。 第一部分
我如何找到世界上最好的编程语言。 第二部分
我如何找到世界上最好的编程语言。 Yo部分(2.72)
当您自由地编写代码,几乎没有思考,无需等待每次启动之前将内核转储,当我们眼前添加功能时,这是一个疯狂的嗡嗡声,现在我们可以做到,现在,请告诉我,这对我有什么影响我什至都不会错过模板吗? 生产力是做事的程序员的主要目标,也是他使用工具的唯一任务。
使用C ++时,我一直在思考如何写自己想要的东西,而不是写给我的东西。 所以我改用了尼姆。 故事结束了,让我在nim工作了几年后与您分享我的经验。
那些不认识的人的一般信息
- 发烧友开发的开源编译器(MIT)。 该语言的创建者是Andreas Rumpf(Araq)。 第二位开发人员是Dominik Picheta(dom96),他撰写了《 行动中的尼姆 》一书。 另外,不久前, Status开始赞助该语言的开发,因此nim拥有了2名全职开发人员。 除了他们,其他人当然也会有所贡献。
- 1.0版最近已发布 ,这意味着该语言是稳定的,不再需要“重大更改”。 如果以前您不想使用不稳定版本,因为更新可能会破坏应用程序,那么现在是时候在项目中尝试使用nim了。
- Nim编译(或转置)为C,C ++(进一步编译为本机代码)或JS(具有某些限制)。 因此,在FFI的帮助下,您可以使用所有现有的C和C ++库。 如果nim上没有必要的软件包-寻找s或pluses。
- 最接近的语言是python(乍看之下是语法)和D(通过功能来看)-IMHO
该文件
这实际上是不好的。 问题:
- 该文档散布在不同的来源
- 该文件
狗屎 不能完全描述语言的所有功能 - 文档有时过于简洁。
示例:如果要编写多线程应用程序,则有很多核心,但是无处可去。
这是有关流的官方文档部分 。 不,您知道, 线程是语言的独立主要部分,在编译时甚至需要在--threads:on
标志中包含其功能。 在那里,取决于垃圾回收器,各种共享内存和锁,线程安全性,特殊的共享模块,共享堆或线程本地堆,地狱知道其他什么。 我怎么知道这一切? 没错,从实际行动的nim,论坛,堆栈溢出,电视以及邻居(通常在任何地方,而不是官方文档中)的角度出发。
还是有所谓的。 “注释”-在使用模板等时,通常在您需要传递回调或仅代码块的任何地方,都可以很好地工作。 我在哪里可以读到它? 是的,在实验功能手册中 。
同意,收集有关各种非情报来源的信息仍然是一种荣幸。 如果用nim编写,则必须这样做。
在论坛和github问题上,有关于改进文档的建议,但是事情没有进展。 在我看来,似乎缺少某种硬手,它会说“一切,社区,拿铁锹去耙这堆……精巧而分散的文字。”
幸运的是,我击中了自己,所以我向您展示了尼姆冠军的名单。
该文件
- 教程1 , 教程2-从他们开始
- 行动中的尼姆是一本解释性的书,确实很好地解释了语言的许多方面,有时甚至比语言要好得多。 文件资料
- Nim手册 -实际上是手册-几乎所有内容都已描述,但没有
- Nim实验手册 -为什么不真正在单独的页面上继续文档?
- 索引 -链接到此处的所有内容,即通常可以在nim中找到的所有内容。 如果您在教程和手册中找不到所需的内容,则肯定会在索引中找到它。
经验教训
帮忙
- IRC是nimmers?的主要栖息地……,在Discord和Gitter上播放。 我从未使用过IRC(现在仍然不使用它)。 通常,这是一个非常奇怪的选择。 还有鸽子信给他...好吧,开玩笑。
- Nim论坛该论坛的功能很少,但是1)在这里您可以找到答案2)在这里您可以问一个问题,如果第1项不起作用3)答案的可能性大于50%4)语言开发人员正坐在论坛上并正在积极响应。 顺便说一下,该论坛是用nim编写的,因此没有任何功能
- Nim电报组 -可以提出问题,但不能获得答案。
- 还有一个俄罗斯电报小组,如果您对nim感到厌倦并且不想听到任何有关它的信息,则应该去那里:)(部分是个玩笑)
游乐场
配套
从其他语言切换到nim
你喜欢什么
列出该语言的所有功能没有任何意义,但是这里有一些功能:
分形复杂度
Nim为您提供了“复杂程度”。 您可以编写高级代码。 您可以使用原始指针进行对接,并尽一切可能attempt to read from nil
。 您可以嵌入C代码。 您可以在汇编程序中编写插入内容 。 您可以编写过程(静态调度)。 不够-有“方法”(动态分配)。 还有吗 有泛型,也有模仿功能的泛型。 有模板-一种替换机制,但不像C ++那样呕吐(这里是否有宏-它仅仅是文本替换,还是更聪明?)。 最后有宏-就像IDDQD,它们启用了上帝模式,并允许您直接使用AST并从字面上替换语法树的各个部分,或者根据需要自己扩展语言。
就是说,在“高”级别上,您可以编写不知道的地狱之词和悲痛,但没有人禁止您进行任何复杂的欺诈。
发展速度
学习曲线不是曲线。 这是直接的。 通过安装nim,您将在第一分钟开始您的第一个问候世界,在第一天,您将编写一个简单的实用程序。 但是在接下来的几个月中,您将学到一些东西。 例如,我从程序开始,然后我需要方法,过了一会儿泛型对我非常有用,最近我发现模板的功能非常丰富,而我却完全不使用宏。 与相同的rust或c ++相比,与nim合并要容易得多。
包装管理
有一个名为nimble的软件包管理器,可以安装,卸载,创建软件包和加载依赖项。 创建软件包(=项目)时,可以使用nimble编写不同的任务(使用nimscript,它是在VM上执行的nim的子集),例如,生成文档,运行测试,复制资产等。 Nimble不仅放置必要的依赖项,而且还允许您为项目配置工作环境。 也就是说,从广义上讲,敏捷是CMake,它不是由变态者而是由普通人编写的。
可读性和表现力
从外部看,nim与带类型注释的python非常相似,尽管nim根本不是python。 Python主义者将不得不忘记动态类型,继承,装饰器和其他乐趣,并且通常会重新构造他们的思维。 不要尝试将您的python经验转移给nim,因为两者之间的差异太大。 一开始,我真的很想要异构集合和带有装饰器的mixin。 但是后来你以某种方式习惯了艰苦的生活:)
这是一个nim程序示例:
type NumberGenerator = object of Service # this service just generates some numbers NumberMessage = object of Message number: int proc run(self: NumberGenerator) = if not waitAvailable("calculator"): echo "Calculator is unavailable, shutting down" return for number in 0..<10: echo &"Sending number {number}" (ref NumberMessage)(number: number).send("calculator")
模块化
一切都分为模块,您可以根据需要导入-仅导入某些字符,或者仅导入某些字符,或者导入全部或全部字符,或者不导入任何字符,并强制用户指定module.function()
的完整路径,并使用其他名称进行导入。 当然,所有这些变化在辩论“哪种编程语言更好”中非常有用,在您的项目中,您将安静地import mymodule
编写import mymodule
而不会提及其他选项。
方法调用语法
可以通过不同方式记录函数调用:
double(2) double 2 2.double() 2.double
一方面,现在每个人都……按自己喜欢的方式写作(当然,每个人都喜欢以不同的方式,甚至在一个项目的框架内也以不同的方式)。 但是随后所有函数都可以编写为方法调用,从而大大提高了可读性。 在python中,它可以是:
list(set(some_list))
nim中的相同代码可以更逻辑地重写:
some_list.set.list #
面向对象
OOP尽管存在,但在plus和python上却与之不同:对象和方法是不同的实体,并且很可能存在于不同的模块中。 此外,您可以为int
等基本类型编写方法
proc double(number: int): int = number * 2 echo $2.double() # prints "4"
另一方面,在nim中有封装(在nim中模块的第一条规则是不告诉任何人没有星号的标识符)。 这是标准模块的示例:
# sharedtables.nim type SharedTable*[A, B] = object ## generic hash SharedTable data: KeyValuePairSeq[A, B] counter, dataLen: int lock: Lock
SharedTable*
类型标有星号,表示它在其他模块中“可见”并且可以导入。 但是这里data
, counter
和lock
是私有成员,而sharedtables.nim
不能从外部访问。 当我决定为SharedTable
类型编写一些其他函数(例如len
或hasKey
,这让我感到非常高兴,发现我无法访问counter
或data
,并且“扩展” SharedTable
的唯一方法是编写自己的 ,加上bl
通常,与同一个python(根据个人经验)相比,使用继承的频率要少得多,因为存在方法调用语法(请参见上文)和对象变量(请参见下文)。 nim路径是组合而不是继承。 多态性也是如此:在nim中,有一些方法可以在后继类中重写,但是必须在编译期间使用--multimethods:on
标志明确指定这些--multimethods:on
。 也就是说,默认情况下,方法不起作用,这稍微鼓励了没有它们的工作。
编译时执行
常量-在编译阶段计算某些内容并将其“缝制”成结果二进制文件的能力。 凉爽舒适。 通常,nim与“编译时间”有特殊关系,甚至还有一个when
关键字-就像if
,但比较是在编译阶段。 你可以写类似
when defined(SDL_VIDEO_DRIVER_WINDOWS): import windows ## oldwinapi lib elif defined(SDL_VIDEO_DRIVER_X11): import x11/x, x11/xlib ## x11 lib
尽管在编译阶段可以执行的操作受到限制(例如,无法进行FFI调用),这非常方便。
参考类型
引用类型-C ++中shared_ptr的类似物,垃圾收集器将处理它。 但是,您也可以在方便时自行调用垃圾收集器。 或者,您可以尝试使用其他垃圾收集器选项。 或者,您可以完全禁用垃圾收集器并使用常规指针。
理想情况下,如果不使用原始指针和FFI,则不太可能出现分段错误。 实际上,到目前为止,任何地方都没有FFI。
Lambdas
有匿名过程(在python中又称为lambdas),但是与匿名过程中的python不同,您可以使用以下语句:
someProc(callback=proc(a: int) -> int = var b = 5*a; result = a)
例外情况
有一些异常,它们抛出起来非常不便:python raise ValueError('bad value')
,nim raise newException(ValueError, "bad value")
。 没什么不寻常的-尝试一下,除了最后,一切都和其他人一样。 我作为例外(而不是错误代码)的支持者感到高兴。 顺便说一下,您可以为函数指出它们可以抛出哪些异常,编译器将检查以下内容:
proc p(what: bool) {.raises: [IOError, OSError].} = if what: raise newException(IOError, "IO") else: raise newException(OSError, "OS")
泛型
泛型非常有表现力,例如,您可以限制可能的类型
proc onlyIntOrString[T: int|string](x, y: T) = discard # int string
您可以将类型作为参数传递给它-它看起来像一个普通函数,但实际上是一个泛型:
proc p(a: typedesc; b: a) = discard # is roughly the same as: proc p[T](a: typedesc[T]; b: T) = discard # hence this is a valid call: p(int, 4) # as parameter 'a' requires a type, but 'b' requires a value.
范本
模板就像C ++中的宏一样,只是正确地完成了:)-您可以安全地将整个代码块转移到模板中,而不用认为替换会破坏外部代码中的某些内容(但是您可以再次,如果确实需要,可以将其弄乱)。
这是一个示例app
模板,根据变量的值,它调用以下代码块之一:
template app*(serverCode: untyped, clientCode: untyped) = # ... case mode of client: clientCode of server: serverCode else: discard
使用do
我可以将整个块传递给模板,例如:
app do: # serverCode echo "I'm server" serverProc() do: # clientCode echo "I'm client" clientProc()
互动壳
如果您需要快速测试某些东西,即调用“解释器”或“ nim shell”的能力(就像您在不带参数的情况下运行python
)。 为此,请使用nim secret
命令或下载inim软件包。
联邦调查局
FFI-与C / C ++中的第三方库进行交互的能力。 不幸的是,要使用外部库,您必须编写一个包装程序,以说明从何处以及从何处进行导入。 例如:
{.link: "/usr/lib/libOgreMain.so".} type ManualObjectSection* {.importcpp: "Ogre::ManualObject::ManualObjectSection", bycopy.} = object
有一些工具可以使该过程成为半自动的:
什么不喜欢
难点
太多了 该语言被认为是极简主义的,但是现在离真相还很远。 例如,为什么我们要对代码重新排序 ?
冗余度
很多东西: system.addInt- “将整数转换为其字符串表示形式并将其附加到结果中”。 在我看来,这是一个非常方便的功能,我在每个项目中都使用它。 这是另一个有趣的例子:fileExists和existFile( https://forum.nim-lang.org/t/3636 )
没有统一
“只有一种方法可以做到”-根本没有:
- 方法调用语法-根据需要编写函数调用
fmt
vs &
- camelCase和underscore_notation
- 这和他的(破坏者:是同一回事)
- 功能 vs 程序 vs 模板
虫子(没有包!)
大约有1400个 bug。 或者只是去论坛- 他们 不断发现一些错误。
稳定度
除了上一段之外,v1还意味着稳定性,对吗? 阿拉克语的创建者在这里飞到论坛上说:“伙计,我这里有另一个(第六个)垃圾收集器,它更凉,更快,更年轻,为您提供线程共享内存(哈哈,在此之前您遭受了苦难,使用拐杖),下载开发分支并尝试。 所有这些“哇,太酷了!这对凡人意味着什么?我们现在需要再次更改所有代码吗?” 似乎没有,所以我更新了nim,运行了一个新的垃圾收集器--gc:arc
,我的程序在编译c ++代码的阶段崩溃了(即不是在nim中,而是在gcc中):
/usr/lib/nim/system.nim:274:77: error: 'union pthread_cond_t' has no member named 'abi' 274 | result = x
太好了! 现在,我不必修复新代码,而必须修复旧代码。 那不是我选择nim时的运行方式吗?
很高兴知道我并不孤单
方法和多线程
默认情况下,multimethods和threads标志是关闭的-您不会 2019年 2020年编写具有重写方法的多线程应用程序? 如果在不考虑流程的情况下创建您的库,然后用户打开它们 ,那真是太棒了 ……哦,是的,有许多很棒的编译指示{.inheritable。}和{.base。}对于继承,以便您的代码不太简洁。
对象变体
您可以通过使用所谓的避免继承 对象变体:
type CoordinateSystem = enum csCar, # Cartesian csCyl, # Cylindrical Coordinates = object case cs: CoordinateSystem: # cs is the coordinate discriminator of csCar: x: float y: float z: float of csCyl: r: float phi: float k: float
根据cs
的值,您可以使用x,y,z字段或r,phi和k。
缺点是什么?
首先,为“最大选项”保留了内存-从而确保可以容纳为对象分配的内存。
其次,继承仍然更加灵活-您始终可以创建后代并添加更多字段,并且在对象变体中,所有字段都在一个部分中严格定义。
第三,最让人生气的是您不能“重用”不同类型的字段:
type # The 3 notations refer to the same 3-D entity, and some coordinates are shared CoordinateSystem = enum csCar, # Cartesian (x,y,z) csCyl, # Cylindrical (r,φ,z) Coordinates = object case cs: CoordinateSystem: # cs is the coordinate discriminator of csCar: x: float y: float z: float # z already defined here of csCyl: r: float phi: float z: float # fails to compile due to redefinition of z
做记号
引用一下:
- 用括号做是一个匿名过程
- 不用括号只是一段代码
一个表达式表示不同的事物__(ツ)_ /¯
何时使用
因此,我们具有函数,过程,泛型,多方法,模板和宏。 什么时候使用模板更好,什么时候使用程序? 模板还是通用? 功能或程序? 那么,宏呢? 我想你明白了。
自定义杂物
python中有装饰器,甚至可以应用于类甚至函数。
尼姆对此有一些实用说明。 这是什么:
敏捷
死了的人不会死。 在敏捷中,一堆项目已经很长时间没有更新了(在敏捷中,这就像死亡一样),并且它们不会被删除。 没有人关注此事。 向后兼容很明显,“您不能只是从萝卜中取出包装”,但仍然...好吧,谢谢,至少不像npm。
泄漏抽象
漏洞抽象的法则是这样的-您使用某种抽象,但迟早会在其中发现一个“漏洞”,这将使您进入一个较低的层次。 Nim是C和C ++的抽象,迟早您会在那儿失败。 赌你在那里不喜欢吗?
Error: execution of an external compiler program 'g++ -c -w -w -fpermissive -pthread -I/usr/lib/nim -I/home/user/c4/systems/network -o /home/user/.cache/nim/enet_d/@m..@s..@s..@s..@s..@s..@s.nimble@spkgs@smsgpack4nim-0.3.0@smsgpack4nim.nim.cpp:6987:136: note: initializing argument 2 of 'void unpack_type__k2dhaoojunqoSwgmQ9bNNug(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA*, NU&)' 6987 | N_LIB_PRIVATE N_NIMCALL(void, unpack_type__k2dhaoojunqoSwgmQ9bNNug)(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA* s, NU& val) { nimfr_("unpack_type", "/home/user/.nimble/pkgs/msgpack4nim-0.3.0/msgpack4nim.nim"); |
tyObject_MsgStreamcolonObjectType ___ kto5qgghQl207nm2KQZEDA * S,NU&VAL){nimfr _( “unpack_type”, “/home/user/.nimble/pkgs/msgpack4nim-0.3.0/msgpack4nim.nim”); Error: execution of an external compiler program 'g++ -c -w -w -fpermissive -pthread -I/usr/lib/nim -I/home/user/c4/systems/network -o /home/user/.cache/nim/enet_d/@m..@s..@s..@s..@s..@s..@s.nimble@spkgs@smsgpack4nim-0.3.0@smsgpack4nim.nim.cpp:6987:136: note: initializing argument 2 of 'void unpack_type__k2dhaoojunqoSwgmQ9bNNug(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA*, NU&)' 6987 | N_LIB_PRIVATE N_NIMCALL(void, unpack_type__k2dhaoojunqoSwgmQ9bNNug)(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA* s, NU& val) { nimfr_("unpack_type", "/home/user/.nimble/pkgs/msgpack4nim-0.3.0/msgpack4nim.nim"); |
/usr/bin/ld: /home/user/.cache/nim/enet_d/stdlib_dollars.nim.cpp.o: in function `dollar___uR9bMx2FZlD8AoPom9cVY9ctA(tyObject_ConnectMessage__e5GUVMJGtJeVjEZUTYbwnA*)': stdlib_dollars.nim.cpp:(.text+0x229): undefined reference to `resizeString(NimStringDesc*, long)' /usr/bin/ld: stdlib_dollars.nim.cpp:(.text+0x267): undefined reference to `resizeString(NimStringDesc*, long)' /usr/bin/ld: stdlib_dollars.nim.cpp:(.text+0x2a2): undefined reference to `resizeString(NimStringDesc*, long)'
所以
我是一个愚蠢的程序员。 我不想知道GC的工作原理,存在的地方以及如何链接,将其缓存在何处以及如何清除垃圾。 它就像一辆汽车-原则上,我知道它的工作原理,关于车轮定位,关于变速箱,我需要填充机油和东西,但总的来说,我只是想坐下来(快速)参加聚会。 机器不是目标,而是达到目的的手段。 如果它崩溃了,我不想陷入困境,而只是将其带到服务中(从某种意义上说,我将在github上打开该问题),如果他们能尽快解决它,那就太好了。
尼姆应该是这样的机器。 在某种程度上,他成为了他,但与此同时,当我沿着这辆车在高速公路上行驶时,我的车轮掉了下来,后视镜指向前方。 工程师追赶我,并随手附上一些东西(“现在,有了这种新的扰流板,您的汽车甚至更快了”),但是后备箱掉下来了。 你知道吗? 我还是喜欢这辆车,因为这是我所见过的所有车中最好的。