原文
我最近决定在
"系统编程"
领域试些
小众语言
.我已用了
Java,Dart
和
Kotlin
等
高级语言编程
多年了(并试了许多其他
相同级别
或更高级的语言),需要扩大视野,因为对
某些类型
应用,
这些语言
并不是最好的工具.
这篇博文中,我想重点介绍
D语言
这里,经过
一些
初步实验,它比
其他语言
更能引起注意.
我还尝试了
Zig
和
Nim
12,但觉得它们
不适合
我,至少
现在
是这样.
当然,我已试过
Rust
,但是
Rust
虽然在很多方面都是个
天才语言
,但并没有真正让我对
编写代码
感到兴奋.相反,一想到
周末
要花时间与
借用检查器
作斗争,我就充满了
恐惧
.
我绝对会在工作环境中使用
Rust
(且已这样了),因为它的
安全保证
(不仅是
内存安全
,还有
资源
和线程安全)和
出色
的性能(在
低内存消耗
和
原始速度
方面),但对
业余
爱好项目,谢谢.
在我看来,
Nim
(另一
非常有趣
语言),在
另一端
走得太远了,
安全
不如
速度和快乐
重要.因此,如果你喜欢它(
速度
非常快,创建
微小
二进制文件且使用
很少
的内存),它可能只是适合你的语言.
Zig
有很多承诺,但目前还没有
准备
好.尽管它专注于
简单性
,但也
非常冗长且难以正确
使用.
D
似乎是个
很好的平衡
.它很容易
熟悉
,同时
有些
非常有趣的功能.它已
存在
了足够长的时间,已足够
稳定
.
本文,我想分享我所学到的东西,
重点
是
元编程
及
单元测试
.
D简介
在
2023
年,D并不是一门新语言.它自
2001
年以来一直存在,但从那时起已有了很大的发展,特别是自
2010
年左右的
D2
版本稳定以来.
它有
3个
不同且维护良好的
编译器
,下载页:
1,
DMD
是用
D
自身编写的
参考
编译器.
2,
GDC
是D的
GCC
前端.
3,基于
LLVM
的
LDC
.
DMD
一般用来
更快编译
(事实上,它可能是
生产级语言
中最快
编译器
之一),但
其他两个
一般更擅长
优化
运行时速度.
在介绍
D的功能
方面D语言旅游做得
非常出色
,而D的
Gems
部分
特别有趣
,因为它展示了
D有
的,而大多数
其他语言
所没有的东西,如
(UFCS)
统一函数调用语法,域保护,
(CTFE)
编译时函数求值,(如
@safe,@nogc,@mustuse
)属性等等.
另见包括消息传递和线本存储的
多线程节
,用它们来共同支持使用类似
Actor
模型来编写
并发代码
.
讨论更高级功能前,先展示一些
D示例
.
下面显示了
D切片
的实际效果:
importstd.stdio : writeln;voidmain(){int[] test =[3,9,11,7,2,76,90,6];
test.writeln;writeln("First element: ", test[0]);writeln("Last element: ", test[$ -1]);writeln("Exclude the first two elements: ",
test[2.. $]);writeln("Slices are views on the memory:");auto test2 = test;auto subView = test[3.. $];
test[]+=1;//将每个元素递增1
test.writeln;
test2.writeln;
subView.writeln;//创建空切片assert(test[2..2].length ==0);}
编译并运行它:
dmd -of=slices slices.d
./slices
[3,9,11,7,2,76,90,6]
First element:3
Last element:6
Exclude the first two elements:[11,7,2,76,90,6]
Slices are views on the memory:[4,10,12,8,3,77,91,7][4,10,12,8,3,77,91,7][8,3,77,91,7]
还可直接
dmd -runfile.d
或
rdmd
(
DMD
自带),从源码运行
D程序
.甚至可按脚本运行:
#!/usr/bin/env rdmd
它显示了许多有趣的
特征
.
1,
test.writeln
与
writeln(test)
相同.这就是
UFCS
.
2,
test[$-1]
,显示了如何在
[]
中按
数组/切片
长度使用
$符号
.
3,
test[2..$]
,类似同样使用
$
的
Go
的
典型切片
.
4,
test[]+=1
,显示了可由
编译器
优化的
向量运算
.
5,
assert(test[2 .. 2].length == 0);
,
D
断定,稍后用来
测试单元
.
相当不错.
D元编程
D有许多
元编程
功能.
元编程
是针对程序自身编程的编程.
Lisp
可能是使用
宏元编程
的先驱,但
宏
并不是
元编程
的唯一方法.
如,如下例所示,
D
有允许在
编译时
检查类型的
模板
,以
特化
函数:
@safe:autoconcat(T)(T lhs, T rhs){staticif(is(T:double)){//T可转换为双精return lhs + rhs;}else{//'~'一般是D中的`连接`符号return lhs ~ rhs;}}
unittest {assert(2.concat(3)==5);assert(4.2.concat(0.8)==5.0);assert("Hello".concat(" D")=="Hello D");}
运行
单元测试
:
dmd -w -main -unittest -run tests.d
//1个模块通过单元测试
该示例有点傻,因为D支持
重载符号
这里,所以只能这样.
如果熟悉
Java
,
concat
类似
通用静态方法
,但与
Java
不同,
D
允许
编译时
检查类型,因此可专门针对
某些类型
特化函数.
static if
是编译时执行的
if
块,
运行时
不存在.
注意,模板有
两个
参数列表:一个包含
编译时参数
,另一个包含
运行时参数
.如果
D编译器
可推导
编译时参数
,则可省略它.
可用
!
符号显式提供编译时参数.
如,
std.conv
标准模块中的
to
模板,把类型当参数,但因为一般
无法推导
,因此几乎总是
显式传递
:
unittest {importstd.conv: to;assert(42.to!string =="42");}
而这只是最基本的
D模板
.
还可用
template
关键字来
执行
更高级操作,如
生成多个函数
:
templateBiDirectionalConverter(T1, T2){importstd.conv: to;
T2 convert(T1 t){return t.to!T2();}
T1 convert(T2 t){return t.to!T1();}}
unittest {
alias StringIntConv = BiDirectionalConverter!(string,int);assert(StringIntConv.convert("20")==20);assert(StringIntConv.convert(20)=="20");}
std.conv
中的八进制(octal)模板,用来在D中声明编译时的
八进制
:
voidmain(){importstd.stdio: writeln;importstd.conv;writeln(octal!"750");}
运行:
dmd -run tests.d
488
强烈建议浏览
D模板
教程,以
了解
更多信息.
D中的
另一个
模板是
插件
模板.它是一个允许好像在
周围域内
编写它一样,
直接
在
调用点
粘贴代码的
复制和粘贴模板
.
mixin templateAbcd(T){
T a, b, c, d;}
unittest {
mixin Abcd!int;
a =10;assert(a ==10);assert(b ==0);assert(c ==0);assert(d ==0);}
最后,还可用
串插件
这里生成
代码串
:
///用T类型的`a,b`和c字段构建`一个结构`.
string abcStruct(T)(string name){return"struct "~ name
~" { "~ T.stringof ~" a; "~ T.stringof ~" b; "~ T.stringof ~" c; "~" }\n";}
unittest {mixin(abcStruct!string("StringStruct"));mixin(abcStruct!int("IntStruct"));auto abcstr =StringStruct("hey","ho","let's go");assert(abcstr.a =="hey");assert(abcstr.b =="ho");assert(abcstr.c =="let's go");auto abcint =IntStruct(42);assert(abcint.a ==42);assert(abcint.b ==0);assert(abcint.c ==0);}
D可用
-mixin
标志创建包含
编译过程
中生成的所有
插件
的
文件
:
dmd -w -main -unittest -mixin=mixins.d -run tests.d
1个模块通过单元测试
现在,查看
mixins.d
文件,找到D编译器生成的
结构
:
//测试.d中扩展.d(67)structStringStruct{ string a; string b; string c;}//测试.d中扩展.d(68)structIntStruct{int a;int b;int c;}
或,用pragma
编译指示
,以便编译时
D
仅打印
生成代码
:
pragma(msg, abcStruct!double("DoubleStruct"));//dmd -w -main -of=tests tests.d//结果:structDoubleStruct{double a;double b;double c;}
更多的
mixin
技巧
官方D文档中的
(Parser)
代码生成示例,显示了编译时很容易解析
串
来生成
常量配置数据
.
单元测试
前例中,使用
unittest
块来演示D的一些功能.我想很明显,
编译单元
中一般不
包含
这些块中代码,因此编译器
运行
测试时,必须传递
-unittest
选项给
编译器
(要实际
运行测试
,或执行生成的
二进制文件
,加上
-run
选项).
回顾下,如下
单元测试
:
unittest {assert(2+2==4);}
把上面的4更改为5并
运行代码
:
dmd -w -main -of=tests -run tests.d
使用
-main
选项,以便编译器在没有
函数
时生成空
main
函数.
-w
标志,按错误对待警告,
-of
来命名
输出文件
.用
--help
查看
所有选项
.
如果
不打印内容
,则所有
测试
都正常.即没有
运行
测试.
现在用
-unittest
重试:
dmd -w -main -unittest -run tests.d
`tests.d(18):[unittest]unittest`失败
`1/1`模块失败的单元测试
输出非常简单.它只是告诉你有
多少模块
的测试失败了,及
断定失败
的文件和行.
对
快速测试
来说不错,但
最好
告诉失败的
真正原因
论坛.
如,这是我
想出
的一个显示
失败断定
期望结果和实际结果的
小模板
,来使
断定
更强大:
autoassertThat(string desc, string op, T)(T lhs, T rhs){importstd.conv: to;const str ="assert(lhs "~ op ~" rhs, \""~
desc ~": \" ~ lhs.to!string() ~ \" "~ op ~" \" ~ rhs.to!string())";returnmixin(str);}
现在,
断定
如下:
unittest {
assertThat!("adding two and two","==")(2+2,5);}
运行
它:
dmd -w -main -unittest -run tests.d
`tests.d-mixin-20(20):[unittest]`加二加二`:4==5`
`1/1`模块失败的单元测试
真酷!
顺便,
D
单元测试一般来
验证函数
属性是否符合
期望
(D编译器一般会
推导
它们,给
每个函数
手动注解
大量属性
非常麻烦).
如,在
D中
实现
树
时,我试
测试
:
@safe @nogc nothrow pure unittest {auto tree =Tree([0,0,0,2],[10,11,12,13]);
assertThat!("children(2) basic case","==")(tree.children(2),[3,-1]);}
仅当按
@safe @nogc nothrow pure
注解,推导
unittest
中使用的函数时,才有效(编译器会
传递性
检查这些函数).
结果
如下
:
myd dmd -unittest -run source/app.d
...一堆错误略...
很有意思!
另一个
常见
用例是只运行
单个测试
,编译器不支持,但你可自己做,正如
@jfondren
在
D论坛
上所示:
moduletester1;
unittest {assert(true);}
unittest {assert(!!true);}
unittest {assert(1!=1);}
unittest {assert(1>0);}version(unittest){booltester(){importstd.meta : AliasSeq;importstd.stdio : writef, writeln;
alias tests = AliasSeq!(__traits(getUnitTests, tester1));staticforeach(i;0.. tests.length){
writef!"Test %d/%d ..."(i +1, tests.length);try{
tests[i]();writeln("ok");}catch(Throwable t){writeln("failed");}}returnfalse;}
shared staticthis(){importcore.runtime : Runtime;
Runtime.moduleUnitTester =&tester;}}voidmain(){assert(false);//这不会运行}
运行它:
dmd -w -main -unittest -run tests.d
测试1/4...好
测试2/4...好
测试3/4...失败
测试4/4...好
非常整洁
,但可能不是你想要的.
这使用了如
getUnitTests
特征等
相当高级
的东西(
D
特征是
元编程
,如果你来自
Rust
或
Scala
,概念可能不一样)和
UDA
(编译时注解)这里.
dub包管理
最后
IDE
支持似乎还不错,但与
Java,Kotlin,Typescript
甚至
Rust
等主流语言相去甚远.
我首先试使用
emacs
(你需要获得d模式,然后安装
serve-d
这里,
LSP
服务器,也支持
VSCode
的D支持).
然后注意到
D
的
IntelliJ
插件非常强大,并且作为
Jebrains
产品的大用户,很好惊喜(一般,小众语言在
IntelliJ
中没有很好的支持)!
向
IntelliJ
插件的
开发者
致敬!它提供了非常好的
开箱即用
体验,来生成
片段
的
漂亮模板
,
代码浏览
(包括进入
Dstdlib
,非常适合学习),
内置文档
风格精美,有扫描器,因此在代码中显示
警告
,通过
dfmt
自动
格式化
,内置支持
dub
.
如果用
d-unit
作为
依赖
,甚至可运行
测试
.
D
,还支持
CPU
和内存分析,及非常好的文档工具
ddoc
这里,与在
Rust
中一样,可在
编译时
执行
D
文档,确保
文档示例
总是有效!
我厌倦了像
Java
这样基于
VM
的语言,并且不太喜欢编写
Rust
,
D
可能会成为我
下个
最喜欢的
语言
.
版权归原作者 fqbqrr 所有, 如有侵权,请联系我们删除。