wc إلى D: 712 حرفًا بدون فرع واحد

بعد قراءة " Beat With 80-line Program on Haskell" ، والتي وجدتها في Hacker News ، قررت أن D قد تكون أفضل. ولقد كتبت wc في D.

Note.per. اقترحت ترجمة المقال أعلاه0xd34df00d، لكنه فضل أن يقوم على أساس "الفوز مع عشرون سطرًا من هاسكل: كتابة مرحاضك" . والآن تتكاثر المقالات مثل إعادة صياغة "العملة المعدنية".

برنامج


يتكون من ملف واحد - 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]);
}


بالطبع ، يستخدم Phobos ، مكتبة D القياسية ، ولكن لماذا لا؟ فوبوس جميل ويأتي مع كل مترجم D. البرنامج نفسه لا يحتوي على أي عبارات. وفي تنفيذ المرحاض على هاسكل ، يتم استخدام العديد منها. يحتوي برنامج D ، بالإضافة إلى البرنامج الرئيسي ، على ثلاث وظائف صغيرة للغاية. يمكنني بسهولة وضع جميع الوظائف في سلسلة نطاق واحدة ، ولكن بعد ذلك من المحتمل أن يتجاوز 80 حرفًا في كل سطر. هذا هو المبدأ الأساسي لنمط التعليمات البرمجية.

أداء


هل المرحاض على D أسرع من دورة المياه الأساسية؟ لا ، ولكن استغرق الأمر مني 15 دقيقة لكتابة الإصدار الخاص بي (كان علي البحث عن walkLength لأنني نسيت اسم الوظيفة).
ملف البياناتخطوطبايتالأساسيةهاسكلد
التطبيق د469063.5 مللي ثانية ± 1.9 مللي ثانية39.6 مللي ثانية ± 7.8 مللي ثانية8.9 مللي ثانية ± 2.1 مللي ثانية
big.txt86264 كيلو4.7 مللي ثانية ± 2.0 مللي ثانية39.6 مللي ثانية ± 7.8 مللي ثانية9.8 مللي ثانية ± 2.1 مللي ثانية
vbig.txt1.7 م96 م658.6 مللي ثانية ± 24.5 مللي ثانية226.4 مللي ثانية ± 29.5 مللي ثانية1.102 ثانية ± 0.022 ثانية
vbig2.txt12.1 م671 م4.4 ثانية ± 0.058 ثانية1.1 ثانية ± 0.039 ثانية7.4 ثانية ± 0.085 ثانية

ذاكرة:
ملف البياناتالأساسيةهاسكلد
التطبيق د2052 ألف7228 ألف7708 ألف
big.txt2112 ألف7512 ألف7616 ألف
vbig.txt2288 ألف42620 ألف7712 ألف
vbig2.txt2360 ألف50860 ألف7736 ألف
مرحاض على هاسكل أسرع؟ بالنسبة للملفات الكبيرة ، بالتأكيد ، لكنها تستخدم مؤشرات متعددة. بالنسبة للملفات الصغيرة ، لا يزال نظام GNU الأساسي يفوز. في هذه المرحلة ، من المرجح أن يكون الإصدار الخاص بي مقيدًا بـ IO ، وعلى أي حال ، فهو سريع جدًا.

لن أجادل في أن لغة ما هي أسرع من لغة أخرى. إذا كنت تقضي بعض الوقت في تحسين المعيار الصغير ، فمن المرجح أن تفوز. لكن ليس في الحياة الحقيقية. لكني سأجادل في أن البرمجة الوظيفية في D تكاد تتوافق مع FP في Haskell.

قليلا عن النطاق في D


النطاق عبارة عن تجريد يمكن تكراره دون لمس المجموعة الأساسية (إن وجدت). من الناحية الفنية ، يمكن أن يكون النطاق عبارة عن هيكل أو فئة تطبق واجهة واحدة أو أكثر من واجهات النطاق. أبسط شكل ، 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 هو مثال بسيط للغاية على النطاق. يعمل كمولد ، دون وجود المجموعة الأساسية. يتكرر من خلال الأعداد الصحيحة من البداية إلى النهاية بزيادات واحدة. يكشف هذا المقتطف بعضًا من بناء الجملة D.

@property bool empty() const {

تتيح لك السمة @ property استخدام الدالة الفارغة بنفس طريقة المتغير العضو (استدعاء دالة بدون أقواس). تعني السمة const في النهاية أننا لا نقوم بتعديل بيانات المثيل التي نسميها فارغة . يطبع اختبار الوحدة المدمجة الأرقام من 0 إلى 10.

وهناك ميزة صغيرة أخرى وهي عدم وجود مُنشئ صريح. يحتوي هيكل Iota على متغيرين عضوين. في عبارة foreach في الاختبار ، نقوم بإنشاء مثيل Iota كما لو كان لديه مُنشئ يقبل إدخالين. هذه حرفية هيكلية. عندما يرى المترجم D هذا ، ولكن لا يحتوي الهيكل على مُنشئ مقابل ، ثم وفقًا لترتيب التصريح عن المتغيرات - سيتم تعيين أعضاء الهيكل int.

العلاقة بين الأعضاء الثلاثة بسيطة. إذا كان فارغًا خاطئًا ، فإن الجبهة مضمونة لإرجاع عنصر جديد بعد التكرار بعد استدعاء popFront. بعد استدعاء popFront ، يمكن تغيير القيمة الفارغة. إذا كان هذا صحيحًا ، فهذا يعني أنه لا يوجد المزيد من العناصر للتكرار وأن المكالمات الإضافية إلى الأمام غير صالحة. وفقًا لوثائق InputRange :

  • يمكن حساب الجبهة بشكل صحيح إذا وفقط في حالة إرجاع فارغة أو إرجاع خطأ.
  • يمكن حساب الواجهة عدة مرات دون استدعاء popFront أو تحوير نطاق أو بيانات أساسية ، ويعطي النتيجة نفسها لكل عملية حساب.

استخدام عبارات foreach أو الحلقات ليست وظيفية للغاية. لنفترض أننا نريد تصفية جميع الأرقام الفردية لـ Iota. يمكننا وضع لو داخل كتلة foreach ، ولكن هذا سيجعل الأمر أسوأ. سيكون من الأفضل إذا كان لدينا نطاق يأخذ نطاقًا ومسندًا يقرر ما إذا كان العنصر مناسبًا أم لا.

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 ومؤشر دالة. عند إنشاء الفلتر ، نسمي testAndIterate ، الذي يأخذ العناصر من Iota حتى يصبح فارغًا أو يرجع المسند إلى false. الفكرة هي أن المسند يقرر ما يجب تصفيته وما يجب تركه. تتم ترجمة الخصائص الأمامية والخالية ببساطة إلى Iota. الشيء الوحيد الذي يقوم بهذه المهمة هو popFront. تقوم بإرجاع العنصر الحالي والمكالمات testAndIterate. هذا كل شئ. هذا هو تطبيق التصفية.

بالطبع ، لدى testAndIterate حلقة من الوقت ، لكن إعادة كتابتها باستخدام العودية ، في رأيي ، هو مجرد غبي. ما يجعل D عظيمًا هو أنه يمكنك استخدام الطريقة المناسبة لكل مهمة. البرمجة الوظيفية جيدة ، وغالبًا ما تتفاخر بها ، ولكنها في بعض الأحيان ليست كذلك. إذا كانت هناك حاجة إلى مجمّع صغير مضمّن أو كان أكثر متعة ، فاستخدمه.

لا يزال استدعاء عامل التصفية لا يبدو جيدًا جدًا. بافتراض أنك معتاد على القراءة من اليسار إلى اليمين ، يظهر الفلتر قبل Iota ، حتى إذا كان يعمل بعد Iota. لا توفر D عبارة توجيه الإخراج ، ولكنها تستخدم بناء جملة استدعاء دالة موحدة (UFCS). إذا كان يمكن تحويل تعبير ضمنيًا إلى المعلمة الأولى للدالة ، فيمكن استدعاء الوظيفة تمامًا كما لو كانت دالة عضو في هذا التعبير. هذا معقد للغاية ، أفهم. مثال سيساعد:

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

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

يوضح المثال أعلاه مكالمتين لوظيفة foo. كما يشير التأكيد ، كلتا المكالمتين متكافئتان. ماذا يعني هذا لمثال Iota Filter الخاص بنا؟ يسمح لنا UFCS بإعادة كتابة اختبار الوحدة على النحو التالي:

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

يجب أن يكون تطبيق الخريطة / تحويل النطاق متاحًا لكل قارئ. بالطبع ، يمكن جعل المرشح أكثر تجريدًا باستخدام القوالب ، ولكن هذه مجرد تفاصيل ، لا شيء جديد من الناحية النظرية.

بالطبع ، هناك أنواع مختلفة من النطاق ، على سبيل المثال ، ثنائي الاتجاه. خمن ما الفرص التي يمنحك ذلك. نصيحة سريعة: هناك نوعان أساسيان جديدان في النطاق ثنائي الاتجاه يسمى back و popBack. هناك أنواع أخرى من النطاق ، ولكن بعد أن تفهم نطاق الإدخال الموضح أعلاه مرتين ، ستتعرف عليهم جميعًا تقريبًا.

PS فقط من أجل الوضوح ، لا تقم بتطبيق المرشح أو الخريطة أو الطي ؛ تحتوي مكتبة Phobos D القياسية على كل ما تحتاجه. ألق نظرة على std.algorithm و std.range .

نبذة عن الكاتب


حصل روبرت Schadeck على درجة الماجستير في علوم الكمبيوتر من جامعة أولدنبورغ. كانت أطروحة الماجستير الخاصة به بعنوان "DMCD - الموزع D متعدد المقاييس الموزع مع التخزين المؤقت" ، حيث عمل على إنشاء مترجم D من الصفر. كان طالب دراسات عليا في علوم الكمبيوتر في 2012-2018. في جامعة أولدنبورغ. تركز أطروحة دكتوراه على أنظمة النصاب بالاقتران مع الرسوم البيانية. منذ عام 2018 ، كان من دواعي سروره استخدام D في عمله اليومي ، للعمل في Symmetry Investments.

حول استثمارات التناظر


Symmetry Investments هي شركة استثمار عالمية لها مكاتب في هونغ كونغ وسنغافورة ولندن. لقد قمنا بأعمال تجارية منذ عام 2014 بعد الانفصال بنجاح عن صندوق تحوط رئيسي في نيويورك.

في Symmetry ، نسعى جاهدين إلى تحمل المخاطر بشكل معقول لخلق قيمة لعملائنا وشركائنا وموظفينا. نحن نستفيد من قدرتنا على إنشاء عقود مربحة للجانبين - بأوسع معنى للكلمة. Win-Win هو مبدأنا الأخلاقي والاستراتيجي الأساسي. من خلال إنشاء خيارات مربحة للجميع ، يمكننا إنشاء حلول فريدة تجمع بين المنظورات التي يُنظر إليها عادةً على أنها غير متوافقة أو معاكسة ، وتغطي أفضل ما يمكن أن يقدمه كل طرف. نحن نعيد دمج تحكيم الدخل الثابت مع الاستراتيجيات الكلية العالمية. نحن نبتكر ونطور تقنية تركز على إمكانات التكامل بين الإنسان والآلة. نحن ننشئ أنظمة تقوم فيها الآلات بأداء عملها بشكل أفضل ، ودعم الأشخاص فيهاحتى يفعلوا ما يفعلونه أفضل. نحن نخلق الجدارة على أساس التعاون: ثقافة تخدم فيها المساهمة الفردية الأهداف الشخصية والجماعية - ويتم مكافأتها وفقًا لذلك. نحن نقدر كل من الشعور بالملكية وروح الفريق من التعاون ، وتحقيق الذات والمجتمع.

كان أعضاء التناظر في الاستثمار أعضاء نشطين في المجتمع د منذ عام 2014. قمنا برعاية تطوير excel-d و dpp و autowrap و libmir وغيرها من المشاريع. أطلقنا Symmetry Autumn of Code في 2018 واستضافنا DConf 2019 في لندن.

تقريبا. نظرًا لأن المترجم ، كالمعتاد ، في حالة سكر ، لا يمكنني أن أشهد على دقة بعض المصطلحات البشرية ، ولا أفهم أي شيء في القسم الأخير. لذا أرسل التصحيحات في الاتصالات الشخصية.

All Articles