|>
) لجافا سكريبت (يستخدم هذا المستند %
كعنصر نائب للرمز المرجعي للموضوع. ومن المؤكد تقريبًا أن هذا لن يكون الخيار النهائي ؛ راجع مناقشة الرمز المميز لتبادل الدراجات للحصول على التفاصيل.)
في استطلاع حالة JS 2020، الإجابة الرابعة على سؤال "ما الذي تشعر أنه مفقود حاليًا في JavaScript؟" كان مشغل الأنابيب . لماذا؟
عندما نقوم بتنفيذ عمليات متتالية (على سبيل المثال، استدعاءات الوظائف) على قيمة في JavaScript، يوجد حاليًا نمطان أساسيان:
أي three(two(one(value)))
مقابل value.one().two().three()
. ومع ذلك، تختلف هذه الأساليب كثيرًا في سهولة القراءة والطلاقة وقابلية التطبيق.
النمط الأول، التعشيش ، قابل للتطبيق بشكل عام - فهو يعمل مع أي تسلسل من العمليات: استدعاءات الوظائف، الحساب، المصفوفات/الكائنات الحرفية، await
yield
، وما إلى ذلك.
ومع ذلك، يصعب قراءة التداخل عندما يصبح عميقًا: يتحرك تدفق التنفيذ من اليمين إلى اليسار ، بدلاً من القراءة من اليسار إلى اليمين للتعليمات البرمجية العادية. إذا كانت هناك وسائط متعددة في بعض المستويات، فإن القراءة ترتد ذهابًا وإيابًا : يجب أن تقفز أعيننا إلى اليسار للعثور على اسم الوظيفة، ثم يجب أن تقفز إلى اليمين للعثور على وسائط إضافية. بالإضافة إلى ذلك، قد يكون تحرير التعليمات البرمجية بعد ذلك أمرًا محفوفًا بالمخاطر: يجب علينا العثور على المكان الصحيح لإدراج وسائط جديدة بين العديد من الأقواس المتداخلة .
خذ بعين الاعتبار رمز العالم الحقيقي هذا من React.
console . log (
chalk . dim (
`$ ${ Object . keys ( envars )
. map ( envar =>
` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
} ` ,
'node' ,
args . join ( ' ' ) ) ) ;
يتكون رمز العالم الحقيقي هذا من تعبيرات متداخلة بعمق . من أجل قراءة تدفق البيانات، يجب على عيون الإنسان أولاً:
ابحث عن البيانات الأولية (التعبير الأعمق، envars
).
ثم قم بالمسح ذهابًا وإيابًا بشكل متكرر من الداخل إلى الخارج لكل تحويل للبيانات، كل واحد إما مشغل بادئة يمكن تفويته بسهولة على اليسار أو مشغل لاحقة على اليمين:
Object.keys()
(الجانب الأيسر)،.map()
(الجانب الأيمن)،.join()
(الجانب الأيمن)،chalk.dim()
(الجانب الأيسر)، ثمconsole.log()
(الجانب الأيسر).نتيجة للتداخل العميق للعديد من التعبيرات (بعضها يستخدم عوامل البادئة ، وبعضها يستخدم عوامل postfix ، وبعضها يستخدم عوامل محيطة )، يجب علينا التحقق من الجانبين الأيسر والأيمن للعثور على رأس كل تعبير .
النمط الثاني، طريقة التسلسل ، قابل للاستخدام فقط إذا كانت القيمة تحتوي على الوظائف المخصصة كطرق لفئتها. وهذا يحد من إمكانية تطبيقه. ولكن عندما يتم تطبيقه، وذلك بفضل هيكله اللاحق، فهو بشكل عام أكثر قابلية للاستخدام وأسهل في القراءة والكتابة. يتدفق تنفيذ التعليمات البرمجية من اليسار إلى اليمين . التعبيرات المتداخلة بعمق غير متشابكة . يتم تجميع كافة الوسائط لاستدعاء الدالة مع اسم الدالة. يعد تحرير التعليمات البرمجية لاحقًا لإدراج المزيد من استدعاءات الطريقة أو حذفها أمرًا تافهًا، حيث سيتعين علينا فقط وضع المؤشر في مكان واحد، ثم البدء في كتابة أو حذف مجموعة واحدة متجاورة من الأحرف.
في الواقع، تعد فوائد تسلسل الطرق جذابة للغاية لدرجة أن بعض المكتبات الشائعة تقوم بتحريف بنية التعليمات البرمجية الخاصة بها خصيصًا للسماح بمزيد من تسلسل الطرق . المثال الأبرز هو jQuery ، التي لا تزال مكتبة JS الأكثر شعبية في العالم. التصميم الأساسي لـ jQuery عبارة عن كائن über واحد يحتوي على العشرات من التوابع، وكلها تعيد نفس نوع الكائن حتى نتمكن من الاستمرار في التسلسل . حتى أن هناك اسمًا لهذا النمط من البرمجة: الواجهات بطلاقة .
لسوء الحظ، على الرغم من طلاقتها، فإن تسلسل الأساليب وحده لا يمكنه استيعاب صيغ جافا سكريبت الأخرى : استدعاءات الوظائف، والحساب، والمصفوفات/الكائنات الحرفية، await
yield
، وما إلى ذلك. وبهذه الطريقة، يظل تسلسل الأساليب محدودًا في إمكانية تطبيقه .
يحاول مشغل الأنابيب المزاوجة بين الراحة وسهولة تسلسل الأساليب مع إمكانية التطبيق الواسعة لتداخل التعبير .
الهيكل العام لجميع مشغلي الأنابيب هو value |>
e1 |>
e2 |>
e3 ، حيث e1 , e2 , e3 كلها تعبيرات تأخذ قيمًا متتالية كمعلمات لها. يقوم عامل التشغيل |>
بعد ذلك بإجراء درجة معينة من السحر "لنقل" value
من الجانب الأيسر إلى الجانب الأيمن.
متابعة هذا الكود الواقعي المتداخل بشدة من React:
console . log (
chalk . dim (
`$ ${ Object . keys ( envars )
. map ( envar =>
` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
} ` ,
'node' ,
args . join ( ' ' ) ) ) ;
…يمكننا فك تشابكها على هذا النحو باستخدام مشغل الأنابيب والعنصر النائب ( %
) الذي يمثل قيمة العملية السابقة:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
الآن، يمكن للقارئ البشري العثور بسرعة على البيانات الأولية (ما كان التعبير الأعمق، envars
)، ثم قراءة خطية ، من اليسار إلى اليمين ، كل تحويل على البيانات.
يمكن للمرء أن يجادل بأن استخدام المتغيرات المؤقتة يجب أن يكون الطريقة الوحيدة لفك تشابك التعليمات البرمجية المتداخلة بشدة. تؤدي تسمية متغير كل خطوة بشكل صريح إلى حدوث شيء مشابه لتسلسل الأساليب، مع فوائد مماثلة لقراءة التعليمات البرمجية وكتابتها.
على سبيل المثال، باستخدام مثالنا الواقعي المعدل السابق من React:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
…ستبدو النسخة التي تستخدم المتغيرات المؤقتة كما يلي:
const envarString = Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' ) ;
const consoleText = `$ ${ envarString } ` ;
const coloredConsoleText = chalk . dim ( consoleText , 'node' , args . join ( ' ' ) ) ;
console . log ( coloredConsoleText ) ;
ولكن هناك أسباب تجعلنا نواجه تعبيرات متداخلة بعمق في كود بعضنا البعض طوال الوقت في العالم الحقيقي ، بدلاً من سطور من المتغيرات المؤقتة. وهناك أسباب تجعل الواجهات بطلاقة القائمة على سلسلة الطريقة لـ jQuery وMocha وما إلى ذلك لا تزال شائعة .
غالبًا ما يكون الأمر مملًا للغاية ومليئًا بالألفاظ لكتابة تعليمات برمجية تحتوي على تسلسل طويل من المتغيرات المؤقتة ذات الاستخدام الواحد. ويمكن القول إن قراءتها مملة ومزعجة بصريًا أيضًا.
إذا كانت التسمية من أصعب المهام في البرمجة، فإن المبرمجين سيتجنبون حتما تسمية المتغيرات عندما يرون أن فائدتها صغيرة نسبيا.
يمكن للمرء أن يجادل بأن استخدام متغير واحد قابل للتغيير باسم قصير من شأنه أن يقلل من كثرة الكلمات في المتغيرات المؤقتة، مما يحقق نتائج مماثلة كما هو الحال مع مشغل الأنابيب.
على سبيل المثال، يمكن إعادة كتابة مثالنا الواقعي المعدل السابق من React على النحو التالي:
let _ ;
_ = Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' ) ;
_ = `$ ${ _ } ` ;
_ = chalk . dim ( _ , 'node' , args . join ( ' ' ) ) ;
_ = console . log ( _ ) ;
لكن كود مثل هذا ليس شائعًا في كود العالم الحقيقي. أحد أسباب ذلك هو أن المتغيرات القابلة للتغيير يمكن أن تتغير بشكل غير متوقع ، مما يسبب أخطاء صامتة يصعب العثور عليها. على سبيل المثال، قد تتم الإشارة إلى المتغير عن طريق الخطأ في عملية الإغلاق. أو قد يتم إعادة تعيينه عن طريق الخطأ ضمن تعبير.
// setup
function one ( ) { return 1 ; }
function double ( x ) { return x * 2 ; }
let _ ;
_ = one ( ) ; // _ is now 1.
_ = double ( _ ) ; // _ is now 2.
_ = Promise . resolve ( ) . then ( ( ) =>
// This does *not* print 2!
// It prints 1, because `_` is reassigned downstream.
console . log ( _ ) ) ;
// _ becomes 1 before the promise callback.
_ = one ( _ ) ;
لن تحدث هذه المشكلة مع مشغل الأنابيب. لا يمكن إعادة تعيين الرمز المميز للموضوع، ولا يمكن للتعليمات البرمجية الموجودة خارج كل خطوة تغيير ربطها.
let _ ;
_ = one ( )
| > double ( % )
| > Promise . resolve ( ) . then ( ( ) =>
// This prints 2, as intended.
console . log ( % ) ) ;
_ = one ( ) ;
لهذا السبب، من الصعب أيضًا قراءة التعليمات البرمجية ذات المتغيرات القابلة للتغيير. لتحديد ما يمثله المتغير في أي نقطة معينة، يجب عليك البحث في النطاق السابق بالكامل عن الأماكن التي تم إعادة تعيينه فيها.
من ناحية أخرى، فإن مرجع الموضوع لخط الأنابيب له نطاق معجمي محدود، وارتباطه غير قابل للتغيير ضمن نطاقه. ولا يمكن إعادة تعيينه عن طريق الخطأ، ويمكن استخدامه بأمان في عمليات الإغلاق.
على الرغم من أن قيمة الموضوع تتغير أيضًا مع كل خطوة من خطوات المسار، إلا أننا نقوم فقط بفحص الخطوة السابقة من المسار لفهمها، مما يؤدي إلى كود أسهل في القراءة.
فائدة أخرى لمشغل الأنابيب على تسلسل بيانات التخصيص (سواء مع متغيرات مؤقتة قابلة للتغيير أو غير قابلة للتغيير) هي أنها عبارة عن تعبيرات .
تعبيرات الأنابيب هي تعبيرات يمكن إرجاعها مباشرةً أو تخصيصها لمتغير أو استخدامها في سياقات مثل تعبيرات JSX.
من ناحية أخرى، يتطلب استخدام المتغيرات المؤقتة تسلسلات من البيانات.
خطوط الأنابيب | المتغيرات المؤقتة |
---|---|
const envVarFormat = vars =>
Object . keys ( vars )
. map ( var => ` ${ var } = ${ vars [ var ] } ` )
. join ( ' ' )
| > chalk . dim ( % , 'node' , args . join ( ' ' ) ) ; | const envVarFormat = ( vars ) => {
let _ = Object . keys ( vars ) ;
_ = _ . map ( var => ` ${ var } = ${ vars [ var ] } ` ) ;
_ = _ . join ( ' ' ) ;
return chalk . dim ( _ , 'node' , args . join ( ' ' ) ) ;
} |
// This example uses JSX.
return (
< ul >
{
values
| > Object . keys ( % )
| > [ ... Array . from ( new Set ( % ) ) ]
| > % . map ( envar => (
< li onClick = {
( ) => doStuff ( values )
} > { envar } < / li >
) )
}
< / ul >
) ; | // This example uses JSX.
let _ = values ;
_ = Object . keys ( _ ) ;
_ = [ ... Array . from ( new Set ( _ ) ) ] ;
_ = _ . map ( envar => (
< li onClick = {
( ) => doStuff ( values )
} > { envar } < / li >
) ) ;
return (
< ul > { _ } < / ul >
) ; |
كان هناك اقتراحان متنافسان لمشغل الأنابيب: Hack Pipes وF# Pipes. (قبل ذلك، كان هناك اقتراح ثالث لـ "المزيج الذكي" من الاقتراحين الأولين، ولكن تم سحبه، لأن تركيبه عبارة عن مجموعة شاملة من أحد الاقتراحات).
يختلف مقترحا الأنبوبين قليلًا فيما يتعلق بكلمة "السحر"، عندما نقوم بتهجئة الكود الخاص بنا عند استخدام |>
.
يعيد كلا الاقتراحين استخدام مفاهيم اللغة الحالية: تعتمد أنابيب Hack على مفهوم التعبير ، بينما تعتمد أنابيب F# على مفهوم الوظيفة الأحادية .
في المقابل، تحتوي تعبيرات الأنابيب ووظائف الأنابيب الأحادية على مقايضات صغيرة ومتماثلة تقريبًا.
في بناء جملة لغة الهاك ، الجانب الأيمن من الأنبوب هو تعبير يحتوي على عنصر نائب خاص، والذي يتم تقييمه باستخدام العنصر النائب المرتبط بنتيجة تقييم تعبير الجانب الأيسر. أي أننا نكتب value |> one(%) |> two(%) |> three(%)
لتوجيه value
عبر الوظائف الثلاث.
Pro: يمكن أن يكون الجانب الأيمن أي تعبير ، ويمكن للعنصر النائب أن ينتقل إلى أي مكان يمكن أن يذهب إليه أي معرف متغير عادي، حتى نتمكن من توجيه أي رمز نريده دون أي قواعد خاصة :
value |> foo(%)
لاستدعاءات الدوال الأحادية،value |> foo(1, %)
لاستدعاءات الدوال n-ary،value |> %.foo()
لاستدعاءات الطريقة،value |> % + 1
للحساب،value |> [%, 0]
للقيم الحرفية للصفيف،value |> {foo: %}
للكائنات الحرفية،value |> `${%}`
للقيم الحرفية للقالب،value |> new Foo(%)
لإنشاء الكائنات،value |> await %
لانتظار الوعود،value |> (yield %)
لإنتاج قيم المولد،value |> import(%)
لاستدعاء الكلمات الرئيسية المشابهة للوظيفة، السلبيات: يعتبر توصيل الأنابيب عبر الوظائف الأحادية أكثر تفصيلاً قليلاً مع أنابيب Hack مقارنةً بأنابيب F#. يتضمن ذلك الدوال الأحادية التي تم إنشاؤها بواسطة مكتبات معالجة الوظائف مثل Ramda، بالإضافة إلى وظائف السهم الأحادي التي تؤدي إلى تدمير معقد للوسائط الخاصة بها: ستكون أنابيب الاختراق أكثر تفصيلًا قليلاً مع لاحقة استدعاء دالة صريحة (%)
.
(سيكون التدمير المعقد لقيمة الموضوع أسهل عندما تتقدم التعبيرات، حيث ستتمكن بعد ذلك من القيام بتعيين/تدمير متغير داخل جسم الأنبوب.)
في بناء جملة الأنبوب في لغة F# ، الجانب الأيمن من الأنبوب هو تعبير يجب تقييمه في دالة أحادية ، والتي يتم استدعاؤها بعد ذلك ضمنيًا باستخدام قيمة الجانب الأيسر كوسيطة وحيدة . أي أننا نكتب value |> one |> two |> three
لتوجيه value
عبر الوظائف الثلاث. left |> right
right(left)
. وهذا ما يسمى البرمجة الضمنية أو الأسلوب الخالي من النقاط.
على سبيل المثال، باستخدام مثالنا الواقعي المعدل السابق من React:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
…الإصدار الذي يستخدم أنابيب F# بدلاً من أنابيب Hack سيبدو كما يلي:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > x => `$ ${ x } `
| > x => chalk . dim ( x , 'node' , args . join ( ' ' ) )
| > console . log ;
Pro: يتيح لنا القيد الذي يجب أن يحله الجانب الأيمن لوظيفة أحادية كتابة توجيهات مقتضبة للغاية عندما تكون العملية التي نريد تنفيذها عبارة عن استدعاء دالة أحادية :
value |> foo
لاستدعاءات الدوال الأحادية. يتضمن ذلك الدوال الأحادية التي تم إنشاؤها بواسطة مكتبات معالجة الوظائف مثل Ramda، بالإضافة إلى دوال السهم الأحادي التي تقوم بتدمير معقد على وسيطاتها: ستكون أنابيب F# أقل لفظًا قليلاً مع استدعاء دالة ضمني (لا (%)
).
Con: القيد يعني أن أي عمليات يتم تنفيذها بواسطة بناء جملة آخر يجب أن تكون أكثر تفصيلاً قليلاً عن طريق تغليف العملية في وظيفة سهم أحادي:
value |> x=> x.foo()
لاستدعاءات الطريقة،value |> x=> x + 1
للحساب،value |> x=> [x, 0]
للقيم الحرفية للمصفوفة،value |> x=> ({foo: x})
للكائنات الحرفية،value |> x=> `${x}`
للقيم الحرفية للقالب،value |> x=> new Foo(x)
لإنشاء الكائنات،value |> x=> import(x)
لاستدعاء الكلمات الرئيسية المشابهة للوظيفة،حتى استدعاء الوظائف المُسمّاة يتطلب التفافًا عندما نحتاج إلى تمرير أكثر من وسيطة واحدة :
value |> x=> foo(1, x)
لاستدعاءات الدوال n-ary. السلبيات: يتم تحديد نطاق عمليات await
yield
وفقًا لوظيفتها المحتوية ، وبالتالي لا يمكن التعامل معها من خلال الوظائف الأحادية وحدها. إذا أردنا دمجها في تعبير توجيهي، فيجب التعامل مع await
yield
كحالات بناء جملة خاصة :
value |> await
انتظار الوعود، وvalue |> yield
لإنتاج قيم المولد. تفرض كل من Hack Pipes وF# Pipes على التوالي ضريبة تركيبية صغيرة على التعبيرات المختلفة:
اختراق الأنابيب يفرض ضريبة طفيفة على استدعاءات الوظائف الأحادية فقط و
تقوم أنابيب F# بفرض ضريبة طفيفة على كافة التعبيرات باستثناء استدعاءات الوظائف الأحادية.
في كلا الاقتراحين، تكون ضريبة بناء الجملة لكل تعبير خاضع للضريبة صغيرة ( كل من (%)
و x=>
يتكونان من ثلاثة أحرف فقط ). غير أن الضريبة تتضاعف بشيوع عباراتها الخاضعة للضريبة على التوالي. لذلك قد يكون من المنطقي فرض ضريبة على التعبيرات الأقل شيوعًا وتحسينها لصالح التعبيرات الأكثر شيوعًا .
تعد استدعاءات الوظائف الأحادية بشكل عام أقل شيوعًا من جميع التعبيرات باستثناء الوظائف الأحادية. على وجه الخصوص، سيكون استدعاء الأسلوب واستدعاء الوظائف n-ary شائعًا دائمًا ؛ في التردد العام، يكون استدعاء الدالة الأحادية مساويًا أو متجاوزًا لهاتين الحالتين وحدهما - ناهيك عن تركيب الجمل الأخرى المنتشرة في كل مكان مثل المصفوفة الحرفية ، والقيمة الحرفية للكائن ، والعمليات الحسابية . يحتوي هذا الشرح على العديد من الأمثلة الواقعية لهذا الاختلاف في معدل الانتشار.
علاوة على ذلك، من المحتمل أيضًا أن تصبح العديد من صيغ الجملة الجديدة المقترحة، مثل استدعاء الامتداد ، وتعبيرات do ، والسجلات/الصفوف الحرفية ، منتشرة في المستقبل . وبالمثل، ستصبح العمليات الحسابية أيضًا أكثر شيوعًا إذا قام TC39 بتوحيد التحميل الزائد للمشغل . سيكون فك تشابك تعبيرات بناء الجملة المستقبلية أكثر طلاقة مع أنابيب Hack مقارنةً بأنابيب F#.
إن ضريبة بناء الجملة الخاصة بأنابيب Hack على استدعاءات الوظائف الأحادية (على سبيل المثال، (%)
لاستدعاء الوظيفة الأحادية للجانب الأيمن) ليست حالة خاصة : فهي ببساطة تكتب تعليمات برمجية عادية بشكل صريح ، بالطريقة التي نتبعها عادةً بدون توجيه.
من ناحية أخرى، تتطلب منا أنابيب F# التمييز بين "التعليمات البرمجية التي تتحول إلى دالة أحادية" مقابل "أي تعبير آخر" - وتذكر إضافة غلاف دالة السهم حول الحالة الأخيرة.
على سبيل المثال، مع Hack Pipes، value |> someFunction + 1
هي صيغة غير صالحة وستفشل مبكرًا . ليست هناك حاجة إلى إدراك أن someFunction + 1
لن يتم تقييمها كدالة أحادية. لكن مع أنابيب F#، لا تزال value |> someFunction + 1
بناء جملة صالحًا - ستفشل في وقت متأخر من وقت التشغيل ، لأن someFunction + 1
غير قابل للاستدعاء.
قدمت مجموعة أبطال الأنابيب أنابيب F# للمرحلة الثانية إلى TC39 مرتين . لم ينجح في التقدم إلى المرحلة الثانية في المرتين. واجهت كل من أنابيب F# (وتطبيق الوظيفة الجزئية (PFA)) معارضة قوية من العديد من ممثلي TC39 الآخرين بسبب مخاوف مختلفة. وقد شملت هذه:
await
.لقد حدث هذا التراجع من خارج مجموعة أبطال الأنابيب. راجع HISTORY.md لمزيد من المعلومات.
تعتقد مجموعة أبطال الأنابيب أن أي مشغل أنابيب أفضل من لا شيء، من أجل جعل التعبيرات المتداخلة بسهولة خطية دون اللجوء إلى المتغيرات المسماة. يعتقد العديد من أعضاء المجموعة البطلة أن أنابيب Hack أفضل قليلاً من أنابيب F#، ويعتقد بعض أعضاء المجموعة البطلة أن أنابيب F# أفضل قليلاً من أنابيب Hack. لكن الجميع في المجموعة البطلة يتفقون على أن أنابيب F# واجهت مقاومة كبيرة جدًا بحيث لا يمكنها اجتياز TC39 في المستقبل المنظور.
للتأكيد، من المحتمل أن محاولة التبديل من أنابيب Hack مرة أخرى إلى أنابيب F# ستؤدي إلى عدم موافقة TC39 مطلقًا على أي أنابيب على الإطلاق. يواجه بناء جملة PFA بالمثل معركة شاقة في TC39 (انظر HISTORY.md). يعتقد العديد من أعضاء مجموعة أبطال الأنابيب أن هذا أمر مؤسف، وهم على استعداد للقتال مرة أخرى لاحقًا من أجل مزيج تقسيم F#-pipe وبناء جملة PFA. ولكن هناك عدد لا بأس به من الممثلين (بما في ذلك منفذي محركات المتصفح) خارج مجموعة Pipe Champion Group الذين يعارضون عمومًا تشجيع البرمجة الضمنية (وصياغة PFA)، بغض النظر عن Hack Pipes.
(تتوفر مسودة المواصفات الرسمية.)
مرجع الموضوع %
هو عامل تشغيل فارغ . إنه بمثابة عنصر نائب لقيمة الموضوع ، وهو محدد بشكل معجمي وغير قابل للتغيير .
%
ليس خيارا نهائيا ( الرمز المميز الدقيق لمرجع الموضوع ليس نهائيًا . يمكن بدلاً من ذلك أن يكون %
^
أو العديد من الرموز المميزة الأخرى. نحن نخطط لاستبدال الرمز المميز الفعلي الذي يجب استخدامه قبل التقدم إلى المرحلة 3. ومع ذلك، %
يبدو أنه الأقل إشكالية من الناحية النحوية، و كما أنه يشبه أيضًا العناصر النائبة لسلاسل تنسيق printf وحرف الدالة #(%)
الخاصة بـ Clojure .)
عامل تشغيل الأنابيب |>
هو عامل تشغيل infix يشكل تعبير توجيه الإخراج (يُسمى أيضًا خط الأنابيب ). يقوم بتقييم جانبه الأيسر ( رأس الأنبوب أو مدخل الأنبوب )، ويربط القيمة الناتجة ( قيمة الموضوع ) بمرجع الموضوع بشكل ثابت، ثم يقيم جانبه الأيمن ( جسم الأنبوب ) بهذا الربط. تصبح القيمة الناتجة للجانب الأيمن هي القيمة النهائية لتعبير الأنبوب بالكامل ( إخراج الأنبوب ).
أسبقية مشغل الأنابيب هي نفسها :
=>
;=
، +=
، وما إلى ذلك؛yield
yield *
; إنه أكثر إحكامًا من عامل الفاصلة فقط ,
إنه أكثر مرونة من جميع المشغلين الآخرين .
على سبيل المثال، v => v |> % == null |> foo(%, 0)
سيتم تجميعها في v => (v |> (% == null) |> foo(%, 0))
,
والذي بدوره يعادل v => foo(v == null, 0)
.
يجب أن يستخدم نص الأنبوب قيمة الموضوع الخاصة به مرة واحدة على الأقل . على سبيل المثال، value |> foo + 1
هي صيغة غير صالحة ، لأن نصها لا يحتوي على مرجع موضوع. يرجع هذا التصميم إلى أن إغفال مرجع الموضوع من نص تعبير توجيه الإخراج هو بالتأكيد خطأ مبرمج عرضي .
وبالمثل، يجب تضمين مرجع الموضوع في جسم الأنبوب. يعد استخدام مرجع موضوع خارج نص التوجيه أيضًا بناء جملة غير صالح .
لمنع الخلط بين التجميع، من غير الصحيح استخدام عوامل تشغيل أخرى لها أسبقية مماثلة (على سبيل المثال، السهم =>
، والعامل الشرطي الثلاثي ?
:
، وعوامل التعيين، وعامل yield
) كرأس أو جسم الأنبوب . عند استخدام |>
مع عوامل التشغيل هذه، يجب علينا استخدام الأقواس للإشارة بوضوح إلى المجموعة الصحيحة. على سبيل المثال، a |> b ? % : c |> %.d
بناء جملة غير صالح؛ يجب تصحيحه إما إلى a |> (b ? % : c) |> %.d
أو a |> (b ? % : c |> %.d)
.
وأخيرًا، لا يمكن استخدام روابط المواضيع داخل التعليمات البرمجية المجمعة ديناميكيًا (على سبيل المثال، مع eval
أو new Function
) خارج تلك التعليمات البرمجية. على سبيل المثال، v |> eval('% + 1')
سوف يلقي خطأ في بناء الجملة عندما يتم تقييم تعبير eval
في وقت التشغيل.
لا توجد قواعد خاصة أخرى .
النتيجة الطبيعية لهذه القواعد هي أنه إذا كنا بحاجة إلى إدخال تأثير جانبي في منتصف سلسلة من تعبيرات الأنابيب، دون تعديل البيانات التي يتم تمريرها من خلالها، فيمكننا استخدام تعبير فاصلة ، كما هو الحال مع value |> (sideEffect(), %)
. كالعادة، سيتم تقييم تعبير الفاصلة على الجانب الأيمن %
، ويمر بشكل أساسي عبر قيمة الموضوع دون تعديلها. وهذا مفيد بشكل خاص للتصحيح السريع: value |> (console.log(%), %)
.
كانت التغييرات الوحيدة على الأمثلة الأصلية هي إزالة الحواف وإزالة التعليقات.
من jquery/build/tasks/sourceMap.js:
// Status quo
var minLoc = Object . keys ( grunt . config ( "uglify.all.files" ) ) [ 0 ] ;
// With pipes
var minLoc = grunt . config ( 'uglify.all.files' ) | > Object . keys ( % ) [ 0 ] ;
من العقدة/deps/npm/lib/unpublish.js:
// Status quo
const json = await npmFetch . json ( npa ( pkgs [ 0 ] ) . escapedName , opts ) ;
// With pipes
const json = pkgs [ 0 ] | > npa ( % ) . escapedName | > await npmFetch . json ( % , opts ) ;
من underscore.js:
// Status quo
return filter ( obj , negate ( cb ( predicate ) ) , context ) ;
// With pipes
return cb ( predicate ) | > _ . negate ( % ) | > _ . filter ( obj , % , context ) ;
من ramda.js.