لقد استخدمت بالفعل التدفقات في channel-chatter وتخلّصت منها في boatswains-bilge. هذا التمرين هو أول تدفق مخصص لك: صنف يُنفّذ البروتوكول، بحيث يعمل عليه الكود نفسه with-input-stream / read1 / stream-read1 الذي كان يعمل على <string-reader>.
أنت تعرف بالفعل TUPLE: child < parent … من dragons-descendants: تسلسل هرمي بأب واحد تتدفق فيه الخانات إلى الأسفل. ولـ Factor طريقة ثانية لتوسيع مخطط الأصناف: المزائج المفتوحة. تُعرّف MIXIN: m صنفًا بلا خانات يمكن لأصناف أخرى الانضمام إليه لاحقًا عبر INSTANCE: c m. الانتماء هنا متعدد إلى متعدد: يمكن للصنف أن يكون نسخةً من أي عدد من المزائج.
يستخدم بروتوكول التدفقات مزيجين لتحديد أي نصف من الواجهة ينفّذه الصنف:
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
يمكن أن يكون التدفق أحد المزيجين، أو الآخر، أو كليهما. بعد ذلك تُوزّع الدوال العامة المعرّفة على input-stream (أو output-stream) الاستدعاء إلى أي صنف انضم إلى المزيج، وهكذا تصل الدوال المحيطة مثل read1 و stream-contents و with-input-stream وغيرها إلى صنفك.
المزائج مفتوحة: يمكنك إضافة عضو جديد بعد تعريف المزيج، حتى في معجم مختلف. وهذا ما يميّزها عن TUPLE: < parent، حيث تُثبَّت العلاقة عند تعريف الصنف الابن.
تقع الدوال العامة للبروتوكول في معجم io:
stream-read1 ( stream -- elt/f )
stream-read ( n stream -- seq/f )
stream-read-unsafe ( n buf stream -- count )
stream-write1 ( elt stream -- )
stream-write ( seq stream -- )
stream-flush ( stream -- )
stream-element-type ( stream -- type )
تُرجع stream-read1 البايت التالي (أو f عند نهاية التدفق)، بينما تكتب stream-write1 بايتًا واحدًا. وstream-read-unsafe هي القراءة الدفعية المنخفضة المستوى التي توفّرها معظم تدفقات الإدخال، أما stream-read فمبنية فوقها وتتولّى تخصيص الذاكرة. وتخبرنا stream-element-type ما إذا كان التدفق يتعامل بالبايتات (+byte+) أم بالمحارف (+character+)؛ وعندما تغلّف تدفقًا آخر، فإن أبسط تنفيذ يفوّض الأمر ببساطة إلى التدفق الأساسي.
destructors
التدفقات قابلة للتخلص منها: التدفق المخصص يرث من disposable وينفّذ dispose*. وهذا هو الإطار نفسه من boatswains-bilge مطبّقًا على صنف تدفق:
TUPLE: my-stream < disposable underlying ;
: <my-stream> ( underlying -- s )
my-stream new-disposable swap >>underlying ;
M: my-stream dispose* underlying>> dispose ;
وبمجرد أن يصبح ذلك جاهزًا، تعمل جميع المُركِّبات with-… من channel-chatter على تدفقك تلقائيًا.
أنت مشغّل التلغراف اللاسلكي على متن سفينة شحن. تصل الإشارة عبر خط مشوّش: آلات السفينة، وتشويش الطقس، وثرثرة أجنبية عابرة، متشابكة مع شفرة مورس التي تنتظرها. وللالتقاط الرسالة تمرّر السلك عبر جهاز شريط لا يُبقي إلا رموز مورس الصالحة (. و- والمسافة التي تفصل بين الحروف).
مهمتك أن تبني الشريط كتيار إدخال مخصّص في Factor.
عرّف tuple باسم tape يمتد من disposable ويحوي خانة واحدة، wire، لتيار الإدخال الأساسي.
TUPLE: tape < disposable wire ;
ثم اكتب البانية <tape> ( wire -- tape ). استخدم new-disposable (وليس new) لكي يسجّل إطار عمل المُدمِّر الشريط، وخزّن التيار الأساسي في الخانة wire.
أعلن أن tape هو input-stream:
INSTANCE: tape input-stream
وبذلك تُوجَّه المُركِّبات الافتراضية في البروتوكول إلى صنفك.
نفّذ M: tape stream-read1 ( tape -- elt/f ). اقرأ البايتات من السلك الأساسي حتى تصادف أحد CHAR: . أو CHAR: - أو CHAR: \s (مسافة). أرجِع ذلك البايت. وإذا أرجع السلك f (نهاية التيار)، فأرجِع f أيضًا، فقد انتهى الشريط.
"x.y -ab cz" <string-reader> <tape>
[ stream-read1 ] [ stream-read1 ] [ stream-read1 ] tri
! => CHAR: . CHAR: - CHAR: \s
نفّذ M: tape stream-element-type ( tape -- type ) بالتفويض إلى السلك: فالشريط لا يغيّر نوع العناصر المقروءة.
نفّذ M: tape dispose* ( tape -- ). يستدعي إطار العمل طريقتك مرة واحدة بالضبط لكل استدعاء لـ dispose؛ كل ما عليك هو التخلّص من السلك الأساسي.
وبعد اكتمال هذه القطع الخمس، يعمل الشريط كتيار إدخال عادي: with-input-stream وstream-read وباقي كلمات البروتوكول كلها تُوجَّه إلى طرقك تلقائيًا.
سجّل في Exercism لتتعلّم وتتقن Factor عبر 47 مفهومًا163 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.