wc到D:712个字符(无单个分支)

阅读Hacker News上找到的“在Haskell上用80行程序击败Beat ”之后,我决定D可能会更好。我在 D.Note.per中写了wc 我建议翻译上面的文章

0xd34df00d,但他更愿意根据他的著作《用Haskell的20条线取胜:写你的Wc》来制作现在,这些文章正像重新生成“铸造的硬币”一样繁衍

程序


包含一个文件-34行和712个字符。

源代码
import std.stdio : writefln, File;
import std.algorithm : map, fold, splitter;
import std.range : walkLength;
import std.typecons : Yes;
import std.uni : byCodePoint;

struct Line {
	size_t chars;
	size_t words;
}

struct Output {
	size_t lines;
	size_t words;
	size_t chars;
}

Output combine(Output a, Line b) pure nothrow {
	return Output(a.lines + 1, a.words + b.words, a.chars + b.chars);
}

Line toLine(char[] l) pure {
	return Line(l.byCodePoint.walkLength, l.splitter.walkLength);
}

void main(string[] args) {
	auto f = File(args[1]);
	Output o = f
		.byLine(Yes.keepTerminator)
		.map!(l => toLine(l))
		.fold!(combine)(Output(0, 0, 0));

	writefln!"%u %u %u %s"(o.lines, o.words, o.chars, args[1]);
}


当然,他使用D标准库Phobos,但是为什么不呢?Phobos很漂亮,并且随每个D编译器一起提供,程序本身不包含任何if语句。在Haskell的wc实现中,使用了多个if。除了主程序外,D程序还包含三个其他小功能。我可以轻松地将所有功能放在一个范围链中,但是每行可能会超过80个字符。这是代码样式的基本原理。

性能


是D上的wc比coreutils wc快吗?不,但是我花了15分钟来编写我的版本(我不得不寻找walkLength,因为我忘记了函数的名称)。
资料档案线字节coreutils哈斯克尔d
应用程式469063.5毫秒±1.9毫秒39.6毫秒±7.8毫秒8.9毫秒±2.1毫秒
big.txt86264k4.7毫秒±2.0毫秒39.6毫秒±7.8毫秒9.8毫秒±2.1毫秒
vbig.txt170万96M658.6毫秒±24.5毫秒226.4毫秒±29.5毫秒1.102秒±0.022秒
vbig2.txt1210万671M4.4秒±0.058秒1.1秒±0.039秒7.4秒±0.085秒

记忆:
资料档案coreutils哈斯克尔d
应用程式2052千7228K7708K
big.txt2112K7512K7616千
vbig.txt2288千42620K7712K
vbig2.txt2360千50860千7736千
在Haskell上工作更快?当然,对于大文件,它使用多线程。对于小文件,GNU coreutils仍然胜出。在此阶段,我的版本很可能受到IO的限制,无论如何,它都非常快。

我不会说一种语言比另一种语言快。如果您花时间优化微基准测试,您很可能会获胜。但是在现实生活中却不是。但是我会争辩说,D中的函数式编程几乎可以赶上Haskell中的FP。

关于D的范围


range是可以迭代而无需接触基础集合(如果有)的抽象。从技术上讲,范围可以是实现一个或多个Range接口的结构或类。最简单的形式InputRange需要一个函数

void popFront();

和两个成员或属性

T front;
bool empty;

T是元素的一种通用类型,它在范围内进行迭代。

D范围是特定类型。当范围落入foreach语句时,编译器会进行一些小的修改。

foreach (e; range) { ... }

变成

for (auto __r = range; !__r.empty; __r.popFront()) {
    auto e = __r.front;
    ...
}

auto e =计算类型,并等于T e =。
考虑到这一点,很容易创建可被foreach使用的范围。

struct Iota {
	int front;
	int end;

	@property bool empty() const {
		return this.front == this.end;
	}

	void popFront() {
		++this.front;
	}
}

unittest {
	import std.stdio;
	foreach(it; Iota(0, 10)) {
		writeln(it);
	}
}

IOTA是范围的一个非常简单的示例。它充当生成器,而没有基础集合。从头到尾以1为增量迭代整数。该代码段显示了一些D语法。

@property bool empty() const {

@属性 属性允许您以与成员变量相同的方式使用函数(调用不带括号的函数)。最后的const属性意味着我们不会修改我们称为empty的实例数据。内置的单元测试可打印从0到10的数字。

另一个小功能是缺少显式构造函数。 Iota结构具有两个int成员变量。在测试的foreach语句中,我们创建一个Iota实例,就好像它具有接受两个整数的构造函数一样。这是结构文字。当编译器D看到这一点,但结构没有相应的构造函数时,则按照变量声明的顺序-结构成员将被分配为int。

这三个成员之间的关系很简单。如果empty为false,则保证在调用popFront之后,front可以在迭代之后返回一个新元素。调用popFront之后,空值可能会更改。如果为真,则意味着不再有要迭代的元素,并且对front的进一步调用无效。根据InputRange文档

  • 仅当空返回或将返回false时,才能正确计算front。
  • 可以调用多次front而不用调用popFront或以其他方式更改范围或基础数据,并且每次计算都得出相同的结果。

使用foreach表达式或循环不是很有功能。假设我们要过滤掉Iota的所有奇数。我们可以在ifeach块中放入if,但这只会使情况更糟。如果我们有一个接受范围的范围和一个决定一个元素是否合适的谓词,那就更好了。

struct Filter {
	Iota input;
	bool function(int) predicate;

	this(Iota input, bool function(int) predicate) {
		this.input = input;
		this.predicate = predicate;
		this.testAndIterate();
	}

	void testAndIterate() {
		while(!this.input.empty
				&& !this.predicate(this.input.front))
		{
			this.input.popFront();
		}
	}

	void popFront() {
		this.input.popFront();
		this.testAndIterate();
	}

	@property int front() {
		return this.input.front;
	}

	@property bool empty() const {
		return this.input.empty;
	}
}

bool isEven(int a) {
	return a % 2 == 0;
}

unittest {
	foreach(it; Filter(Iota(0,10), &isEven)) {
		writeln(it);
	}
}

过滤器也非常简单:需要Iota和函数指针。在构造Filter时,我们调用testAndIterate,它从Iota中获取元素,直到它为空或谓词返回false。这个想法是谓词决定要过滤的内容和要保留的内容。front和empty属性仅转换为Iota。真正完成这项工作的唯一一件事就是popFront。它返回当前项目并调用testAndIterate。就这样。这是一个过滤器实现。

当然,testAndIterate有一个while循环,但是在我看来,使用递归重写它只是愚蠢的。 D的出色之处在于,您可以为每个任务使用适当的方法。函数式编程是好的,并且经常炫耀,但有时不是。如果需要一点嵌入式汇编程序,或者更有趣,请使用。

调用Filter看起来仍然不太好。假设您习惯于从左到右阅读,“过滤器”将在Iota之前显示,即使它在Iota之后运行。D不提供管道语句,而是使用统一函数调用语法(UFCS)。如果可以将表达式隐式转换为函数的第一个参数,则可以像该表达式的成员函数一样调用该函数。我知道这很复杂。一个例子会有所帮助:

string foo(string a) {
	return a ~ "World";
}

unittest {
	string a = foo("Hello ");
	string b = "Hello ".foo();
	assert(a == b);
}

上面的示例显示了对foo函数的两次调用。正如断言所暗示的,两个调用都是等效的。这对于我们的Io​​ta筛选器示例意味着什么?UFCS允许我们这样重写单元测试:

unittest {
	foreach(it; Iota(1,10).Filter(&isEven)) {
		writeln(it);
	}
}

现在,每个读者都可以访问范围的地图/变换实现。当然,可以使用模板使过滤器更抽象,但这只是细节,从概念上讲并不是什么新鲜事。

当然,存在不同类型的范围,例如双向。猜猜这给您带来了什么机会。快速提示:双向范围内有两个新的原语,称为back和popBack。范围还有其他类型,但是在两次理解上面显示的输入范围之后,您几乎都可以识别它们。

PS只是为了清楚起见,请勿实现自己的过滤器,地图或折叠;Phobos D标准库可满足您的所有需求。看一下std.algorithmstd.range

关于作者


Robert Schadeck在奥尔登堡大学获得计算机科学硕士学位。他的硕士论文题目为“ DMCD-具有缓存的分布式多线程D编译器”,在那里他从头开始创建D编译器。他于2012-2018年毕业于计算机科学专业。在奥尔登堡大学。他的博士学位论文着重于定额系统和图形。自2018年以来,他很高兴在Symmetry Investments的日常工作中使用D.

关于对称投资


Symmetry Investments是一家全球投资公司,在香港,新加坡和伦敦设有办事处。自从一家主要的纽约对冲基金成功分离后,我们自2014年以来一直开展业务。

在Symmetry,我们努力合理地冒险,为我们的客户,合作伙伴和员工创造价值。从广义上讲,我们从产生双赢合同的能力中受益。双赢是我们的基本道德和战略原则。通过产生双赢的选择,我们可以创建独特的解决方案,结合通常被认为是不兼容或相反的观点,并涵盖各方能够提供的所有最佳方法。我们正在将固定收益仲裁与全球宏观战略重新整合。我们发明并开发了一种专注于人机集成潜力的技术。我们创建的系统中,机器可以发挥最大作用,为这样他们才能做到最好。我们正在建立基于协作的精英管理:一种文化,在这种文化中,个人贡献既可以实现个人目标,也可以实现集体目标,并因此得到回报。我们既重视主人翁意识,也重视合作,自我实现和社区的团队精神。

自2014年以来,Symmetry Investments人们一直是社区D的活跃成员。我们赞助了excel-d,dpp,autowrap,libmir等项目的开发。我们于2018年启动了Symmetry秋季代码大会,并在伦敦举办了DConf 2019。

大约 由于翻译员像往常一样喝醉了,因此我无法保证某些人类术语的准确性,并且在上一节中我什么都不懂。因此,请发送个人更正中的更正。

All Articles