Go: الاقتراح: المواصفات: مرافق البرمجة العامة

تم إنشاؤها على ١٤ أبريل ٢٠١٦  ·  816تعليقات  ·  مصدر: golang/go

تقترح هذه المشكلة أن Go يجب أن تدعم شكلاً من أشكال البرمجة العامة.
يحتوي على ملصق Go2 ، نظرًا لأن اللغة Go1.x تم إنجازها بشكل أو بآخر.

يصاحب هذه المشكلة اقتراح عام للأدوية العامة من ianlancetaylor يتضمن أربعة مقترحات معيبة محددة لآليات البرمجة العامة لـ Go.

ليس القصد من ذلك إضافة أدوية عامة إلى Go في هذا الوقت ، ولكن بدلاً من ذلك لتوضيح كيف سيبدو الاقتراح الكامل للناس. نأمل أن يكون هذا مفيدًا لأي شخص يقترح تغييرات لغة مماثلة في المستقبل.

Go2 LanguageChange NeedsInvestigation Proposal generics

التعليق الأكثر فائدة

اسمحوا لي أن أذكر الجميع بشكل استباقي https://golang.org/wiki/NoMeToo سياستنا. حفلة الرموز التعبيرية أعلاه.

ال 816 كومينتر

يذكر CL https://golang.org/cl/22057 هذه المشكلة.

اسمحوا لي أن أذكر الجميع بشكل استباقي https://golang.org/wiki/NoMeToo سياستنا. حفلة الرموز التعبيرية أعلاه.

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

هناك نوعان من "المتطلبات" في الاقتراح المرتبط قد يؤديان إلى تعقيد التنفيذ وتقليل أمان النوع:

  • حدد الأنواع العامة بناءً على الأنواع غير المعروفة حتى يتم إنشاء مثيل لها.
  • لا تتطلب علاقة صريحة بين تعريف النوع أو الوظيفة العامة واستخدامها. وهذا يعني أنه لا ينبغي للبرامج أن تقول صراحة أن النوع T يطبق G.

يبدو أن هذه المتطلبات تستبعد على سبيل المثال نظامًا مشابهًا لنظام سمات Rust ، حيث تكون الأنواع العامة مقيدة بحدود السمات. لماذا هذه الحاجة؟

يصبح من المغري دمج الأدوية الجنيسة في المكتبة القياسية بمستوى منخفض جدًا ، كما هو الحال في C ++ std :: basic_string، الأمراض المنقولة جنسيا :: المخصص>. هذا له فوائده - وإلا لن يفعله أحد - ولكن له تأثيرات واسعة النطاق ومدهشة في بعض الأحيان ، كما هو الحال في رسائل خطأ C ++ غير المفهومة.

تنشأ المشكلة في C ++ من نوع التحقق من التعليمات البرمجية التي تم إنشاؤها. يجب أن يكون هناك فحص إضافي للنوع قبل إنشاء الكود. يتيح اقتراح مفاهيم C ++ هذا من خلال السماح لمؤلف الكود العام بتحديد متطلبات النوع العام. بهذه الطريقة ، يمكن أن يفشل التجميع في التحقق من النوع قبل إنشاء الرمز ويمكن طباعة رسائل الخطأ البسيطة. مشكلة الأدوية الجنيسة C ++ (بدون مفاهيم) هي أن الكود العام _هو _ مواصفات النوع العام. هذا ما يخلق رسائل الخطأ غير المفهومة.

يجب ألا يكون الرمز العام هو مواصفات النوع العام.

tamird إنها ميزة أساسية لأنواع واجهة Go التي يمكنك من خلالها تحديد نوع غير واجهة T ومن ثم تحديد نوع واجهة I مثل أن T تنفذ I. راجع https://golang.org/doc/faq#implements_interface . سيكون من غير المتسق إذا نفذت Go شكلاً من أشكال الأدوية التي لا يمكن استخدام النوع العام لها إلا مع النوع T الذي ينص صراحةً على أنه "يمكن استخدامي لتنفيذ G."

لست على دراية بـ Rust ، لكنني لا أعرف أي لغة تتطلب من T أن تنص صراحة على أنه يمكن استخدامها لتنفيذ G. لا يعني المتطلبان اللذان ذكرتهما أن G لا يمكنه فرض متطلبات على T ، فقط نظرًا لأنني أفرض متطلبات على T. ، فإن المتطلبات تعني فقط أنه يمكن كتابة G و T بشكل مستقل. هذه ميزة مرغوبة للغاية للأدوية ، ولا يمكنني تخيل التخلي عنها.

ianlancetaylor https://doc.rust-lang.org/book/traits.html يشرح سمات روست. بينما أعتقد أنهم نموذج جيد بشكل عام ، إلا أنهم سيكونون غير مناسبين لـ Go كما هو موجود اليوم.

sbunce لقد اعتقدت أيضًا أن المفاهيم هي الحل ، ويمكنك رؤية الفكرة مبعثرة في المقترحات المختلفة قبل آخرها. ولكن من المخيب للآمال أنه تم التخطيط أصلاً للمفاهيم لما أصبح C ++ 11 ، وهو الآن عام 2016 ، ولا تزال مثيرة للجدل وليست قريبة بشكل خاص من إدراجها في لغة C ++.

هل ستكون هناك قيمة للأدبيات الأكاديمية لأي إرشادات حول طرق التقييم؟

الورقة الوحيدة التي قرأتها حول هذا الموضوع هي هل يستفيد المطورون من الأنواع العامة؟ (paywall آسف ، قد جوجل طريقك إلى تنزيل pdf) الذي قال ما يلي

وبالتالي ، تفسير متحفظ للتجربة
هو أن الأنواع العامة يمكن اعتبارها مقايضة
بين خصائص التوثيق الإيجابية و
خصائص التمدد السلبية. الجزء المثير من
الدراسة هي أنها أظهرت حالة حيث استخدام
(أقوى) نظام النوع الثابت كان له تأثير سلبي على
وقت التطوير بينما في نفس الوقت المنفعة المتوقعة-
مناسب - تقليل وقت تحديد الخطأ من النوع - لم يظهر.
نعتقد أن مثل هذه المهام يمكن أن تساعد في التجارب المستقبلية في
تحديد تأثير أنظمة النوع.

أرى أيضًا https://github.com/golang/go/issues/15295 يشير أيضًا إلى الأدوية العامة خفيفة الوزن والمرنة الموجهة للكائنات .

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

يرجى الاطلاع على: http://dl.acm.org/citation.cfm؟id=2738008 بقلم باربرا ليسكوف:

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

أعتقد أن ما فعلوه هناك رائع جدًا - أنا آسف إذا كان هذا هو المكان غير الصحيح للتوقف ولكن لم أجد مكانًا للتعليق فيه / المقترحات ولم أجد مشكلة مناسبة هنا.

قد يكون من المثير للاهتمام أن يكون لديك واحد أو أكثر من transpilers التجريبية - شفرة مصدر Go Genics إلى Go 1.xy مصدر الشفرة.
أعني - الكثير من الحديث / الحجج مقابل رأيي ، ولا أحد يكتب شفرة المصدر التي _حاول_ تنفيذ _ نوعًا _ من الأدوية الجنيسة لـ Go.

فقط للحصول على المعرفة والخبرة مع Go and genics - لمعرفة ما يصلح وما لا ينجح.
إذا كانت جميع حلول Go Genics ليست جيدة حقًا ، إذن ؛ لا توجد أدوية جنيسة لـ Go.

هل يمكن أن يتضمن الاقتراح أيضًا الآثار المترتبة على الحجم الثنائي وبصمة الذاكرة؟ أتوقع أنه سيكون هناك تكرار في التعليمات البرمجية لكل نوع من أنواع القيمة الملموسة بحيث تعمل تحسينات المحول البرمجي عليها. آمل في الحصول على ضمان بعدم وجود تكرار في التعليمات البرمجية لأنواع المؤشرات الملموسة.

أقدم مصفوفة قرار بف. تشمل المعايير الخاصة بي تأثيرات الوضوح (تعقيد المصدر ، الحجم). كما أنني أجبرت معايير التصنيف لتحديد أوزان المعايير. قد تختلف الخاصة بك بالطبع. لقد استخدمت "واجهات" كبديل افتراضي وقارنت هذا بـ "نسخ / لصق" الأدوية الجنيسة ، والأدوية القائمة على القوالب (كنت أفكر في شيء مثل كيفية عمل لغة D) ، وشيء أطلق عليه اسم عامات أسلوب إنشاء مثيل وقت التشغيل. أنا متأكد من أن هذا تبسيط كبير للغاية. ومع ذلك ، قد يثير بعض الأفكار حول كيفية تقييم الخيارات ... يجب أن يكون هذا رابطًا عامًا إلى جدول بيانات Google الخاص بي ، هنا

Pingingyizhouzhang و andrewcmyers حتى يتمكنوا من التعبير عن آرائهم حول الجنس مثل الأدوية الجنسية في Go. يبدو أنها يمكن أن تكون مباراة جيدة :)

تصميم الأدوية التي توصلنا إليها لـ Genus يحتوي على فحص ثابت من النوع المعياري ، ولا يتطلب التصريح المسبق عن تلك الأنواع التي تنفذ واجهة ما ، وتأتي بأداء معقول. سأنظر إليه بالتأكيد إذا كنت تفكر في الأدوية الجنيسة لـ Go. يبدو أنه مناسب تمامًا من فهمي لـ Go.

فيما يلي رابط للورقة التي لا تتطلب الوصول إلى مكتبة ACM الرقمية:
http://www.cs.cornell.edu/andru/papers/genus/

صفحة Genus الرئيسية هنا: http://www.cs.cornell.edu/projects/genus/

لم نصدر المترجم علنًا بعد ، لكننا نخطط للقيام بذلك قريبًا.

سعيد للإجابة على أي أسئلة الناس.

من حيث مصفوفة قرار mandolyte ، سجل Genus 17 ، تعادل رقم 1. أود أن أضيف بعض المعايير للتسجيل ، رغم ذلك. على سبيل المثال ، يعد فحص النوع المعياري أمرًا مهمًا ، مثل الآخرين مثل sbunce التي تمت ملاحظتها أعلاه ، لكن المخططات القائمة على القوالب تفتقر إليها. يحتوي التقرير الفني لورقة Genus على جدول أكبر بكثير في الصفحة 34 ، يقارن بين تصاميم الأدوية الجنيسة المختلفة.

لقد قمت للتو بالاطلاع على ملخص وثيقة Go Generics بالكامل ، والتي كانت تلخيصًا مفيدًا للمناقشات السابقة. لا تعاني آلية الأدوية الجنيسة في Genus ، في رأيي ، من المشكلات المحددة لـ C ++ أو Java أو C #. يتم تجسيد الأجناس الجنسية ، على عكس Java ، بحيث يمكنك معرفة الأنواع في وقت التشغيل. يمكنك أيضًا إنشاء مثيل للأنواع البدائية ، ولا تحصل على ملاكمة ضمنية في الأماكن التي لا تريدها حقًا: مصفوفات من T حيث T هي بدائية. نظام الكتابة هو الأقرب إلى Haskell و Rust - في الواقع أقوى قليلاً ، لكنني أعتقد أيضًا أنه بديهي. التخصص البدائي ala C # غير مدعوم حاليًا في Genus ولكن يمكن أن يكون كذلك. في معظم الحالات ، يمكن تحديد التخصص في وقت الارتباط ، لذلك لن يكون إنشاء رمز وقت التشغيل الحقيقي مطلوبًا.

يذكر CL https://golang.org/cl/22163 هذه المشكلة.

طريقة لتقييد الأنواع العامة التي لا تتطلب إضافة مفاهيم لغة جديدة: https://docs.google.com/document/d/1rX4huWffJ0y1ZjqEpPrDy-kk-m9zWfatgCluGRBQveQ/edit؟usp=sharing.

يبدو Genus رائعًا حقًا ومن الواضح أنه تقدم مهم في الفن ، لكنني لا أرى كيف يمكن تطبيقه على Go. هل لدى أي شخص رسم تخطيطي لكيفية تكامله مع نظام / فلسفة نوع Go؟

المشكلة هي أن فريق go يعرقل المحاولات. يوضح العنوان بوضوح نوايا فريق go. وإذا لم يكن ذلك كافيًا لردع جميع المتلقين ، فإن الميزات المطلوبة من هذا النطاق الواسع في المقترحات من قبل إيان توضح أنه إذا كنت تريد الأدوية الجنيسة ، فهم لا يريدونك. من غير المألوف محاولة الحوار مع فريق go. لأولئك الذين يبحثون عن الأدوية الجنيسة ، أقول كسر اللغة. ابدأ رحلة جديدة - سيتبعها الكثير. لقد رأيت بالفعل بعض الأعمال الرائعة المنجزة في الشوكات. نظموا أنفسكم ، وحشدوا حول قضية

إذا أراد أي شخص محاولة إنشاء امتداد للأدوية الجنيسة إلى Go استنادًا إلى تصميم Genus ، يسعدنا تقديم المساعدة. نحن لا نعرف ما يكفي لإنتاج تصميم يتوافق مع اللغة الحالية. أعتقد أن الخطوة الأولى ستكون اقتراح تصميم بسيط مع أمثلة مدروسة.

andrewcmyers على أمل أن ianlancetaylor سيعمل معك على ذلك. مجرد وجود بعض الأمثلة للنظر فيها سيساعد كثيرًا.

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

أحد الجوانب الرئيسية في Go هو أنه عندما تكتب برنامج Go ، فإن معظم ما تكتبه هو رمز. هذا يختلف عن C ++ و Java ، حيث يكون الكثير مما تكتبه من الأنواع. يبدو أن الجنس في الغالب يتعلق بالأنواع: أنت تكتب قيودًا ونماذج ، بدلاً من رمز. نظام كتابة Go بسيط للغاية. نظام نوع الجنس أكثر تعقيدًا بكثير.

أفكار النمذجة بأثر رجعي ، رغم أنها مفيدة بشكل واضح لجافا ، لا يبدو أنها مناسبة لـ Go على الإطلاق. يستخدم الأشخاص بالفعل أنواع المحولات لمطابقة الأنواع الموجودة بالواجهات ؛ لا ينبغي أن تكون هناك حاجة إلى أي شيء آخر عند استخدام الأدوية الجنيسة.

سيكون من المثير للاهتمام رؤية هذه الأفكار مطبقة على Go ، لكنني لست متفائلًا بشأن النتيجة.

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

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

لن أتفاجأ إذا كانت أكثر تعقيدًا من (ما قبل عام) جافا بالمعنى النظري للنوع ، على الرغم من أنه من الأسهل استخدامها في الممارسة.

وبغض النظر عن التعقيد النسبي ، فإنهما مختلفان بدرجة كافية بحيث لا يستطيع Genus تعيين 1: 1. لا يوجد تصنيف فرعي يبدو كبيرًا.

إذا كنت مهتما:

يوجد ملخص موجز للاختلافات الفلسفية / التصميمية ذات الصلة التي ذكرتها في إدخالات الأسئلة الشائعة التالية:

على عكس معظم اللغات ، فإن مواصفات Go قصيرة جدًا وواضحة حول الخصائص ذات الصلة لنظام الكتابة تبدأ من https://golang.org/ref/spec#Constants وتنتقل مباشرة حتى القسم المعنون "Blocks" (وكلها أقل من 11 صفحة مطبوعة).

على عكس Java و C # Genics ، لا تعتمد آلية Genus Genus على التصنيف الفرعي. من ناحية أخرى ، يبدو لي أن Go لديه تصنيف فرعي ، لكن تصنيف فرعي هيكلي. يعد هذا أيضًا تطابقًا جيدًا مع نهج الجنس ، والذي له نكهة هيكلية بدلاً من الاعتماد على العلاقات المعلنة مسبقًا.

لا أعتقد أن Go له تصنيف فرعي هيكلي.

في حين أن النوعين الأساسيين متطابقان هما بالتالي متطابقان
يمكن استبدالها ببعضها البعض ، https://play.golang.org/p/cT15aQ-PFr

لا يمتد هذا إلى نوعين يشتركان في مجموعة فرعية مشتركة من الحقول ،
https://play.golang.org/p/KrC9_BDXuh.

يوم الخميس ، 28 أبريل 2016 الساعة 1:09 مساءً ، Andrew Myers [email protected]
كتب:

على عكس Java و C # Genics ، لا تعتمد آلية Genus Genus
تصنيف فرعي. من ناحية أخرى ، يبدو لي أن Go لديه تصنيف فرعي ،
لكن التصنيف الفرعي الهيكلي. هذا أيضًا تطابق جيد لنهج الجنس ،
التي لها نكهة هيكلية بدلاً من الاعتماد على المعلن مسبقًا
العلاقات.

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment -215298127

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

هذا هو بالضبط السبب الذي جعلني أزعجك ، يبدو أن الجنس هو نهج أفضل بكثير من Java / C # مثل الأدوية الجنيسة.

كانت هناك بعض الأفكار فيما يتعلق بالتخصص في أنواع الواجهة ؛ على سبيل المثال نهج _ قوالب العبوة_ "مقترحات" 1 2 هي أمثلة على ذلك.

TL ؛ د. تبدو الحزمة العامة مع تخصص الواجهة بالشكل التالي:

package set
type E interface { Equal(other E) bool }
type Set struct { items []E }
func (s *Set) Add(item E) { ... }

الإصدار 1. مع تخصص نطاق النطاق:

package main
import items set[[E: *Item]]

type Item struct { ... }
func (a *Item) Equal(b *Item) bool { ... }

var xs items.Set
xs.Add(&Item{})

الإصدار 2. إعلان نطاق التخصص:

package main
import set

type Item struct { ... }
func (a *Item) Equal(b *Item) bool { ... }

var xs set.Set[[E: *Item]]
xs.Add(&Item{})

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

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

type E interface{}
func Reduce(zero E, items []E, fn func(a, b E) E) E { ... }

Reduce[[E: int]](0, []int{1,2,3,4}, func(a, b int)int { return a + b } )
// there are probably ways to have some aliases (or similar) to make it less verbose
alias ReduceInt Reduce[[E: int]]
func ReduceInt Reduce[[E: int]]

يتميز نهج تخصص الواجهة بخصائص مثيرة للاهتمام:

  • ستكون الحزم الموجودة بالفعل التي تستخدم الواجهات قابلة للتخصص. على سبيل المثال ، سأكون قادرًا على الاتصال بـ sort.Sort[[Interface:MyItems]](...) ولدي عمل الفرز على النوع الملموس بدلاً من الواجهة (مع مكاسب محتملة من التضمين).
  • تم تبسيط الاختبار ، ولا يلزمني سوى التأكد من أن الكود العام يعمل مع الواجهات.
  • من السهل تحديد كيفية عملها. على سبيل المثال ، تخيل أن [[E: int]] يستبدل كل تعريفات E بـ int .

ولكن ، توجد مشكلات في الإسهاب عند العمل عبر الحزم:

type op
import "set"

type E interface{}
func Union(a, b set.Set[[set.E: E]]) set.Set[[set.E: E]] {
    result := set.New[[set.E: E]]()
    ...
}

_بالطبع ، الأمر برمته أسهل في تحديده من تنفيذه. داخليًا ، من المحتمل أن يكون هناك الكثير من المشاكل والطرق التي يمكن أن تعمل بها.

_PS ، إلى المتذمرين بشأن التقدم البطيء في الأدوية الجنيسة ، أحيي فريق Go لقضاء المزيد من الوقت في المشكلات التي تعود بفائدة أكبر على المجتمع ، مثل أخطاء المترجم / وقت التشغيل ، SSA ، GC ، http2._

egonelbre إن وجهة نظرك القائلة بأن الأدوية الجنيسة على مستوى الحزمة ستمنع "إساءة الاستخدام" هي نقطة مهمة حقًا أعتقد أن معظم الناس يتجاهلونها. هذا بالإضافة إلى بساطتها النسبية الدلالية والنحوية (تتأثر فقط الحزم وبنى الاستيراد) مما يجعلها جذابة للغاية لـ Go.

andrewcymyers المثير للاهتمام أنك تعتقد أن واجهات Go تعمل كقيود على غرار Genus. كنت أعتقد أنه لا يزال لديهم مشكلة أنه لا يمكنك التعبير عن قيود المعلمات متعددة الأنواع معهم.

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

اكتب [V، E] رسم بياني [واجهة V {Edges () E}، واجهة E {Endpoints () (V، V)}] ...

أعتقد أن المشكلة الأكبر في الواجهات مثل القيود هي أن الأساليب ليست منتشرة في Go كما في Java. الأنواع المضمنة ليس لها طرق. لا توجد مجموعة من الأساليب العامة مثل تلك الموجودة في java.lang.Object. لا يقوم المستخدمون عادةً بتعريف طرق مثل Equals أو HashCode على أنواعها ما لم يحتاجوا إلى ذلك تحديدًا ، لأن هذه الطرق لا تؤهل نوعًا للاستخدام كمفاتيح خريطة ، أو في أي خوارزمية تحتاج إلى المساواة.

(المساواة في Go هي قصة شيقة. تعطي اللغة نوعك "==" إذا كان يفي بمتطلبات معينة (راجع https://golang.org/ref/spec#Logical_operators ، ابحث عن "قابلة للمقارنة"). أي نوع بـ " == "يمكن أن يكون بمثابة مفتاح خريطة. ولكن إذا كان نوعك لا يستحق" == "، فلا يوجد شيء يمكنك كتابته يجعله يعمل كمفتاح خريطة.)

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

الطريقة الأخرى للذهاب هي إضافة طرق لأنواع تفتقر إليها. يمكنك القيام بذلك باللغة الحالية بطريقة أخف بكثير مما في Java:

اكتب كثافة العمليات
func (i Int) منطقي أقل (j Int) {return i <j}

نوع Int "يرث" كل عوامل التشغيل والخصائص الأخرى لـ int. على الرغم من أنه يتعين عليك الإدلاء بين الاثنين لاستخدام Int و int معًا ، مما قد يكون أمرًا مؤلمًا.

نماذج الجنس يمكن أن تساعد هنا. لكن يجب أن تظل بسيطة للغاية. أعتقد أن ianlancetaylor كان ضيقًا جدًا في توصيفه لـ Go على أنه كتابة المزيد من التعليمات البرمجية وأنواع أقل. المبدأ العام هو أن Go يمقت التعقيد. نحن ننظر إلى Java و C ++ ومصممون على عدم الذهاب إلى هناك أبدًا. (لا اقصد التقليل من شأنك.)

لذا فإن إحدى الأفكار السريعة لميزة تشبه النموذج ستكون: اجعل المستخدم يكتب أنواعًا مثل Int أعلاه ، وفي عمليات إنشاء مثيل عامة تسمح بـ "int with Int" ، وهذا يعني استخدام النوع int ولكن تعامل معه مثل Int. ثم لا يوجد بناء لغة صريح يسمى النموذج ، مع الكلمة الرئيسية الخاصة به ، ودلالات الوراثة ، وما إلى ذلك. لا أفهم النماذج جيدًا بما يكفي لأعرف ما إذا كان ذلك ممكنًا أم لا ، ولكن الأمر يتعلق بروح Go.

jba نحن بالتأكيد نتفق مع مبدأ تجنب التعقيد. "بسيط قدر الإمكان ولكن ليس أبسط." من المحتمل أن أترك بعض ميزات Genus خارج Go على هذه الأسس ، على الأقل في البداية.

من الأشياء الرائعة في نهج Genus أنه يتعامل مع الأنواع المضمنة بسلاسة. تذكر أن الأنواع البدائية في Java ليس لها طرق ، وأن Genus يرث هذا السلوك. بدلاً من ذلك ، يتعامل Genus مع الأنواع البدائية - كما لو - كان لديهم مجموعة كبيرة نسبيًا من الأساليب بغرض تلبية القيود. يتطلب جدول التجزئة إمكانية تجزئة مفاتيحه ومقارنتها ، لكن جميع الأنواع الأولية تفي بهذا القيد. لذا فإن عمليات إنشاء مثيل مثل Map [int، boolean] تعتبر قانونية تمامًا بدون أي ضجة أخرى. ليست هناك حاجة للتمييز بين نوعين من الأعداد الصحيحة (int مقابل Int) لتحقيق ذلك. ومع ذلك ، إذا لم تكن int مزودة بعمليات كافية لبعض الاستخدامات ، فسنستخدم نموذجًا تقريبًا مثل استخدام Int أعلاه.

شيء آخر جدير بالذكر هو فكرة "النماذج الطبيعية" في الجنس. لا يتعين عليك عادةً التصريح عن نموذج لاستخدام نوع عام: إذا كانت وسيطة النوع تفي بالقيد ، فسيتم إنشاء نموذج طبيعي تلقائيًا. تجربتنا هي أن هذه هي الحالة المعتادة ؛ عادة لا تكون هناك حاجة للإعلان عن نماذج صريحة مسماة. ولكن إذا كانت هناك حاجة إلى نموذج - على سبيل المثال ، إذا كنت تريد تجزئة ints بطريقة غير قياسية - فإن بناء الجملة يكون مشابهًا لما اقترحته: Map [int with fancyHash، boolean] . أود أن أزعم أن Genus خفيف من الناحية التركيبية في حالات الاستخدام العادية ولكن مع وجود طاقة احتياطية عند الحاجة.

egonelbre ما تقترحه هنا يبدو وكأنه أنواع افتراضية تدعمها Scala. هناك ورقة ECOOP'97 كتبها Kresten Krab Thorup ، "Genericity في Java مع الأنواع الافتراضية" ، والتي تستكشف هذا الاتجاه. قمنا أيضًا بتطوير آليات للأنواع الافتراضية والفصول الافتراضية في عملنا ("J &: تقاطع متداخل لتكوين برامج قابلة للتطوير" ، OOPSLA'06).

نظرًا لأن التهيئة الحرفية منتشرة في Go ، كان علي أن أتساءل كيف ستبدو الوظيفة الحرفية. أظن أن الكود الخاص بمعالجة هذا موجود إلى حد كبير في Go إنشاء وإصلاح وإعادة تسمية. ربما سيلهم شخصًا ما :-)

// تعريف نوع func (العام)
اكتب Sum64 func (X ، Y) float64 {
إرجاع float64 (X) + float64 (Y)
}

// إنشاء واحد ، موضعيًا
أنا: = 42
var j uint = 86
المجموع: = & Sum64 {i، j}

// إنشاء واحد ، من خلال أنواع المعلمات المسماة
sum: = & Sum64 {X: int، Y: uint}

// استخدمه الآن ...
النتيجة: = sum (i، j) // النتيجة هي 128

يتطلب اقتراح إيان الكثير. لا يمكننا تطوير جميع الميزات دفعة واحدة ، وستظل موجودة في حالة غير مكتملة لعدة أشهر.

في غضون ذلك ، لا يمكن تسمية المشروع غير المكتمل بلغة Go الرسمية حتى يتم الانتهاء منه لأن ذلك سيخاطر بتفتيت النظام البيئي.

لذا فإن السؤال هو كيف تخطط لذلك.

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

بناء جملة ممكن؟

// Module defining generic type
module list(type t)

type node struct {
    next *node
    data t
}
// Module using generic type:
import (
    intlist "module/path/to/list" (int)
    funclist "module/path/to/list" (func (int) int)
)

l := intlist.New()
l.Insert(5)

@ md2perpe ، بناء الجملة ليس الجزء الصعب من هذه المشكلة. في الواقع ، إنه أسهل بكثير. يرجى الاطلاع على المناقشة والمستندات المرتبطة أعلاه.

@ md2perpe لقد ناقشنا معلمات الحزم الكاملة ("الوحدات") كطريقة للتعميم داخليًا - يبدو أنها طريقة لتقليل الحمل النحوي. لكن لها قضايا أخرى. على سبيل المثال ، ليس من الواضح كيف يمكن للمرء أن يحددها بأنواع ليست على مستوى الحزمة. لكن الفكرة قد لا تزال تستحق الاستكشاف بالتفصيل.

أرغب في مشاركة منظور: في الكون الموازي ، كانت جميع تواقيع وظائف Go مقيدة دائمًا بذكر أنواع الواجهة فقط ، وبدلاً من الطلب على الأدوية الجنيسة اليوم ، هناك طريقة لتجنب المراوغة المرتبطة بقيم الواجهة. فكر في كيفية حل هذه المشكلة (دون تغيير اللغة). لدي بعض الأفكار.

thwd إذن ، سيستمر مؤلف المكتبة في استخدام الواجهات ، ولكن بدون تبديل النوع وتأكيدات النوع المطلوبة اليوم. وهل يمكن لمستخدم المكتبة أن يمر ببساطة في أنواع محددة كما لو أن المكتبة ستستخدم الأنواع كما هي ... وبعد ذلك هل سيقوم المترجم بالتوفيق بين الاثنين؟ وإذا لم تستطع أن تذكر لماذا؟ (مثل استخدام مشغل modulo في المكتبة ، لكن المستخدم قدم شريحة من شيء ما.

هل أنا قريب؟ :-)

تضمين التغريدة دعونا نتبادل رسائل البريد الإلكتروني حتى لا تلوث هذا الموضوع. يمكنك الوصول إلي في "me at thwd dot me". أي شخص آخر قد يكون مهتمًا بقراءة هذا ؛ أرسل لي بريدًا إلكترونيًا وسأضيفك إلى الموضوع.

إنها ميزة رائعة مقابل type system و collection library .
بناء جملة محتمل:

type Element<T> struct {
    prev, next *Element<T>
    list *List<T>
    value T
}
type List<E> struct {
    root Element<E>
    len int
}

مقابل interface

type Collection<E> interface {
    Size() int
    Add(e E) bool
}

super type أو type implement :

func contain(l List<parent E>, e E) bool
<V> func (c Collection<child E>)Map(fn func(e E) V) Collection

ما ورد أعلاه الملقب في جافا:

boolean contain(List<? super E>, E e)
<V> Collection Map(Function<? extend E, V> mapFunc);

@ leaxoy كما قيل من قبل ، بناء الجملة ليس الجزء الصعب هنا. انظر المناقشة أعلاه.

فقط كن على علم بأن تكلفة الواجهة ضخمة بشكل لا يصدق.

يُرجى توضيح سبب اعتقادك أن تكلفة الواجهة "لا تُصدق"
كبير.
لا ينبغي أن يكون أسوأ من مكالمات C ++ الافتراضية غير المتخصصة.

minux لا أستطيع أن أقول عن تكاليف الأداء ولكن فيما يتعلق بجودة الكود. لا يمكن التحقق من interface{} في وقت الترجمة ولكن يمكن التحقق من الأدوية الجنيسة. في رأيي ، هذا ، في معظم الحالات ، أكثر أهمية من مشكلات الأداء لاستخدام interface{} .

تضمين التغريدة

ليس هناك حقًا أي جانب سلبي لهذا لأن المعالجة المطلوبة لهذا لا تبطئ من عمل المترجم.

هناك جانبان (على الأقل) من الجوانب السلبية.

أحدهما هو زيادة العمل للرابط: إذا كانت التخصصات لنوعين تؤدي إلى نفس رمز الجهاز الأساسي ، فنحن لا نريد تجميع نسختين من هذا الرمز وربطهما.

آخر هو أن الحزم ذات المعلمات أقل تعبيرًا من الطرق ذات المعلمات. (راجع المقترحات المرتبطة بالتعليق الأول للحصول على التفاصيل.)

هل الكتابة المفرطة فكرة جيدة؟

func getAddFunc (aType type) func(aType, aType) aType {
    return func(a, b aType) aType {
        return a+b
    }
}

هل الكتابة المفرطة فكرة جيدة؟

ما تصفه هنا هو مجرد كتابة معلمات ala C ++ (على سبيل المثال ، قوالب). إنه لا يقوم بالتحقق من الكتابة بطريقة معيارية لأنه لا توجد طريقة لمعرفة أن النوع aType يحتوي على + عملية من المعلومات المقدمة. معلمات النوع المقيدة كما هو الحال في CLU و Haskell و Java و Genus هو الحل.

@ golang101 لدي اقتراح مفصل على هذا المنوال. سأرسل CL لإضافته إلى القائمة ، لكن من غير المحتمل أن يتم اعتماده.

يذكر CL https://golang.org/cl/38731 هذه المشكلة.

تضمين التغريدة

إنه لا يقوم بالتحقق من الكتابة بطريقة معيارية لأنه لا توجد طريقة لمعرفة أن النوع aType يحتوي على + عملية من المعلومات المقدمة.

بالتأكيد هناك. هذا القيد ضمني في تعريف الوظيفة ، ويمكن نشر قيود هذا النموذج لجميع مستدعي وقت الترجمة (متعدية) لـ getAddFunc .

القيد ليس جزءًا من Go _type_ - أي أنه لا يمكن ترميزه في نظام النوع لجزء وقت التشغيل من اللغة - لكن هذا لا يعني أنه لا يمكن تقييمه بطريقة معيارية.

تمت إضافة اقتراحي كـ 2016-09-compile-time-functions.md .

لا أتوقع أن يتم تبنيها ، لكنها على الأقل يمكن أن تكون بمثابة نقطة مرجعية مثيرة للاهتمام.

bcmills أشعر أن تجميع وظائف الوقت فكرة قوية ، بصرف النظر عن أي اعتبار للأدوية. على سبيل المثال ، لقد كتبت أداة حل سودوكو تحتاج إلى popcount. لتسريع ذلك ، قمت بحساب عدد popcounts للقيم المحتملة المختلفة وقمت بتخزينها كمصدر Go . هذا شيء يمكن للمرء فعله بـ go:generate . ولكن إذا كانت هناك وظيفة وقت ترجمة ، فيمكن حساب جدول البحث هذا أيضًا في وقت الترجمة ، مما يحافظ على الكود الذي تم إنشاؤه بواسطة الجهاز من الاضطرار إلى الالتزام بإعادة الشراء. بشكل عام ، يعد أي نوع من الوظائف الرياضية القابلة للحفظ مناسبًا لجداول البحث المعدة مسبقًا مع وظائف وقت الترجمة.

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

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

بالنسبة للجزء الثاني ، لا أرى طريقة يمكنك من خلالها إنشاء شيء مثل "شريحة من الوظائف تعالج شرائح من نوع معين وتعيد عنصرًا واحدًا" ، أو في بنية مخصصة []func<T>([]T) T ، والتي من السهل جدًا القيام به في كل لغة وظيفية مكتوبة بشكل ثابت. ما نحتاجه حقًا هو أن تكون القيم قادرة على التعامل مع الأنواع البارامترية ، وليس إنشاء كود على مستوى شفرة المصدر.

تضمين التغريدة

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

إذا كنت تتحدث عن معلمة من نوع واحد ، فسيتم كتابتها في اقتراحي:

const func SliceOfSelectors(T gotype) gotype { return []func([]T)T (type) }

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

نعم ، ولكن في رأيي ، يجب السماح بهذا النوع من الأشياء التي تتطلب الملاكمة مع الحفاظ على أمان الكتابة ، ربما باستخدام صيغة خاصة تشير إلى "الصندوق". يتمثل جزء كبير من إضافة "الأدوية الجنيسة" في تجنب النوع غير الآمن من interface{} حتى عندما لا يمكن تجنب النفقات العامة interface{} . (ربما تسمح فقط ببناءات نوع حدودي معينة مع أنواع المؤشر والواجهة المعبأة "بالفعل"؟ الكائنات المعبأة في Java Integer إلخ ليست فكرة سيئة تمامًا ، على الرغم من أن شرائح أنواع القيم صعبة)

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

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

من ناحية أخرى ، إذا استوعبت تواقيع الكتابة المتطلبات الخاصة بمعلمات النوع ، كما هو الحال في لغات مثل CLU أو ML أو Haskell أو Genus ، فيمكن تجميع وحدة نمطية دون أي وصول إلى الأجزاء الداخلية للوحدات التي تعتمد عليها.

تضمين التغريدة

يتمثل جزء كبير من إضافة "العناصر العامة" في تجنب النوع غير الآمن للواجهة {} حتى عندما لا يمكن تجنب الحمل الزائد للواجهة {}.

"لا يمكن تجنبه" نسبي. لاحظ أن النفقات العامة للملاكمة هي النقطة رقم 3 في منشور روس من عام 2009 (https://research.swtch.com/generic).

توقع أن يكون لدى Go2 نظام نوع حدودي حديث قائم على نظرية نوع الصوت بدلاً من الاختراق المستند إلى معالجة أجزاء من الكود المصدري

إن "نظرية نوع الصوت" الجيدة هي نظرية وصفية وليست إلزامية. يستمد اقتراحي على وجه الخصوص من حساب لامدا من الدرجة الثانية (على غرار النظام F) ، حيث يشير gotype إلى النوع type ونظام نوع الترتيب الأول بأكمله يتم رفعه في الثانية -ترتيب أنواع ("وقت الترجمة").

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

صحيح أن نظرية النوع الأساسي في اقتراحي أقل تحديدًا رسميًا مما كانت عليه في المؤلفات الأكاديمية ، لكن هذا لا يعني أنها غير موجودة.

تضمين التغريدة

إذا كانت الوحدة "أ" تستخدم الوحدة "ب" ، والتي تستخدم الوحدة "ج" ، والتي تستخدم الوحدة "د" ، فقد يحتاج التغيير في كيفية استخدام معلمة النوع في "د" إلى الانتشار طوال الطريق إلى "أ" ، حتى إذا لم يكن لدى منفذ "أ" فكرة أن "د" موجود في النظام.

هذا صحيح بالفعل في Go today: إذا نظرت بعناية ، ستلاحظ أن ملفات الكائن التي تم إنشاؤها بواسطة المترجم لحزمة Go معينة تتضمن معلومات عن أجزاء التبعيات متعدية التي تؤثر على واجهة برمجة التطبيقات المصدرة.

سيتم إضعاف أداة التوصيل السائبة التي توفرها أنظمة الوحدات ، وستكون البرامج أكثر هشاشة.

لقد سمعت أن نفس الحجة المستخدمة للدعوة إلى تصدير أنواع interface بدلاً من الأنواع الملموسة في Go APIs ، واتضح أن العكس هو أكثر شيوعًا: التجريد المبكر يفرط في أنواع ويعيق امتداد واجهات برمجة التطبيقات. (على سبيل المثال ، انظر # 19584.) إذا كنت تريد الاعتماد على هذا الخط من الجدل ، أعتقد أنك بحاجة إلى تقديم بعض الأمثلة الملموسة.

هذه إحدى مشكلات قوالب C ++.

كما أراها ، فإن المشاكل الرئيسية مع قوالب C ++ هي (بدون ترتيب معين):

  • الغموض النحوي المفرط.
    أ. الغموض بين أسماء الأنواع وأسماء القيم.
    ب. دعم واسع بشكل مفرط للحمل الزائد على المشغل ، مما يؤدي إلى ضعف القدرة على استنتاج القيود من استخدام المشغل.
  • الاعتماد المفرط على دقة التحميل الزائد للبرمجة الوصفية (أو ، على نحو مكافئ ، التطور المخصص لدعم البرمجة الوصفية).
    أ. خاصة قواعد WRT المرجعية-انهيار.
  • تطبيق واسع للغاية لمبدأ SFINAE ، مما يؤدي إلى قيود يصعب نشرها والعديد من الشروط الضمنية في تعريفات النوع ، مما يؤدي إلى الإبلاغ عن الأخطاء الصعبة للغاية.
  • الإفراط في استخدام اللصق الرمزي والتضمين النصي (المعالج الأولي C) بدلاً من بديل AST ومصنوعات التجميع ذات الترتيب الأعلى (والتي يبدو أنها تمت معالجتها جزئيًا على الأقل باستخدام الوحدات النمطية).
  • عدم وجود لغات تمهيد جيدة لمترجمي C ++ ، مما يؤدي إلى ضعف الإبلاغ عن الأخطاء في سلالات المترجم طويل العمر (مثل GCC toolchain).
  • مضاعفة (وأحيانًا مضاعفة) الأسماء الناتجة عن تعيين مجموعات من المشغلين على "مفاهيم" مختلفة الأسماء (بدلاً من معاملة المشغلين أنفسهم كقيود أساسية).

لقد كنت أقوم بالتشفير في C ++ وإيقافه لمدة عقد حتى الآن ويسعدني مناقشة أوجه القصور في C ++ مطولًا ، لكن حقيقة أن تبعيات البرنامج متعدية لم تكن أبدًا بالقرب من أعلى قائمة الشكاوى الخاصة بي.

من ناحية أخرى ، هل تحتاج إلى تحديث سلسلة من تبعيات O (N) فقط لإضافة طريقة واحدة إلى نوع في الوحدة A وتكون قادرًا على استخدامها في الوحدة D؟ هذا هو نوع المشكلة الذي يبطئني بشكل منتظم. حيث تتعارض البارامترية والاقتران الفضفاض ، سأختار البارامترية في أي يوم.

ومع ذلك ، فإنني أعتقد اعتقادًا راسخًا أنه يجب الفصل بين البرمجة الوصفية وتعدد الأشكال البارامتري ، وأن ارتباك C ++ بينهما هو السبب الجذري لكون قوالب C ++ مزعجة. ببساطة ، تحاول C ++ تنفيذ فكرة نظرية النوع باستخدام وحدات ماكرو أساسية على المنشطات ، وهو أمر يمثل مشكلة كبيرة لأن المبرمجين يحبون التفكير في القوالب على أنها تعدد أشكال بارامتي حقيقي ويتعرضون لسلوك غير متوقع. تعتبر وظائف وقت الترجمة فكرة رائعة للبرمجة الوصفية واستبدال الاختراق الذي هو go generate ، لكنني لا أعتقد أنه يجب أن يكون الطريقة المباركة للقيام بالبرمجة العامة.

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

func <T io.Reader> ReadAll(in T)

والتي يجب أن تتجنب الحمل الزائد للواجهة (مثل استخدام Rust) ، على الرغم من أنها في هذه الحالة ليست مفيدة للغاية.

قد يكون أفضل مثال على ذلك هو الحزمة sort ، حيث يمكنك الحصول على شيء مثل

func <T Comparable> Sort(slice []T)

حيث Comparable هو ببساطة واجهة قديمة جيدة يمكن للأنواع تنفيذها. يمكن عندئذٍ استدعاء Sort على شريحة من أنواع القيم التي تنفذ Comparable ، دون وضعها في الصناديق في أنواع الواجهة.

bcmills التبعيات العابرة غير المقيدة بنظام النوع ، في رأيي ، هي جوهر بعض شكواك حول C ++. لا تمثل التبعيات المتعدية مشكلة كبيرة إذا كنت تتحكم في الوحدات A و B و C و D. بشكل عام ، أنت تطور الوحدة A وقد تكون مدركًا بشكل ضعيف أن الوحدة D موجودة هناك ، وعلى العكس ، مطور D قد لا يكون على دراية بـ A. إذا كانت الوحدة D الآن ، دون إجراء أي تغيير على الإعلانات المرئية في D ، تبدأ في استخدام عامل جديد على معلمة نوع - أو تستخدم فقط معلمة النوع هذه كوسيطة نوع إلى وحدة نمطية جديدة E مع وحدة نمطية خاصة بها القيود الضمنية - ستنتقل هذه القيود إلى جميع العملاء ، الذين قد لا يستخدمون وسيطات النوع التي تفي بالقيود. لا شيء يخبر المطور D أنهم يتفوقون عليه. في الواقع ، لديك نوع من الاستدلال العام ، مع كل صعوبات التصحيح التي تستتبعها.

أعتقد أن النهج الذي اتخذناه في Genus [ PLDI'15 ] أفضل بكثير. معلمات النوع لها قيود واضحة ، لكن خفيفة الوزن (أتناول وجهة نظرك حول دعم قيود العملية ؛ أظهر CLU كيفية القيام بذلك بشكل صحيح طوال الطريق في عام 1977). فحص نوع الجنس معياري بالكامل. يمكن تجميع التعليمات البرمجية العامة مرة واحدة فقط لتحسين مساحة التعليمات البرمجية أو تخصيصها لوسائط نوع معين للحصول على أداء جيد.

تضمين التغريدة

إذا كانت الوحدة D الآن ، دون إجراء أي تغيير على الإعلانات المرئية في D ، تبدأ في استخدام عامل جديد على معلمة نوع [...] [العملاء] قد لا تستخدم وسائط النوع التي تفي بالقيود. لا شيء يخبر المطور D أنهم يتفوقون عليه.

بالتأكيد ، لكن هذا صحيح بالفعل بالنسبة للعديد من القيود الضمنية في Go بشكل مستقل عن أي آلية برمجة عامة.

على سبيل المثال ، قد تتلقى الوظيفة معلمة من نوع الواجهة وتستدعي طرقها في البداية بالتسلسل. إذا تغيرت هذه الوظيفة لاحقًا لاستدعاء هذه الأساليب بشكل متزامن (عن طريق توليد goroutines إضافية) ، فإن القيد "يجب أن يكون آمنًا للاستخدام المتزامن" لا ينعكس في نظام النوع.

وبالمثل ، لا يحدد نظام النوع Go اليوم قيودًا على الأعمار المتغيرة: تفترض بعض تطبيقات io.Writer خطأً أنها يمكن أن تحتفظ بمرجع للشريحة التي تم تمريرها والقراءة منها لاحقًا (على سبيل المثال عن طريق القيام بالكتابة الفعلية بشكل غير متزامن في goroutine الخلفية) ، ولكن هذا يتسبب في حدوث سباقات البيانات إذا حاول المتصل Write إعادة استخدام نفس شريحة الدعم مقابل Write لاحق.

أو قد تأخذ وظيفة تستخدم مفتاح نوع مسارًا مختلفًا تتم إضافة طريقة ما إلى أحد الأنواع الموجودة في المحول.

أو قد تتعطل إحدى الوظائف التي تتحقق من رمز خطأ معين إذا غيّرت الوظيفة التي تولد الخطأ الطريقة التي تُبلغ بها عن هذا الشرط. (على سبيل المثال ، راجع https://github.com/golang/go/issues/19647.)

أو قد يتعطل فحص دالة لنوع خطأ معين إذا تمت إضافة أغلفة حول الخطأ أو إزالتها (كما حدث في الحزمة القياسية net في Go 1.5).

أو قد يتغير التخزين المؤقت على قناة مكشوفة في واجهة برمجة التطبيقات ، مما يؤدي إلى حدوث حالات توقف و / أو سباقات.

...وما إلى ذلك وهلم جرا.

Go ليس غريبًا في هذا الصدد: القيود الضمنية منتشرة في كل مكان في برامج العالم الحقيقي.


إذا حاولت التقاط جميع القيود ذات الصلة في التعليقات التوضيحية الواضحة ، فسينتهي بك الأمر في اتجاه واحد من اتجاهين.

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

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


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

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

إذن ، حيثما توجد قيود يسهل التقاطها في نظام الكتابة ، فلماذا لا نعطي المبرمجين القدرة على كتابتها؟ لدينا 40 عامًا من الخبرة في البرمجة باستخدام معلمات النوع المقيدة بشكل ثابت. هذا تعليق توضيحي بسيط وبديهي وثابت يؤتي ثماره.

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

ألاحظ أن بعض الأمثلة الخاصة بك للقيود الضمنية الأخرى تتضمن معالجة الأخطاء. أعتقد أن فحصنا الثابت الخفيف للاستثناءات [ PLDI 2016 ] سوف يعالج هذه الأمثلة.

تضمين التغريدة

إذن ، حيثما توجد قيود يسهل التقاطها في نظام الكتابة ، فلماذا لا نعطي المبرمجين القدرة على كتابتها؟
[...]
بمجرد أن تبدأ في بناء برنامج أكبر يطابق وحدات البرامج ، فإنك تبدأ في الرغبة في كتابة تعليقات تشرح هذه القيود الضمنية على أي حال. بافتراض وجود طريقة جيدة وقابلة للتحقق للتعبير عنها ، فلماذا لا تدع المترجم يدخل في النكتة حتى يتمكن من مساعدتك؟

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

لكنني أود أن آخذ هذه الحجة خطوة أخرى إلى الأمام: العكس صحيح أيضًا! إذا لم تكن بحاجة إلى كتابة تعليق لقيد (لأنه واضح في السياق للبشر الذين يعملون مع الكود) ، فلماذا تحتاج إلى كتابة هذا التعليق للمترجم؟ بغض النظر عن تفضيلاتي الشخصية ، فإن استخدام Go لجمع القمامة والقيم الصفرية يشير بوضوح إلى التحيز تجاه "عدم مطالبة المبرمجين بذكر ثوابت واضحة". قد يكون الأمر كذلك أن النمذجة على غرار الجنس يمكن أن تعبر عن العديد من القيود التي سيتم التعبير عنها في التعليقات ، ولكن كيف تسير الأمور فيما يتعلق بإلغاء القيود التي سيتم استبعادها أيضًا في التعليقات؟

يبدو لي أن النماذج ذات النمط الجنساني هي أكثر من مجرد تعليقات على أي حال: فهم في الواقع يغيرون دلالات الشفرة في بعض الحالات ، ولا يقيدونها فقط. الآن سيكون لدينا آليتان مختلفتان - واجهات ونماذج النوع - لتحديد معايير السلوك. قد يمثل ذلك تحولًا كبيرًا في لغة Go: لقد اكتشفنا بعض أفضل الممارسات للواجهات بمرور الوقت (مثل "تحديد الواجهات من جانب المستهلك") وليس من الواضح أن هذه التجربة ستترجم إلى نظام مختلف تمامًا ، حتى إهمال التوافق مع Go 1.

علاوة على ذلك ، فإن إحدى الخصائص الممتازة لـ Go هي أنه يمكن قراءة مواصفاتها (وفهمها إلى حد كبير) في فترة ما بعد الظهر. ليس من الواضح بالنسبة لي أنه يمكن إضافة نظام قيود على غرار الجنس إلى لغة Go دون تعقيدها بشكل كبير - سأكون فضوليًا لرؤية اقتراح ملموس لإجراء تغييرات على المواصفات.

هذه نقطة بيانات مثيرة للاهتمام لـ "metaprogramming". سيكون من الجيد بالنسبة لأنواع معينة في حزم sync و atomic - أي atomic.Value و sync.Map - لدعم طرق CompareAndSwap ، لكن هؤلاء يعملون فقط للأنواع التي يمكن مقارنتها. تظل باقي واجهات برمجة التطبيقات API atomic.Value و sync.Map مفيدة بدون هذه الطرق ، لذلك بالنسبة لحالة الاستخدام هذه ، نحتاج إما إلى شيء مثل SFINAE (أو أنواع أخرى من واجهات برمجة التطبيقات المحددة شرطيًا) أو يجب أن نسقط العودة إلى تسلسل هرمي أكثر تعقيدًا للأنواع.

أريد أن أتخلى عن هذه الفكرة النحوية الإبداعية المتمثلة في استخدام المقطوعات اللغوية الأصلية.

bcmills هل يمكنك شرح المزيد عن هذه النقاط الثلاث؟

  1. الغموض بين أسماء الأنواع وأسماء القيم.
  2. دعم واسع للغاية لأحمال المشغل الزائدة
    3. الإفراط في الاعتماد على دقة التحميل الزائد للبرمجة

تضمين التغريدة

  1. الغموض بين أسماء الأنواع وأسماء القيم.

هذا المقال يعطي مقدمة جيدة. لتحليل برنامج C ++ ، يجب أن تعرف أي الأسماء هي أنواع وأيها قيم. عندما تقوم بتحليل برنامج C ++ مقولب ، لا تتوفر لديك هذه المعلومات لأعضاء معلمات القالب.

تظهر مشكلة مماثلة في Go for composite literals ، لكن الغموض يكون بين القيم وأسماء الحقول بدلاً من القيم والأنواع. في كود Go هذا:

const a = someValue
x := T{a: b}

هل a هو اسم حقل حرفي ، أم أنه يتم استخدام a الثابت كمفتاح خريطة أو فهرس مصفوفة؟

  1. دعم واسع للغاية لأحمال المشغل الزائدة

يعد البحث المعتمد على الوسيطة مكانًا جيدًا للبدء. يمكن أن تحدث الأحمال الزائدة للمشغلين في C ++ كطرق على نوع المستقبِل أو كوظائف مجانية في أي من مساحات الأسماء العديدة ، كما أن قواعد حل هذه الأحمال الزائدة معقدة للغاية.

هناك العديد من الطرق لتجنب هذا التعقيد ، ولكن أبسطها (كما يفعل Go حاليًا) هو عدم السماح للمشغل بالحمل الزائد تمامًا.

  1. الإفراط في الاعتماد على دقة التحميل الزائد للبرمجة الوصفية

مكتبة <type_traits> مكان جيد للبدء. تحقق من التنفيذ في منطقتك الودودة libc++ لترى كيف تلعب دقة التحميل الزائد.

إذا كان Go يدعم البرمجة الوصفية (وحتى هذا مشكوك فيه جدًا) ، فلن أتوقع أن يتضمن حل الحمل الزائد كعملية أساسية لحماية التعريفات الشرطية.

تضمين التغريدة
نظرًا لأنني لم أستخدم C ++ مطلقًا ، هل يمكنك إلقاء بعض الضوء على موضع التحميل الزائد للمشغل من خلال تنفيذ "واجهات" محددة مسبقًا من حيث التعقيد. Python و Kotlin أمثلة على ذلك.

أعتقد أن ADL نفسه يمثل مشكلة كبيرة في قوالب C ++ التي لم يتم ذكرها في الغالب ، لأنها تجبر المترجم على تأخير حل جميع الأسماء حتى وقت إنشاء مثيل ، ويمكن أن يؤدي إلى أخطاء دقيقة للغاية ، ويرجع ذلك جزئيًا إلى "المثالي" و " المترجمون الكسالى "يتصرفون هنا بشكل مختلف والمعيار يسمح بذلك. حقيقة أنه يدعم التحميل الزائد للمشغل ليس في الحقيقة أسوأ جزء منه على الإطلاق.

يعتمد هذا الاقتراح على القوالب ، فلن يكون نظام التوسع الكلي كافيًا؟ أنا لا أتحدث عن go generate أو مشاريع مثل gotemplate. أنا أتحدث عن المزيد مثل هذا:

macro MacroFoo(stmt ast.Statement) {
    ....
}

يمكن أن يقلل الماكرو من النموذج المعياري واستخدام الانعكاس.

أعتقد أن C ++ مثال جيد بما فيه الكفاية على أن الأدوية الجنسية لا ينبغي أن تستند إلى القوالب أو وحدات الماكرو. خاصة إذا أخذنا في الاعتبار أن Go يحتوي على أشياء مثل الوظائف المجهولة التي لا يمكن "إنشاء مثيل لها" في وقت الترجمة باستثناء التحسين.

samadadi ، يمكنك إيصال وجهة نظرك دون أن تقول "ما خطبكم أيها الناس". بعد قولي هذا ، تم طرح حجة التعقيد عدة مرات بالفعل.

ليست Go هي اللغة الأولى التي تحاول تحقيق البساطة من خلال حذف دعم تعدد الأشكال البارامترية (الأدوية العامة) ، على الرغم من أن هذه الميزة أصبحت ذات أهمية متزايدة على مدار الأربعين عامًا الماضية - من خلال تجربتي ، فهي عنصر أساسي في دورات البرمجة في الفصل الثاني.

تكمن مشكلة عدم وجود هذه الميزة في اللغة في أن المبرمجين ينتهي بهم الأمر إلى اللجوء إلى الحلول البديلة الأسوأ. على سبيل المثال ، غالبًا ما يكتب مبرمجو Go قوالب التعليمات البرمجية التي يتم توسيعها بشكل كبير لإنتاج الكود "الحقيقي" للأنواع المختلفة المرغوبة. لكن لغة البرمجة الحقيقية هي التي تكتبها ، وليست اللغة التي يراها المترجم. لذلك تعني هذه الإستراتيجية بشكل فعال أنك تستخدم لغة (لم تعد قياسية) بها كل هشاشة ونفخ التعليمات البرمجية لقوالب C ++.

كما هو مذكور في https://blog.golang.org/toward-go2 ، نحتاج إلى تقديم "تقارير الخبرة" ، بحيث يمكن تحديد أهداف الحاجة والتصميم. هل يمكنك أن تستغرق بضع دقائق وتوثق حالات الماكرو التي لاحظتها؟

يرجى إبقاء هذا الخطأ في الموضوع والمدني. ومرة أخرى ، https://golang.org/wiki/NoMeToo. الرجاء التعليق فقط إذا كان لديك معلومات فريدة وبناءة لإضافتها.

mandolyte من السهل جدًا العثور على تفسيرات مفصلة على الويب تدعو إلى إنشاء كود كبديل (جزئي) للأدوية:
https://appliedgo.net/generics/
https://www.calhoun.io/using-code-generation-to-survive-without-generics-in-go/
http://blog.ralch.com/tutorial/golang-code-generation-and-generics/

من الواضح أن هناك الكثير من الأشخاص يتبعون هذا النهج.

andrewcmyers ، هناك بعض القيود بالإضافة إلى محاذير ملائمة عند استخدام إنشاء الكود ولكن.
بشكل عام - إذا كنت تعتقد أن هذا الأسلوب هو الأفضل / الجيد بما فيه الكفاية ، أعتقد أن الجهد المبذول للسماح بإنشاء جيل مشابه إلى حد ما من داخل سلسلة أدوات go سيكون نعمة.

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

الجانب السلبي - لا توجد سابقة (أعرفها) لهذا النهج داخل سلسلة أدوات go.

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

IMHO: إنه حل وسط يمكن تحقيقه بسهولة وبسعر منخفض.

لكي أكون واضحًا ، لا أعتبر إنشاء رمز على غرار الماكرو ، سواء تم ذلك باستخدام أدوات gen أو cpp أو gofmt -r أو أدوات ماكرو / قالب أخرى ، ليكون حلاً جيدًا لمشكلة الأدوية العامة حتى لو تم توحيده. لديها نفس مشاكل قوالب C ++: كثرة التعليمات البرمجية ، ونقص التحقق من النوع المعياري ، وصعوبة تصحيح الأخطاء. يزداد الأمر سوءًا عندما تبدأ ، كما هو طبيعي ، في بناء كود عام من حيث الكود العام الآخر. في رأيي ، المزايا محدودة: ستبقي الحياة بسيطة نسبيًا لكتاب مترجم Go كما أنها تنتج كودًا فعالًا - ما لم يكن هناك ضغط ذاكرة التخزين المؤقت للتعليمات ، وهو موقف متكرر في البرامج الحديثة!

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

في الأربعاء ، 26 يوليو 2017 ، الساعة 22:41 ، كتب أندرو مايرز ، [email protected] :

لأكون واضحًا ، لا أعتبر إنشاء كود بأسلوب الماكرو ، سواء تم ذلك
باستخدام أدوات gen أو cpp أو gofmt -r أو غيرها من أدوات الماكرو / القالب ، لتكون منتجًا جيدًا
حل مشكلة الأدوية الجنسية حتى لو تم توحيدها. لها نفس الشيء
مشاكل مثل قوالب C ++: سخام التعليمات البرمجية ، ونقص التحقق من النوع المعياري ، و
صعوبة التصحيح. يزداد الأمر سوءًا عندما تبدأ ، كما هو طبيعي ، في البناء
رمز عام من حيث الكود العام الآخر. في رأيي ، المزايا
محدود: سيبقي الحياة بسيطة نسبيًا لكتاب مترجم Go
وينتج رمزًا فعالاً - ما لم يكن هناك ذاكرة تخزين مؤقت للتعليمات
الضغط ، وهو الوضع المتكرر في البرامج الحديثة!

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-318242016 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AT4HVb2SPMpe5dlEDUQeadIRKPaB74zoks5sR_jSgaJpZM4IG-xv
.

لا شك أن إنشاء الكود ليس حلاً حقيقيًا حتى لو تم اختتامه ببعض الدعم اللغوي لجعل الشكل والمظهر "جزءًا من اللغة"

كانت وجهة نظري أنها كانت فعالة للغاية من حيث التكلفة.

راجع للشغل ، إذا نظرت إلى بعض بدائل إنشاء الكود ، يمكنك بسهولة أن ترى كيف كان من الممكن أن تكون أكثر قابلية للقراءة وأسرع وتفتقر إلى بعض المفاهيم الخاطئة (مثل التكرار على مصفوفات من المؤشرات مقابل القيم) إذا كانت اللغة تمنحهم أدوات أفضل من أجل هذا.

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

بعد قراءة مقترحات الأدوية الجنيسة من قبل bcmills و ianlancetaylor ، لقد أبديت الملاحظات التالية:

وظائف وقت الترجمة وأنواع الدرجة الأولى

تعجبني فكرة تقييم وقت الترجمة ، لكني لا أرى فائدة قصرها على الوظائف البحتة. يقدم هذا الاقتراح gotype المدمج ، لكنه يحد من استخدامه للوظائف الثابتة وأي أنواع بيانات محددة ضمن نطاق الوظيفة. من منظور مستخدم المكتبة ، يقتصر إنشاء مثيل على وظائف المُنشئ مثل "جديد" ، ويؤدي إلى توقيعات دالة مثل هذا:

const func New(K, V gotype, hashfn Hashfn(K), eqfn Eqfn(K)) func()*Hashmap(K, V, hashfn, eqfn)

لا يمكن فصل نوع الإرجاع هنا إلى نوع دالة لأننا مقيدون بالوظائف البحتة. بالإضافة إلى ذلك ، يحدد التوقيع "نوعين" جديدين في التوقيع نفسه (K و V) ، مما يعني أنه من أجل تحليل معلمة واحدة ، يجب علينا تحليل قائمة المعلمات بأكملها. هذا جيد للمترجم ، لكنني أتساءل عما إذا كان يضيف تعقيدًا إلى واجهة برمجة التطبيقات العامة للحزمة.

اكتب المعلمات في Go

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

ضمن قسم "مدقق النوع" ، يسرد الاقتراح أيضًا بعض قيود النوع المفيدة لتسريع العملية ، مثل "قابل للفهرسة" ، "قابل للمقارنة" ، "قابل للاستدعاء" ، "مركب" ، إلخ ... ما لا أفهمه هو لماذا لا تسمح للمستخدم بتحديد قيود النوع الخاصة به؟ الاقتراح ينص على أن

لا توجد قيود على كيفية استخدام الأنواع ذات المعلمات في دالة ذات معلمات.

ومع ذلك ، إذا كانت المعرفات بها قيود أكثر مرتبطة بها ، ألن يكون لذلك تأثير مساعدة المترجم؟ يعتبر:

HashMap[Anything,Anything] // Compiler must always compare the implementation and usages to make sure this is valid.

ضد

HashMap[Comparable,Anything] // Compiler can first filter out instantiations for incomparable types before running an exhaustive check.

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

MustafaHosny اللهم امين

تعجبني فكرة تقييم وقت الترجمة ، لكني لا أرى فائدة قصرها على الوظائف البحتة.

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

MustafaHosny اللهم امين

ما لا أفهمه هو لماذا لا تسمح للمستخدم بتحديد قيود النوع الخاصة به؟

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

إذا كانت المعرفات بها قيود أكثر مرتبطة بها ، ألن يكون لذلك تأثير مساعدة المترجم؟

يجب أن تكون اللغة مصممة لمستخدميها ، وليس للكتاب المترجمين.

ليست هناك حاجة لتحديد بناء جملة إضافي لأنواع القيود لأنه يمكن استنتاج جميع القيود النحوية من الاستخدام.

هذا هو الطريق C ++ نزلت. يتطلب تحليل برنامج عالمي لتحديد الاستخدامات ذات الصلة. لا يمكن للمبرمجين تفسير التعليمات البرمجية بطريقة معيارية ، ورسائل الخطأ مطولة وغير مفهومة.

يمكن أن يكون من السهل جدًا وخفيف الوزن تحديد العمليات المطلوبة. انظر CLU (1977) على سبيل المثال.

تضمين التغريدة

يتطلب تحليل برنامج عالمي لتحديد الاستخدامات ذات الصلة. لا يمكن للمبرمجين تفسير التعليمات البرمجية بطريقة معيارية ،

يستخدم هذا تعريفًا خاصًا لكلمة "معيارية" ، والذي لا أعتقد أنه عالمي كما يبدو أنك تفترض. بموجب اقتراح 2013 ، سيكون لكل وظيفة أو نوع مجموعة لا لبس فيها من القيود المستنبطة من أسفل إلى أعلى من الحزم المستوردة ، بنفس الطريقة تمامًا التي اشتُق بها وقت التشغيل (وقيود وقت التشغيل) للوظائف غير المعلمية من أسفل حتى من سلاسل المكالمات اليوم.

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

ورسائل الخطأ مطولة وغير مفهومة.

لدينا مثالان (GCC و MSVC) يوضحان أن رسائل الخطأ التي تم إنشاؤها بسذاجة غير مفهومة. أعتقد أنه من المبالغة افتراض أن رسائل الخطأ للقيود الضمنية سيئة في جوهرها .

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

أنا شخصيًا لا أشعر أن القيود الصريحة لا تتماشى مع نهج Go الحالي ، نظرًا لأن الواجهات هي قيود صريحة لنوع وقت التشغيل ، على الرغم من أن لها تعبيرًا محدودًا.

لدينا مثالان (GCC و MSVC) يوضحان أن رسائل الخطأ التي تم إنشاؤها بسذاجة غير مفهومة. أعتقد أنه من المبالغة افتراض أن رسائل الخطأ للقيود الضمنية سيئة في جوهرها.

قائمة المجمعين التي ينتج عنها الاستدلال بالنوع غير المحلي - وهو ما تقترحه - رسائل خطأ سيئة أطول قليلاً من ذلك. يتضمن SML و OCaml و GHC ، حيث تم بذل الكثير من الجهد بالفعل لتحسين رسائل الخطأ وحيث يوجد على الأقل بعض هيكل الوحدة الصريح الذي يساعد. قد تكون قادرًا على القيام بعمل أفضل ، وإذا توصلت إلى خوارزمية لرسائل خطأ جيدة مع المخطط الذي تقترحه ، فسيكون لديك منشور جيد. كنقطة انطلاق نحو تلك الخوارزمية ، قد تجد أوراق POPL 2014 و PLDI 2015 الخاصة بنا حول توطين الأخطاء مفيدة. إنهم أكثر أو أقل من أحدث ما توصلت إليه التكنولوجيا.

لأنه يمكن الاستدلال على جميع القيود النحوية من الاستخدام.

ألا يحد ذلك من اتساع البرامج العامة القابلة للتحقق من النوع؟ على سبيل المثال ، لاحظ أن اقتراح معلمات النوع لا يحدد قيد "قابل للتكرار". في اللغة الحالية ، قد يتوافق هذا إما مع شريحة أو قناة ، لكن النوع المركب (على سبيل المثال قائمة مرتبطة) لن يلبي بالضرورة هذه المتطلبات. تحديد واجهة مثل

type Iterable[T] interface {
    Next() T
}

يساعد في حالة القائمة المرتبطة ، ولكن الآن يجب أن يتم تمديد الشريحة المضمنة وأنواع القنوات لتلبية هذه الواجهة.

يبدو القيد الذي يقول "أنا أقبل مجموعة جميع الأنواع التي تكون إما متكررة أو شرائح أو قنوات" موقفًا مربحًا للجانبين للمستخدم ، ومؤلف الحزمة ، ومنفذ المترجم. النقطة التي أحاول توضيحها هي أن القيود هي مجموعة شاملة من البرامج الصالحة نحويًا ، وبعضها قد لا يكون منطقيًا من منظور اللغة ، ولكن فقط من منظور API.

يجب أن تكون اللغة مصممة لمستخدميها ، وليس للكتاب المترجمين.

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

ماذا لو بدلاً من

type Iterable[T] interface {
    Next() T
}

فصلنا فكرة "الواجهات" عن "القيود". ثم قد يكون لدينا

type T generic

type Iterable class {
    Next() T
}

حيث تعني "class" فئة نوع بنمط Haskell ، وليس فئة بنمط Java.

قد يساعد وجود "فئات طباعة" منفصلة عن "واجهات" في توضيح بعض عدم التعامد في الفكرتين. ثم Sortable (تجاهل sort.Interface) قد يبدو كما يلي:

type T generic

type Comparable class {
    Less(a, b T) bool
}

type Sortable class {
    Next() Comparable
}

فيما يلي بعض التعليقات على قسم "فئات ومفاهيم النوع" في Genus بواسطة andrewcmyers وإمكانية تطبيقه على Go.

يتناول هذا القسم قيود فئات ومفاهيم النوع ، موضحًا

أولاً ، يجب أن يُشاهد الرضا عن القيود بشكل فريد

لست متأكدًا من فهمي لهذا القيد. ألن يؤدي ربط قيد ما بفصل المعرفات إلى منعه من أن يكون فريدًا بالنسبة لنوع معين؟ يبدو لي أن جملة "where" في Genus تبني أساسًا نوعًا / قيدًا من قيد معين ، ولكن هذا يبدو مشابهًا لإنشاء مثيل متغير من نوع معين. القيد بهذه الطريقة يشبه النوع .

إليك تبسيط مثير لتعريفات القيد ، تم تكييفها مع Go:

kind Any interface{} // accepts any type that satisfies interface{}.
type T Any // Declare a type of Any kind. Also binds it to an identifier.
kind Eq T == T // accepts any type for which equality is defined.

لذلك سيظهر إعلان الخريطة على النحو التالي:

type Map[K Eq, V Any] struct {
}

حيث يمكن أن يبدو في Genus مثل:

type Map[K, V] where Eq[K], Any[V] struct {
}

وفي اقتراح Type-Params الحالي سيبدو كما يلي:

type Map[K,V] struct {
}

أعتقد أننا يمكن أن نتفق جميعًا على أن السماح للقيود بالاستفادة من نظام الكتابة الحالي يمكن أن يزيل التداخل بين ميزات اللغة ، ويجعل من السهل فهم ميزات جديدة.

وثانيًا ، تحدد نماذجهم كيفية تكييف نوع واحد ، بينما في لغة ذات تصنيف فرعي ، يمثل كل نوع مُكيَّف بشكل عام جميع أنواعه الفرعية.

يبدو هذا القيد أقل صلة بـ Go نظرًا لأن اللغة لديها بالفعل قواعد تحويل جيدة بين الأنواع المسماة / غير المسماة والواجهات المتداخلة.

تقترح الأمثلة المقدمة نماذج كحل ، والتي تبدو ميزة مفيدة ولكنها ليست ضرورية لـ Go. إذا كانت المكتبة تتوقع نوعًا ما لتنفيذ http.Handler على سبيل المثال ، ويريد المستخدم سلوكيات مختلفة اعتمادًا على السياق ، فإن محولات الكتابة تكون بسيطة:

type handleFunc func(http.ResponseWriter, *http.Request)
func (f handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { f(w,r) }

في الواقع ، هذا ما تفعله المكتبة القياسية .

MustafaHosny اللهم امين

أولاً ، يجب أن يُشاهد الرضا عن القيود بشكل فريد
لست متأكدًا من فهمي لهذا القيد. ألن يمنع ربط قيد بفصل المعرفات من أن يكون فريدًا لنوع معين؟

الفكرة هي أنه في Genus يمكنك تلبية نفس القيد بنفس النوع بأكثر من طريقة ، على عكس Haskell. على سبيل المثال ، إذا كان لديك HashSet[T] ، يمكنك كتابة HashSet[String] لسلاسل التجزئة بالطريقة المعتادة ولكن HashSet[String with CaseInsens] لتجزئة ومقارنة السلاسل مع CaseInsens النموذج ، الذي يُفترض أنه يتعامل مع السلاسل بطريقة غير حساسة لحالة الأحرف. يميز الجنس بالفعل هذين النوعين ؛ قد يكون هذا مبالغة بالنسبة لجو. حتى إذا لم يتتبع نظام الكتابة ذلك ، فلا يزال من المهم أن تكون قادرًا على تجاوز العمليات الافتراضية التي يوفرها النوع.

النوع أي واجهة {} // يقبل أي نوع يتوافق مع الواجهة {}.
اكتب T أي // أعلن نوعًا من أي نوع. يربطها أيضًا بمعرف.
النوع Eq T == T // يقبل أي نوع يتم تعريف المساواة له.
اكتب خريطة [K Eq، V أي] هيكل {...
}

سيكون المعادل الأخلاقي لهذا في جنس:

constraint Any[T] {}
// Just use Any as if it were a type
constraint Eq[K] {
   boolean equals(K);
}
class Map[K, V] where Eq[K] { ... }

في فاميليا نكتب فقط:

interface Eq {
    boolean equals(This);
}
class Map[K where Eq, V] { ... }

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

هذا ليس اقتراحًا ولكنه حالة استخدام محتملة يجب مراعاتها عند تصميم الاقتراح.

هناك شيئان شائعان في Go Code اليوم

  • التفاف قيمة واجهة لتوفير وظائف إضافية (التفاف http.ResponseWriter لإطار عمل)
  • وجود طرق اختيارية تحتوي أحيانًا على قيم الواجهة (مثل Temporary() bool على net.Error )

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

type MyError struct {
  error
  extraContext extraContextType
}
func (m MyError) Error() string {
  return fmt.Sprintf("%s: %s", m.extraContext, m.error)
}

إذا قمت بلف خطأ في هذا الهيكل فإنك تخفي أي طرق إضافية على الخطأ الأصلي.

إذا لم تقم بتغليف الخطأ في البنية ، فلا يمكنك توفير سياق إضافي.

لنفترض أن الاقتراح العام المقبول يتيح لك تحديد شيء مثل التالي (بناء الجملة التعسفي الذي حاولت جعله قبيحًا عمدًا حتى لا يركز عليه أحد)

type MyError generic_over[E which_is_a_type_satisfying error] struct {
  E
  extraContext extraContextType
}
func (m MyError) Error() string {
  return fmt.Sprintf("%s: %s", m.extraContext, m.E)
}

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

ما نحتاجه هنا حقًا هو اتخاذ قيمة عشوائية لواجهة الخطأ وتضمين نوعها الديناميكي.

هذا يثير قلقين على الفور

  • يجب إنشاء النوع في وقت التشغيل (من المحتمل أن يكون مطلوبًا بواسطة الانعكاس على أي حال)
  • يجب أن يصاب إنشاء النوع بالذعر إذا كانت قيمة الخطأ لا شيء

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

بدون ذلك ، تقوم فقط بإنشاء MyError على نوع الخطأ نفسه وليس النوع الديناميكي للواجهة.

لنفترض أن لدينا وظيفة سحرية unbox لسحبها و (بطريقة ما) نطبق المعلومات:

func wrap(ec extraContext, err error) error {
  if err == nil {
    return nil
  }
  return MyError{
    E: unbox(err),
    extraContext: ec,
  }
}

لنفترض الآن أن لدينا خطأ غير صفري ، err ، نوعه الديناميكي هو *net.DNSError . ثم هذا

wrapped := wrap(getExtraContext(), err)
//wrapped 's dynamic type is a MyStruct embedding E=*net.DNSError
_, ok := wrapped.(net.Error)
fmt.Println(ok)

ستطبع true . ولكن إذا كان النوع الديناميكي err هو *os.PathError لكان قد طبع false.

آمل أن يكون المعنى المقترح واضحًا نظرًا للصيغة المنفصلة المستخدمة في العرض التوضيحي.

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

jimmyfrasche إذا كنت أفهم ما تريده ، فهي آلية تكيف خالية من الغلاف. تريد أن تكون قادرًا على توسيع مجموعة العمليات التي يقدمها نوع ما دون التفافه في كائن آخر يخفي الأصل. هذه هي الوظيفة التي تقدمها Genus.

تضمين التغريدة

يسمح Struct's in Go بالتضمين. إذا أضفت حقلاً بدون اسم ولكن بنوع إلى بنية ، فإنه يقوم بأمرين: يقوم بإنشاء حقل يحمل نفس اسم النوع ويسمح بالإرسال الشفاف لأي طرق من هذا النوع. هذا يبدو بفظاعة مثل الميراث لكنه ليس كذلك. إذا كان لديك نوع T يحتوي على طريقة Foo () ، فإن ما يلي يكون مكافئًا

type S struct {
  T
}

و

type S struct {
  T T
}
func (s S) Foo() {
  s.T.Foo()
}

(عندما يطلق على Foo اسم "هذا" يكون دائمًا من النوع T).

يمكنك أيضًا تضمين واجهات في البنيات. يمنح هذا الهيكل جميع الطرق الموجودة في عقد الواجهة (على الرغم من أنك تحتاج إلى تعيين بعض القيمة الديناميكية للحقل الضمني أو سيؤدي إلى حالة من الذعر مع ما يعادل استثناء مؤشر فارغ)

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

func isTemp(err error) bool {
  if t, ok := err.(interface{ Temporary() bool}); ok {
    return t.Temporary()
  }
  return false
}

من الشائع أيضًا التفاف نوع في نوع آخر لتوفير ميزات إضافية. هذا يعمل بشكل جيد مع الأنواع التي ليست واجهة. عندما تقوم بلف واجهة على الرغم من أنك تخفي أيضًا الطرق التي لا تعرفها عنها ولا يمكنك استعادتها بتأكيدات نوع "اختبار الميزة": لا يكشف النوع المغلف إلا الطرق المطلوبة للواجهة حتى إذا كانت تحتوي على طرق اختيارية . يعتبر:

type A struct {}
func (A) Foo()
func (A) Bar()

type I interface {
  Foo()
}

type B struct {
  I
}

var i I = B{A{}}

لا يمكنك استدعاء Bar على i أو حتى تعلم أنه موجود ما لم تعلم أن النوع الديناميكي i هو B حتى تتمكن من فكه والوصول إلى الحقل I لكتابة تأكيد على ذلك .

هذا يسبب مشاكل حقيقية ، خاصة التعامل مع الواجهات الشائعة مثل الخطأ أو القارئ.

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

تضمين التغريدة ما يسمح لك Genus بفعله هو استخدام نوع واحد لتلبية عقد "واجهة" بدون تعبئته. لا تزال القيمة لها نوعها الأصلي وعملياتها الأصلية. علاوة على ذلك ، يمكن للبرنامج تحديد العمليات التي يجب أن يستخدمها النوع لتلبية العقد - بشكل افتراضي ، هي العمليات التي يوفرها النوع ، ولكن يمكن للبرنامج توفير عمليات جديدة إذا لم يكن لدى النوع العمليات اللازمة. يمكنه أيضًا استبدال العمليات التي قد يستخدمها النوع.

jimmyfrascheandrewcmyers لحالة الاستخدام هذه ، راجع أيضًا https://github.com/golang/go/issues/4146#issuecomment -318200547.

jimmyfrasche بالنسبة لي ، يبدو أن المشكلة الرئيسية هنا هي الحصول على النوع / القيمة الديناميكية للمتغير. وبغض النظر عن التضمين ، سيكون من الأمثلة المبسطة

type MyError generic_over[E which_is_a_type_satisfying error] struct {
  e E
  extraContext extraContextType
}
func (m MyError) Error() string {
  return fmt.Sprintf("%s: %s", m.extraContext, m.e)
}

القيمة التي تم تعيينها لـ e تحتاج إلى نوع ديناميكي (أو ملموس) من شيء مثل *net.DNSError ، والذي يستخدم error . فيما يلي طريقتان محتملتان يمكن أن يؤدي تغيير اللغة في المستقبل إلى معالجة هذه المشكلة:

  1. احصل على دالة سحرية تشبه unbox مثل الدالة التي تكشف عن القيمة الديناميكية للمتغير. هذا ينطبق على أي نوع غير ملموس ، على سبيل المثال النقابات.
  2. إذا كان تغيير اللغة يدعم متغيرات النوع ، فقدم وسيلة للحصول على النوع الديناميكي للمتغير. باستخدام معلومات النوع ، يمكننا كتابة الدالة unbox بأنفسنا. فمثلا،
func unbox(v T1) T2 {
    t := dynTypeOf(v)
    return v.(t)
}

يمكن كتابة wrap بنفس الطريقة كما في السابق ، أو كما في السابق

func wrap(ec extraContext, err error) error {
  if err == nil {
    return nil
  }
  t := dynTypeOf(err)
  return MyError{
    e: v.(t),
    extraContext: ec,
  }
}
  1. إذا كان تغيير اللغة يدعم قيود النوع ، فإليك فكرة بديلة:
type E1 which_is_a_type_satisfying error
type E2 which_is_a_type_satisfying error

func wrap(ec extraContext, err E1) E2 {
  if err == nil {
    return nil
  }
  return MyError{
    e: err,
    extraContext: ec,
  }
}

في هذا المثال ، نقبل قيمة من أي نوع تقوم بتنفيذ خطأ. أي مستخدم wrap ويتوقع error سيتلقى واحدًا. ومع ذلك ، فإن نوع e داخل MyError هو نفس نوع err الذي يتم تمريره ، والذي لا يقتصر على نوع الواجهة. إذا أراد المرء نفس السلوك مثل 2 ،

var iface error = ...
wrap(getExtraContext(), unbox(iface))

نظرًا لأنه لا يبدو أن أي شخص آخر قد فعل ذلك ، أود أن أشير إلى "تقارير التجربة" الواضحة جدًا للأدوية الجنيسة كما دعا إليها https://blog.golang.org/toward-go2.

الأول هو النوع المدمج map :

m := make(map[string]string)

التالي هو النوع المدمج chan :

c := make(chan bool)

أخيرًا ، المكتبة القياسية مليئة ببدائل interface{} حيث تعمل الأدوية الجنيسة بأمان أكبر:

  • heap.Interface (https://golang.org/pkg/container/heap/#Interface)
  • list.Element (https://golang.org/pkg/container/list/#Element)
  • ring.Ring (https://golang.org/pkg/container/ring/#Ring)
  • sync.Pool (https://golang.org/pkg/sync/#Pool)
  • القادمة sync.Map (https://tip.golang.org/pkg/sync/#Map)
  • atomic.Value (https://golang.org/pkg/sync/atomic/#Value)

قد يكون هناك آخرون أنا في عداد المفقودين. النقطة المهمة هي أن كل ما سبق هو المكان الذي أتوقع أن تكون الأدوية الجنيسة فيه مفيدة.

(ملاحظة: أنا لا أدرج هنا sort.Sort لأنه مثال ممتاز لكيفية استخدام الواجهات بدلاً من الأدوية العامة.)

http://www.yinwang.org/blog-cn/2014/04/18/golang
أعتقد أن العامل العام مهم ، وإلا لا يمكنه التعامل مع أنواع مماثلة. أحيانًا لا تستطيع الواجهة حل المشكلة.

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

bxqgit يرجى الاحتفاظ بهذه المدنية. لا داعي لإهانة أي شخص.

بالنسبة لما سيأتي به المستقبل ، سنرى ، لكنني أعلم أنه بينما في 98٪ من وقتي لا أحتاج إلى الأدوية الجنيسة ، كلما احتجت إليها ، أتمنى أن أتمكن من استخدامها. كيف يتم استخدامها مقابل كيف يتم استخدامها بشكل خاطئ هو مناقشة مختلفة. يجب أن يكون تثقيف المستخدمين جزءًا من العملية.

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

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

يعد نظام الواجهة الحالي قيد التشغيل مفيدًا للغاية ، ومع ذلك فهو سيء جدًا عندما تحتاج ، على سبيل المثال ، إلى تنفيذ عام للقائمة ، والتي تحتاج مع الواجهات إلى قيد نوع وقت التنفيذ ، ومع ذلك إذا أضفت أدوية عامة ، يمكن استبدال النوع العام بـ جمع الوقت بالنوع الفعلي ، مما يجعل القيد غير ضروري.

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

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

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

القالب معقد ولكن Generics بسيط

انظر إلى الوظائف SortInts و SortFloats و SortStrings في حزمة الفرز. أو SearchInts أو SearchFloats أو SearchStrings. أو التوابع Len و Less و Swap لـ byName في الحزمة io / ioutil. نقية النسخ المتداول.

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

تصويتي هو لا للأدوية العامة للتطبيقات ، نعم لمزيد من الوظائف العامة المضمنة مثل append و copy التي تعمل على أنواع أساسية متعددة. ربما يمكن إضافة sort و search لأنواع المجموعات؟

بالنسبة لتطبيقاتي ، النوع الوحيد المفقود هو مجموعة غير مرتبة (https://github.com/golang/go/issues/7088) ، أود هذا كنوع مضمن بحيث يحصل على الكتابة العامة مثل slice و map . ضع العمل في المترجم (قياس الأداء لكل نوع أساسي ومجموعة مختارة من أنواع struct ثم ضبطها للحصول على أفضل أداء) واحتفظ بالتعليقات التوضيحية الإضافية خارج كود التطبيق.

smap مدمج بدلاً من sync.Map من فضلك. من تجربتي استخدام interface{} لسلامة نوع وقت التشغيل هو عيب في التصميم. يعد فحص نوع وقت الترجمة سببًا رئيسيًا لاستخدام Go على الإطلاق.

تضمين التغريدة

من واقع خبرتي في استخدام الواجهة {} لأمان نوع وقت التشغيل هو عيب في التصميم.

هل يمكنك فقط كتابة غلاف صغير (نوع آمن)؟
https://play.golang.org/p/tG6hd-j5yx

pierrre هذا الغلاف أفضل من شيك reflect.TypeOf(item).AssignableTo(type) . لكن كتابة النوع الخاص بك مع map + sync.Mutex أو sync.RWMutex هو نفس التعقيد بدون تأكيد النوع الذي يتطلبه sync.Map .

تم استخدام خريطتي المتزامنة للخرائط العالمية لكائنات المزامنة مع var myMapLock = sync.RWMutex{} بجوارها بدلاً من إنشاء نوع. يمكن أن يكون هذا أنظف. يبدو النوع المدمج العام مناسبًا لي ولكنه يتطلب عملاً لا يمكنني القيام به ، وأنا أفضل أسلوبي بدلاً من تأكيد الكتابة.

أظن أن رد الفعل الحشوي السلبي للأدوية التي يبدو أن العديد من مبرمجي Go قد نشأ لأن تعرضهم الرئيسي للأدوية كان عبر قوالب C ++. هذا أمر مؤسف لأن C ++ حصلت على الأدوية الجنيسة بشكل خاطئ منذ اليوم الأول وتفاقمت الخطأ منذ ذلك الحين. يمكن أن تكون Generics for Go أبسط بكثير وأقل عرضة للخطأ.

سيكون من المخيب للآمال رؤية Go يصبح أكثر وأكثر تعقيدًا من خلال إضافة أنواع معلمات مضمنة. سيكون من الأفضل فقط إضافة دعم اللغة للمبرمجين لكتابة أنواع معلمات خاصة بهم. ثم يمكن تقديم الأنواع الخاصة كمكتبات بدلاً من تشويش اللغة الأساسية.

andrewcmyers "Generics for Go يمكن أن تكون أبسط بكثير وأقل عرضة للخطأ." - مثل الأدوية الجنيسة في C #.

إنه لأمر مخيب للآمال أن نرى Go يصبح أكثر وأكثر تعقيدًا من خلال إضافة أنواع معلمات مضمنة.

على الرغم من التكهنات في هذه القضية ، أعتقد أن هذا غير مرجح للغاية.

الأس على مقياس التعقيد للأنواع ذات المعلمات هو التباين.
أنواع Go (باستثناء الواجهات) ثابتة ويمكن ويجب أن يكون كذلك
أبقى على القاعدة.

تنفيذ ميكانيكي بمساعدة المترجم "نوع النسخ المقرب"
من شأنه أن يحل 99٪ من المشكلة بطريقة صحيحة مع جوهر Go
مبادئ السطحية وعدم المفاجأة.

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

في 28 تشرين الثاني (نوفمبر) 2017 ، الساعة 23:54 ، كتب "Andrew Myers" [email protected] :

أظن أن رد الفعل الحشوي السلبي للأدوية يذهب الكثير
يبدو أن المبرمجين قد نشأوا لأن تعرضهم الرئيسي للأدوية كان
عبر قوالب C ++. هذا أمر مؤسف لأن C ++ حصلت على الأدوية الجنيسة بشكل مأساوي
خاطئ من اليوم الأول وقد أدى إلى تفاقم الخطأ منذ ذلك الحين. Generics for
يمكن أن يكون Go أبسط بكثير وأقل عرضة للخطأ.

إنه لأمر مخيب للآمال أن نرى Go يصبح أكثر وأكثر تعقيدًا من خلال الإضافة
أنواع معلمات مضمنة. سيكون من الأفضل إضافة اللغة فقط
دعم المبرمجين لكتابة أنواع معلمات خاصة بهم. ثم
يمكن فقط توفير أنواع خاصة كمكتبات بدلاً من الازدحام
اللغة الأساسية.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-347691444 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AJZ_jPsQd2qBbn9NI1wZeT-O2JpyraTMks5s7I81gaJpZM4IG-xv
.

نعم ، يمكنك الحصول على أدوية عامة بدون قوالب. القوالب هي شكل من أشكال تعدد الأشكال البارامترية المتقدمة في الغالب لمرافق البرمجة الوصفية.

ianlancetaylor Rust يسمح للبرنامج بتنفيذ سمة T على نوع حالي Q ، بشرط أن يحدد الصندوق الخاص به إما T أو Q .

مجرد فكرة: أتساءل عما إذا كان Simon Peyton Jones (نعم ، من شهرة Haskell) و / أو مطوري Rust قد يكونون قادرين على المساعدة. من المحتمل أن يكون لدى Rust و Haskell أكثر أنظمة النوع تقدمًا من أي لغة إنتاج ، ويجب أن تتعلم Go منهما.

هناك أيضًا Phillip Wadler ، الذي عمل على Generic Java ، والذي أدى في النهاية إلى تطبيق Java العام للأدوية اليوم.

tarcieri لا أعتقد أن الأدوية الجنيسة لجافا جيدة جدًا ، لكنها تم اختبارها في المعركة.

DemiMarie لدينا أندرو مايرز هنا ، لحسن الحظ.

بناءً على تجربتي الشخصية ، أعتقد أن الأشخاص الذين يعرفون الكثير عن اللغات المختلفة وأنظمة الكتابة المختلفة يمكن أن يكونوا مفيدين جدًا في فحص الأفكار. ولكن لإنتاج الأفكار في المقام الأول ، ما نحتاجه هم أشخاص على دراية كبيرة بـ Go ، وكيف تعمل اليوم ، وكيف يمكن أن تعمل بشكل معقول في المستقبل. تم تصميم Go ليكون ، من بين أشياء أخرى ، لغة بسيطة. من غير المحتمل أن يكون استيراد الأفكار من لغات مثل Haskell أو Rust ، والتي هي أكثر تعقيدًا من Go ، مناسبًا بشكل جيد. وبشكل عام ، من غير المحتمل أن تكون الأفكار من الأشخاص الذين لم يكتبوا بالفعل قدرًا معقولاً من كود Go مناسبة ؛ لا يعني ذلك أن الأفكار ستكون سيئة على هذا النحو ، فقط لأنها لن تتناسب جيدًا مع بقية اللغة.

على سبيل المثال ، من المهم أن نفهم أن Go لديه بالفعل دعم جزئي للبرمجة العامة باستخدام أنواع الواجهة ولديه بالفعل (تقريبًا) دعم كامل باستخدام حزمة الانعكاس. في حين أن هذين النهجين للبرمجة العامة غير مرضيين لأسباب مختلفة ، فإن أي اقتراح للأدوية الجنيسة في Go يجب أن يتفاعل معهم بشكل جيد مع معالجة أوجه القصور في نفس الوقت.

في الواقع ، أثناء وجودي هنا ، فكرت منذ فترة في البرمجة العامة مع الواجهات لفترة من الوقت ، وخرجت بثلاثة أسباب تجعلها غير مرضية.

  1. تتطلب الواجهات التعبير عن جميع العمليات كطرق. هذا يجعل كتابة واجهة لأنواع مضمنة ، مثل أنواع القنوات ، أمرًا مؤلمًا. تدعم جميع أنواع القنوات عامل التشغيل <- لإرسال واستقبال العمليات ، ومن السهل كتابة واجهة باستخدام طرق Send و Receive ، ولكن من أجل تعيين قيمة قناة إلى نوع الواجهة هذا ، يجب عليك كتابة أساليب لوحة معيارية Send و Receive . ستبدو هذه الطرق المعيارية متشابهة تمامًا لكل نوع قناة مختلف ، وهو أمر ممل.

  2. يتم كتابة الواجهات ديناميكيًا ، وبالتالي يتم اكتشاف الأخطاء التي تجمع بين القيم المختلفة المكتوبة بشكل ثابت فقط في وقت التشغيل ، وليس وقت الترجمة. على سبيل المثال ، ستتطلب وظيفة Merge التي تدمج قناتين في قناة واحدة باستخدام طريقتين Send و Receive أن تحتوي القناتان على عناصر من نفس النوع ، لكن ذلك لا يمكن إجراء الفحص إلا في وقت التشغيل.

  3. دائما ما تكون الواجهات محاصر. على سبيل المثال ، لا توجد طريقة لاستخدام الواجهات لتجميع زوج من الأنواع الأخرى دون وضع تلك الأنواع الأخرى في قيم الواجهة ، مما يتطلب تخصيصات إضافية للذاكرة ومطاردة المؤشر.

يسعدني أن أحصل على kibitz على مقترحات الأدوية الجنيسة لـ Go. ربما يكون من المثير للاهتمام أيضًا زيادة كمية الأبحاث حول الأدوية الجنيسة في جامعة كورنيل مؤخرًا ، والتي يبدو أنها ذات صلة بما يمكن فعله باستخدام Go:

http://www.cs.cornell.edu/andru/papers/familia/ (Zhang & Myers، OOPSLA'17)
http://io.livecode.ch/learn/namin/unsound (أمين وتيت ، OOPSLA'16)
http://www.cs.cornell.edu/projects/genus/ (Zhang et al.، PLDI '15)
https://www.cs.cornell.edu/~ross/publications/shapes/shapes-pldi14.pdf (Greenman، Muehlboeck & Tate، PLDI '14)

في خريطة قياس الأداء مقابل شريحة لنوع مجموعة غير مرتب ، كتبت اختبارات وحدة منفصلة لكل منها ، ولكن مع أنواع الواجهة ، يمكنني دمج قائمتَي الاختبار هاتين في واحدة:

type Item interface {
    Equal(Item) bool
}

type Set interface {
    Add(Item) Set
    Remove(Item) Set
    Combine(...Set) Set
    Reduce() Set
    Has(Item) bool
    Equal(Set) bool
    Diff(Set) Set
}

اختبار إزالة عنصر:

type RemoveCase struct {
    Set
    Item
    Out Set
}

func TestRemove(t *testing.T) {
    for i, c := range RemoveCases {
        if c.Out.Equal(c.Set.Remove(c.Item)) == false {
            t.Fatalf("%v failed", i)
        }
    }
}

بهذه الطريقة يمكنني تجميع الحالات المنفصلة سابقًا معًا في شريحة واحدة من الحالات دون أي مشكلة:

var RemoveCases = []RemoveCase{
    {
        Set: MapPathSet{
            &Path{{0, 0}}:         {},
            &Path{{0, 1}, {1, 1}}: {},
        },
        Item: Path{{0, 0}},
        Out: MapPathSet{
            &Path{{0, 1}, {1, 1}}: {},
        },
    },
    {
        Set: SlicePathSet{
            {{0, 0}},
            {{0, 1}, {1, 1}},
        },
        Item: Path{{0, 0}},
        Out: SlicePathSet{
            {{0, 1}, {1, 1}},
        },
    },
}

لكل نوع ملموس كان علي تحديد طرق الواجهة. فمثلا:

func (the MapPathSet) Remove(an Item) Set {
    return MapDelete(the, an.(Path))
}
func (the SlicePathSet) Remove(an Item) Set {
    return SliceDelete(the, an.(Path))
}

يمكن أن تستخدم هذه الاختبارات العامة فحصًا لنوع وقت الترجمة المقترح:

type Item generic {
    Equal(Item) bool
}
func (the SlicePathSet) Remove(an Item) Set {
    return SliceDelete(the, an)
}

المصدر: https://github.com/pciet/pathsetbenchmark

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

إذن ماذا عن النوع "العام" الذي يمثل واجهة ويحتوي على تأكيد نوع غير مرئي أضافه المترجم عند استخدامه بشكل ملموس؟

andrewcmyers كانت ورقة "العائلة" مثيرة للاهتمام (وطريقة فوق رأسي). كانت الفكرة الرئيسية هي الميراث. كيف ستتغير المفاهيم للغة مثل Go التي تعتمد على التكوين بدلاً من الميراث؟

شكرا. لا ينطبق جزء الميراث على Go - إذا كنت مهتمًا فقط بالأدوية الجنيسة لـ Go ، فيمكنك التوقف عن القراءة بعد القسم 4 من الورقة. الشيء الرئيسي في تلك الورقة ذات الصلة بـ Go هو أنها توضح كيفية استخدام الواجهات بالطريقة التي تُستخدم بها لـ Go now وكقيود على أنواع التجريدات العامة. مما يعني أنك تحصل على قوة فئات نوع Haskell دون إضافة بنية جديدة تمامًا إلى اللغة.

andrewcmyers هل يمكنك إعطاء مثال على كيف سيبدو هذا في Go؟

الشيء الرئيسي في تلك الورقة ذات الصلة بـ Go هو أنها توضح كيفية استخدام الواجهات بالطريقة التي تُستخدم بها لـ Go now وكقيود على أنواع التجريدات العامة.

ما أفهمه هو أن واجهة Go تحدد قيدًا على نوع (على سبيل المثال "يمكن مقارنة هذا النوع من أجل المساواة باستخدام" واجهة مقارنة النوع "لأنها ترضي وجود طريقة Eq"). لست متأكدًا من أنني أفهم ما تقصده بقيد النوع.

لست على دراية بـ Haskell ، لكن قراءة نظرة عامة سريعة جعلتني أخمن أن الأنواع التي تناسب واجهة Go ستلائم فئة النوع هذا. هل يمكن أن تشرح ما هو المختلف في فئات نوع هاسكل؟

ستكون المقارنة الملموسة بين Familia و Go مثيرة للاهتمام. شكرا لتقاسم ورقتك.

يمكن النظر إلى واجهات Go على أنها تصف قيدًا على الأنواع ، عبر التصنيف الفرعي الهيكلي. ومع ذلك ، فإن هذا النوع من القيود ، كما هو ، ليس معبرًا بدرجة كافية لالتقاط القيود التي تريدها للبرمجة العامة. على سبيل المثال ، لا يمكنك التعبير عن قيد النوع المسمى Eq في ورقة العائلة.

بعض الأفكار حول الدافع وراء المزيد من مرافق البرمجة العامة في Go:

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

لكن sync.Map هي حالة استخدام مختلفة. هناك حاجة في المكتبة القياسية لتنفيذ خريطة متزامنة عامة ناضجة تتجاوز مجرد بنية بها خريطة وكائن. بالنسبة للتعامل مع النوع ، يمكننا التفافه بنوع آخر يعيّن نوعًا غير واجهة {} ويقوم بتأكيد النوع ، أو يمكننا إضافة فحص انعكاس داخليًا بحيث يجب أن تتطابق العناصر التي تلي الأول مع نفس النوع. كلاهما يحتوي على فحوصات وقت التشغيل ، ويتطلب الالتفاف إعادة كتابة كل طريقة لكل نوع استخدام ولكنه يضيف فحصًا لنوع وقت الترجمة للإدخال ويخفي تأكيد نوع الإخراج ، ومع الفحص الداخلي لا يزال يتعين علينا القيام بتأكيد نوع الإخراج على أي حال. في كلتا الحالتين ، نقوم بتحويلات الواجهة دون أي استخدام فعلي للواجهات ؛ الواجهة {} اختراق للغة ولن تكون واضحة لمبرمجي Go الجدد. على الرغم من أن json.Marshal هو تصميم جيد في رأيي (بما في ذلك علامات البنية القبيحة ولكن المعقولة).

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

تحتوي الخريطة العادية فقط على فحص لنوع وقت التجميع ولا تتطلب أيًا من هذه السقالات. أنا أزعم أن المزامنة يجب أن تكون الخريطة هي نفسها أو لا يجب أن تكون في المكتبة القياسية لـ Go 2.

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

إذا قمت بإضافة sync.Map كإعداد مضمّن ، فإلى أي مدى ستصل؟ هل لديك حالة خاصة لكل حاوية؟
المزامنة. الخريطة ليست الحاوية الوحيدة وبعضها أفضل لبعض الحالات من البعض الآخر.

Azareal : سجلchowey هذه في أغسطس:

أخيرًا ، المكتبة القياسية مليئة ببدائل الواجهة {} حيث تعمل الأدوية الجنيسة بأمان أكبر:

• heap.Interface (https://golang.org/pkg/container/heap/#Interface)
• list.Element (https://golang.org/pkg/container/list/#Element)
• ring.Ring (https://golang.org/pkg/container/ring/#Ring)
• sync.Pool (https://golang.org/pkg/sync/#Pool)
• خريطة sync.Map القادمة (https://tip.golang.org/pkg/sync/#Map)
• القيمة الذرية (https://golang.org/pkg/sync/atomic/#Value)

قد يكون هناك آخرون أنا في عداد المفقودين. النقطة المهمة هي أن كل ما سبق هو المكان الذي أتوقع أن تكون الأدوية الجنيسة فيه مفيدة.

وأود المجموعة غير المرتبة للأنواع التي يمكن مقارنتها من أجل المساواة.

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

أتساءل عما إذا كانت هناك تطبيقات بديلة معقولة مع Go 1 تحقق نفس الهدف لأنواع المكتبات القياسية هذه بدون واجهة {} وبدون تطبيقات عامة.

تتغلب واجهات golang وفئات نوع haskell على شيئين (عظيمين جدًا!):

1.) (نوع القيد) يقومون بتجميع أنواع مختلفة بعلامة واحدة ، اسم الواجهة
2.) (إرسال) يعرضون الإرسال بشكل مختلف عن كل نوع لمجموعة معينة من الوظائف عبر تنفيذ الواجهة

ولكن،

1.) في بعض الأحيان تريد فقط مجموعات مجهولة مثل مجموعة int و float64 و string. كيف يجب أن تسمي هذه الواجهة ، NumericandString؟

2.) في كثير من الأحيان ، لا ترغب في الإرسال بشكل مختلف لكل نوع من أنواع الواجهة ولكن لتقديم طريقة واحدة فقط لجميع أنواع الواجهة المدرجة (ربما يكون ذلك ممكنًا باستخدام الطرق الافتراضية للواجهات )

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

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

بالمناسبة.
أعترف لأولئك الذين يقولون أن go لديها بالفعل تعدد الأشكال وبالتالي فإن go لم تعد لغة بسيطة مثل C. إنها لغة برمجة نظام عالية المستوى. فلماذا لا يتم توسيع تعدد الأشكال بالعروض.

إليك مكتبة بدأتها اليوم لأنواع المجموعات العامة غير المرتبة: https://github.com/pciet/unordered

هذا يعطي أمثلة في التوثيق والاختبار التي تكتب نمط الغلاف (شكرًاpierrre) لسلامة نوع وقت التجميع ولديه أيضًا فحص الانعكاس لسلامة نوع وقت التشغيل.

ما هي احتياجات هناك للأدوية؟ تمركز موقفي السلبي تجاه الأنواع العامة للمكتبة القياسية في وقت سابق حول استخدام الواجهة {} ؛ يمكن حل شكواي باستخدام نوع خاص بالحزمة للواجهة {} (مثل type Item interface{} in pciet / unordered) الذي يوثق القيود غير القابلة للتعبير المقصودة.

لا أرى الحاجة إلى ميزة لغة إضافية في حين أن الوثائق فقط يمكن أن توصلنا إلى هناك الآن. توجد بالفعل كميات كبيرة من التعليمات البرمجية التي تم اختبارها في المعركة في المكتبة القياسية التي توفر تسهيلات عامة (راجع https://github.com/golang/go/issues/23077).

يتحقق نوع الكود الخاص بك في وقت التشغيل (ومن هذا المنظور فإنه ليس أفضل بأي حال من interface{} إن لم يكن أسوأ). باستخدام الأدوية الجنيسة ، كان من الممكن أن يكون لديك أنواع المجموعات مع فحوصات نوع وقت الترجمة.

يمكن إيقاف عمليات التحقق من وقت تشغيل zerkms عن طريق تعيين تأكيد = خطأ (لن يتم ذلك في المكتبة القياسية) ، وهناك نمط استخدام لفحوصات وقت الترجمة ، وعلى أي حال ، فإن فحص النوع ينظر فقط إلى بنية الواجهة (باستخدام تضيف الواجهة نفقات أكثر من التحقق من النوع). إذا كانت الواجهة لا تعمل ، فسيتعين عليك كتابة النوع الخاص بك.

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

يمكن إيقاف تشغيل عمليات التحقق في وقت التشغيل عن طريق تعيين التوكيد = خطأ

ثم لا شيء يضمن الصحة

أنت تقول إن الشفرة العامة ذات الأداء الأقصى هي حاجة أساسية.

لم أقل ذلك. نوع الأمان سيكون أمرًا عظيمًا. لا يزال الحل الخاص بك مصابًا بالعدوى interface{} .

ولكن ربما تصبح المكتبة القياسية أسرع ، وربما يحتاج الآخرون إلى مثل هذا الشيء.

قد يكون الأمر كذلك ، إذا كان فريق التطوير الأساسي سعيدًا بتنفيذ كل ما أحتاجه عند الطلب وبسرعة.

تضمين التغريدة

لا أرى الحاجة إلى ميزة لغة إضافية في حين أن الوثائق فقط يمكن أن توصلنا إلى هناك الآن.

أنت تقول هذا ، ولكن ليس لديك مشكلة في استخدام ميزات اللغة العامة في شكل شرائح ووظيفة الصنع.

لا أرى الحاجة إلى ميزة لغة إضافية في حين أن الوثائق فقط يمكن أن توصلنا إلى هناك الآن.

فلماذا عناء استخدام لغة مكتوبة بشكل ثابت؟ يمكنك استخدام لغة مكتوبة ديناميكيًا مثل Python والاعتماد على الوثائق للتأكد من إرسال أنواع البيانات الصحيحة إلى API الخاص بك.

أعتقد أن إحدى مزايا Go هي التسهيلات لفرض بعض القيود من قبل المترجم لمنع الأخطاء المستقبلية. يمكن تمديد هذه التسهيلات (مع دعم الأدوية الجنيسة) لفرض بعض القيود الأخرى لمنع المزيد من الأخطاء في المستقبل.

أنت تقول هذا ، ولكن ليس لديك مشكلة في استخدام ميزات اللغة العامة في شكل شرائح ووظيفة الصنع.

أقول إن الميزات الحالية توصلنا إلى نقطة متوازنة جيدة لديها حلول برمجة عامة ويجب أن تكون هناك أسباب حقيقية قوية للتغيير من نظام النوع Go 1. ليست الطريقة التي يمكن للتغيير من خلالها تحسين اللغة ولكن المشكلات التي يواجهها الأشخاص الآن ، مثل الحفاظ على الكثير من تبديل نوع وقت التشغيل للواجهة {} في حزم مكتبة fmt وقاعدة البيانات القياسية ، والتي سيتم إصلاحها.

فلماذا عناء استخدام لغة مكتوبة بشكل ثابت؟ يمكنك استخدام لغة مكتوبة ديناميكيًا مثل Python والاعتماد على الوثائق للتأكد من إرسال أنواع البيانات الصحيحة إلى API الخاص بك.

لقد سمعت اقتراحات لكتابة أنظمة بلغة Python بدلاً من اللغات والمؤسسات المكتوبة بشكل ثابت.

يستخدم معظم مبرمجي Go أنواع استخدام المكتبة القياسية التي لا يمكن وصفها بالكامل بدون توثيق أو دون النظر إلى التنفيذ. تعمل الأنواع ذات الأنواع الفرعية البارامترية أو الأنواع العامة ذات القيود المطبقة فقط على إصلاح مجموعة فرعية من هذه الحالات برمجيًا وستولد الكثير من العمل الذي تم إنجازه بالفعل في المكتبة القياسية.

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

يمكن للدالة / الطريقة التي تأخذ واجهة رفض بعض الأنواع عند الإنشاء من خلال عدم وجود حالة افتراضية ولا حالة للنوع. يبدو هذا كإضافة برمجة عامة معقولة إذا كانت الميزة قابلة للتنفيذ.

إذا تمكنت واجهات Go من التقاط نوع المنفذ ، فقد يكون هناك شكل من أشكال الأدوية العامة متوافق تمامًا مع بناء جملة Go الحالي - نموذج معلمة واحد من الأدوية العامة ( عرض توضيحي ).

@ dc0d لأنواع الحاويات العامة أعتقد أن الميزة تضيف فحصًا لنوع وقت الترجمة دون الحاجة إلى نوع غلاف: https://gist.github.com/pciet/36a9dcbe99f6fb71f5fc2d3c455971e5

pciet أنت على حق. في الكود المقدم ، رقم 4 ، يوضح النموذج أنه تم التقاط النوع للشرائح والقنوات (والمصفوفات). ولكن ليس للخرائط ، لأن هناك معلمة واحدة وواحدة فقط من النوع: المنفذ. ونظرًا لأن الخريطة تحتاج إلى معلمتين من النوع ، يلزم وجود واجهات غلاف.

راجع للشغل يجب أن أؤكد على الغرض التوضيحي لهذا الرمز ، كخط فكري. أنا لست مصمم لغة. هذه مجرد طريقة افتراضية للتفكير في تنفيذ الأدوية الجنيسة في Go:

  • متوافق مع Go الحالي
  • بسيطة (معلمة نوع عام واحدة ، والتي _ تشعر _ مثل _ هذا _ في OO الأخرى ، بالإشارة إلى المنفذ الحالي)

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

كتب adg :

يصاحب هذه المشكلة اقتراح عام للأدوية العامة من ianlancetaylor يتضمن أربعة مقترحات معيبة محددة لآليات البرمجة العامة لـ Go.

Afaics ، المقطع المرتبط المقتطف على النحو التالي فشل في تحديد حالة عامة تفتقر إلى الواجهات الحالية _ "لا توجد طريقة لكتابة طريقة تأخذ واجهة للمتصل الموفر من النوع T ، لأي T ، وإرجاع قيمة من نفس النوع T. "_.

لا توجد طريقة لكتابة واجهة باستخدام طريقة تأخذ وسيطة من النوع T لأي T وتعيد قيمة من نفس النوع.

إذن ، كيف يمكن للشفرة الموجودة في نوع موقع الاتصال التحقق من أنها تحتوي على النوع T كقيمة للنتيجة؟ على سبيل المثال ، قد تحتوي الواجهة المذكورة على طريقة مصنع لبناء النوع T. وهذا هو السبب في أننا نحتاج إلى تحديد واجهات على النوع T.

الواجهات ليست مجرد أنواع ؛ هم أيضا قيم. لا توجد طريقة لاستخدام أنواع الواجهة بدون استخدام قيم الواجهة ، وقيم الواجهة ليست فعالة دائمًا.

متفق عليه أنه نظرًا لأنه لا يمكن حاليًا تحديد الواجهات بشكل صريح على النوع T الذي تعمل عليه ، فإن النوع T لا يمكن للمبرمج الوصول إليه.

هذا ما تفعله حدود فئة الحروف في موقع تعريف الوظيفة مع الأخذ كإدخال من النوع T ووجود عبارة where أو requires توضح الواجهة (الواجهات) المطلوبة للنوع T. في العديد من الظروف يمكن تحويل قواميس الواجهة هذه تلقائيًا إلى شكل واحد في وقت الترجمة بحيث لا يتم تمرير مؤشر (مؤشرات) القاموس (للواجهات) إلى الوظيفة في وقت التشغيل (monomorphisation الذي أفترض أن مترجم Go ينطبق على الواجهات حاليًا؟). من خلال "القيم" في الاقتباس أعلاه ، أفترض أنه يعني نوع الإدخال T وليس قاموس الأساليب لنوع الواجهة المنفذة بواسطة النوع T.

إذا سمحنا بعد ذلك بمعلمات النوع في أنواع البيانات (على سبيل المثال struct ) ، فإن النوع المذكور أعلاه يمكن أن يكون هو نفسه معاملاً لذلك لدينا نوع T<U> . المصانع لهذه الأنواع التي تحتاج إلى الاحتفاظ بمعرفة U تسمى الأنواع ذات النوع العالي (HKT) .

الجينات تسمح بالحاويات متعددة الأشكال الآمنة من النوع.

راجع أيضًا مسألة الحاويات "غير المتجانسة" التي نوقشت أدناه. لذلك نعني من خلال تعدد الأشكال العمومية لنوع قيمة الحاوية (على سبيل المثال ، نوع عنصر المجموعة) ، ولكن هناك أيضًا مسألة ما إذا كان بإمكاننا وضع أكثر من نوع قيمة واحد في الحاوية في وقت واحد مما يجعلها غير متجانسة.


tamird كتب:

يبدو أن هذه المتطلبات تستبعد على سبيل المثال نظامًا مشابهًا لنظام سمات Rust ، حيث تكون الأنواع العامة مقيدة بحدود السمات.

حدود سمة الصدأ هي في الأساس حدود فئة نوع.

كتب alex :

سمات رست. بينما أعتقد أنهم نموذج جيد بشكل عام ، إلا أنهم سيكونون غير مناسبين لـ Go كما هو موجود اليوم.

لماذا تعتقد أنهم غير لائقين؟ ربما تفكر في أن كائنات السمات التي تستخدم إرسال وقت التشغيل تكون أقل أداءً من monomorphism؟ ولكن يمكن اعتبارها منفصلة عن مبدأ عام حدود فئة النوع (راجع مناقشتي للحاويات / المجموعات غير المتجانسة أدناه). تعد واجهات Afaics ، Go بالفعل حدودًا شبيهة بالسمات وتحقق هدف الأنواع التي تتمثل في الربط المتأخر للقواميس بأنواع البيانات في موقع الاستدعاء ، بدلاً من النمط المضاد لـ OOP الذي يرتبط مبكرًا (حتى لو كان لا يزال قيد التجميع- time) قواميس لأنواع البيانات (عند إنشاء مثيل / إنشاء). يمكن للطبقات الطباعية (على الأقل تحسين جزئي لدرجات الحرية) أن تحل مشكلة التعبير التي لا تستطيع OOP.

jimmyfrasche كتب:

  • https://golang.org/doc/faq#covariant_types

أتفق مع الرابط أعلاه على أن الأنواع الطباعية لا تقوم بالفعل بترتيب فرعي ولا تعبر عن أي علاقة وراثية. وتوافق مع عدم الخلط غير الضروري بين "العمومية" (كمفهوم إعادة الاستخدام أو النمطية أكثر من تعدد الأشكال البارامترية) مع الوراثة كما تفعل الفئة الفرعية.

ومع ذلك ، أريد أيضًا أن أشير إلى أن التسلسلات الهرمية للميراث (الملقب بالتصنيف الفرعي) أمر لا مفر منه 1 عند التعيين إلى (مدخلات الوظيفة) ومن (مخرجات الوظيفة) إذا كانت اللغة تدعم الاتحادات والتقاطعات ، لأنه على سبيل المثال int ν string يمكن قبول مهمة من int أو string لكن لا يمكن لأي منهما قبول مهمة من int ν string . بدون النقابات ، فإن الطرق البديلة الوحيدة لتوفير حاويات / مجموعات غير متجانسة من النوع الثابت هي تصنيفات فرعية أو تعدد أشكال محدود الوجودي (ويعرف أيضًا باسم كائنات السمات في الصدأ والتقدير الوجودي في هاسكل). تحتوي الروابط أعلاه على مناقشة حول المفاضلات بين الوجوديين والنقابات. Afaik ، الطريقة الوحيدة لعمل حاويات / مجموعات غير متجانسة في Go now هي استيعاب جميع الأنواع في interface{} فارغًا والذي يتخلص من معلومات الكتابة وسأفترض أنه يتطلب فحصًا من النوع ووقت التشغيل ، أي نوع من 2 يهزم نقطة الكتابة الثابتة.

إن "النمط المضاد" الذي يجب تجنبه هو تصنيف فرعي يُعرف أيضًا باسم الوراثة الافتراضية (راجع أيضًا "EDIT # 2" حول مشكلات الاستيعاب الضمني والمساواة ، إلخ).

1 بغض النظر عما إذا كانت متطابقة هيكليًا أو اسميًا لأن التصنيف الفرعي يرجع إلى مبدأ استبدال Liskov استنادًا إلى مجموعات المقارنة واتجاه التعيين مع مدخلات الوظيفة المعاكسة لقيم الإرجاع ، على سبيل المثال معلمة نوع struct أو interface لا يمكن أن يتواجد في كل من مدخلات الدالة وقيم الإرجاع ما لم تكن ثابتة بدلاً من متغير مشترك أو متغير معاكس.

2 لن تنطبق الاستبدادية لأننا لا نستطيع فحص الكون من اللاحتمية غير المحدودة. لذا ، كما أفهمها ، فإن هذا الموضوع يتعلق باختيار الحد الأمثل ("نقطة جيدة") لمستوى ذكر الكتابة في المسائل العامة.

كتب andrewcmyers :

على عكس Java و C # Genics ، لا تعتمد آلية Genus Genus على التصنيف الفرعي.

إن الوراثة والتصنيف الفرعي ( وليس التصنيف الفرعي الهيكلي ) هو أسوأ نمط مضاد لا تريد نسخه من Java و Scala و Ceylon و C ++ (لا علاقة له بمشاكل قوالب C ++ ).

thwd كتب:

الأس على مقياس التعقيد للأنواع ذات المعلمات هو التباين. أنواع Go (باستثناء الواجهات) ثابتة ويمكن ويجب أن تظل هي القاعدة.

التصنيف الفرعي مع الثبات الجانبي خطوات تعقيد التغاير. تعمل الثبات أيضًا على تحسين بعض المشكلات المتعلقة بالتصنيفات الفرعية (على سبيل المثال Rectangle مقابل Square ) ولكن ليس مشكلات أخرى (مثل الضم الضمني والمساواة وما إلى ذلك).

كتب bxqgit :

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

لاحظ أن Scala يحاول دمج OOP ، و subclassing ، و FP ، والوحدات النمطية العامة ، و HKT ، والفئات الطباعية (عبر implicit ) كلها في PL واحد. ربما تكون الفئات الطباعية وحدها كافية.

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

وبالتالي أعتقد أنه ليس من الصحيح ربط بلادة وتعقيد تلك PLs مع الأنواع في Rust على سبيل المثال. ودعنا لا نلوم أنواع الحروف على عمر Rust + استعارة الاستعارة الحصرية للتغير.

Afaics ، في قسم الدلالات في _Type Parameters في Go_ ، المشكلة التي واجهتها ianlancetaylor هي مشكلة تصور لأنه ( afaics ) على ما يبدو يعيد اختراع أنواع الطباعة عن غير قصد:

هل يمكننا دمج SortableSlice و PSortableSlice للحصول على أفضل ما في العالمين؟ ليس تماما؛ لا توجد طريقة لكتابة دالة ذات معلمات تدعم نوعًا بطريقة Less أو نوع مضمّن. تكمن المشكلة في أنه لا يمكن إنشاء مثيل لـ SortableSlice.Less لنوع بدون طريقة Less ، ولا توجد طريقة لإنشاء مثيل فقط لطريقة لبعض الأنواع دون غيرها.

العبارة requires Less[T] لفئة الكتابة المرتبطة (حتى لو استنتجها المترجم ضمنيًا) في طريقة Less لـ []T موجودة على T وليس []T . تنفيذ فئة typeclass Less[T] (التي تحتوي على طريقة Less ) لكل T ستوفر إما تنفيذًا في نص الوظيفة للطريقة أو تعيين < وظيفة مدمجة U[T] إذا كانت أساليب Sortable[U] تحتاج إلى معلمة نوع U تمثل نوع التنفيذ ، على سبيل المثال []T . لدى Afairkeean طريقة أخرى لهيكلة الفرز باستخدام فئة نوع منفصلة لنوع القيمة T والتي لا تتطلب HKT.

لاحظ أن هذه الطرق لـ []T قد تقوم بتطبيق فئة كتابة Sortable[U] ، حيث U هو []T .

(جانبًا تقنيًا: قد يبدو أنه يمكننا دمج SortableSlice و PSortableSlice من خلال وجود آلية ما لإنشاء مثيل فقط لطريقة لبعض وسيطات النوع دون غيرها. ومع ذلك ، ستكون النتيجة التضحية بالتجميع - أمان نوع الوقت ، لأن استخدام النوع الخاطئ قد يؤدي إلى ذعر وقت التشغيل. في Go ، يمكن للمرء بالفعل استخدام أنواع وأساليب الواجهة وتأكيدات الكتابة لتحديد السلوك في وقت التشغيل. ليست هناك حاجة لتوفير طريقة أخرى للقيام بذلك باستخدام معلمات النوع .)

يتم حل تحديد فئة الحروف المرتبط بموقع الاستدعاء في وقت التحويل لـ T المعروف بشكل ثابت. إذا كانت هناك حاجة إلى إرسال ديناميكي غير متجانس ، فراجع الخيارات التي شرحتها في رسالتي السابقة.

آمل أن يجد keean وقتًا للمجيء إلى هنا والمساعدة في شرح فئات الطباعة لأنه أكثر خبرة وساعدني في تعلم هذه المفاهيم. قد يكون لدي بعض الأخطاء في توضيحي.

ملاحظة ملاحظة لأولئك الذين قرأوا رسالتي السابقة بالفعل ، لاحظ أنني قمت بتحريرها على نطاق واسع بعد حوالي 10 ساعات من نشرها (بعد بعض النوم) لنأمل أن تجعل النقاط حول الحاويات غير المتجانسة أكثر تماسكًا.


يبدو أن قسم الدورات غير صحيح. بناء وقت التشغيل لمثيل S[T]{e} لـ struct لا علاقة له باختيار تنفيذ الوظيفة العامة المسماة. من المفترض أنه يعتقد أن المترجم لا يعرف ما إذا كان متخصصًا في تنفيذ الوظيفة العامة لنوع الحجج ، ولكن كل هذه الأنواع معروفة في وقت الترجمة.

ربما يمكن تبسيط مواصفات قسم فحص النوع من خلال دراسة مفهوم @ keean لرسم بياني متصل لأنواع مميزة كعقد لخوارزمية التوحيد. يجب أن تحتوي أي أنواع مميزة متصلة بواسطة حافة على أنواع متطابقة ، مع وجود حواف تم إنشاؤها لأي أنواع تتصل عبر التعيين أو بطريقة أخرى في التعليمات البرمجية المصدر. إذا كان هناك اتحاد وتقاطع (من رسالتي السابقة) ، فيجب مراعاة اتجاه المهمة ( بطريقة ما؟ ). يبدأ كل نوع غير معروف مميز بحد أدنى علوي (LUB) للحدود العلوية والحدود السفلية الأكبر (GLB) من أسفل ، ومن ثم يمكن للقيود تغيير هذه الحدود. يجب أن يكون للأنواع المتصلة حدود متوافقة. يجب أن تكون جميع القيود عبارة عن حدود فئة نمطية.

قيد التنفيذ :

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

أعتقد أن المصطلح التقني الصحيح هو monomorphisation .

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

قد يخبر التنميط المبرمج عن الوظائف التي يمكن أن تستفيد أكثر من monomorphisation. ربما يقوم مُحسِّن Java Hotspot بتحسين monomorphisation في وقت التشغيل؟

egonelbre كتب:

يوجد ملخص لمناقشات Go Generics ، والذي يحاول تقديم نظرة عامة على المناقشات من أماكن مختلفة.

يبدو أن قسم النظرة العامة يشير ضمنيًا إلى أن استخدام Java العالمي لمراجع الملاكمة للحالات الموجودة في الحاوية هو المحور الوحيد للتصميم الذي يعارض تمامًا تشكيل قوالب C ++. ولكن يتم تطبيق حدود فئة النوع (والتي يمكن تنفيذها أيضًا باستخدام قوالب C ++ ولكن أحادية الشكل دائمًا) على الوظائف وليس على معلمات نوع الحاوية. وبالتالي فإن النظرة العامة تفتقر إلى محور التصميم الخاص بفئات الطباعة حيث يمكننا اختيار ما إذا كنا سنحدد كل وظيفة محددة من فئة الطباعة. باستخدام فئات الطباعة ، نجعل المبرمجين أسرع دائمًا (أقل نموذجية) ويمكن أن نحصل على توازن أكثر دقة بين جعل المجمعين / التنفيذين أسرع / أبطأ ونفخ الكود أكبر / أقل. في رسالتي السابقة ، ربما يكون الخيار الأمثل هو ما إذا كان اختيار الوظائف المراد تعديلها يعتمد على ملف التعريف (تلقائيًا أو على الأرجح عن طريق التعليق التوضيحي).

في قسم المشاكل: هياكل البيانات العامة :

سلبيات

  • تميل الهياكل العامة إلى تجميع الميزات من جميع الاستخدامات ، مما يؤدي إلى زيادة أوقات الترجمة أو زيادة حجم التعليمات البرمجية أو الحاجة إلى رابط أكثر ذكاءً.

بالنسبة إلى فئات الطباعة ، يعد هذا إما غير صحيح أو يمثل مشكلة أقل ، لأن الواجهات تحتاج فقط إلى التنفيذ لأنواع البيانات التي يتم توفيرها للوظائف التي تستخدم تلك الواجهات. تعتبر Typeclasses حول الربط المتأخر للتنفيذ بالواجهة ، على عكس OOP الذي يربط كل نوع من أنواع البيانات بأساليبه لتطبيق class .

كذلك ، لا يلزم وضع كل الطرق في واجهة واحدة. يمكن لعبارة requires (حتى لو استنتجها المحول البرمجي ضمنيًا) الموجودة في فئة نوع مرتبط بتعريف دالة ، أن تخلط وتطابق الواجهات المطلوبة.

  • تميل الهياكل العامة وواجهات برمجة التطبيقات التي تعمل عليها إلى أن تكون مجردة أكثر من واجهات برمجة التطبيقات المصممة لغرض معين ، والتي يمكن أن تفرض عبئًا معرفيًا على المتصلين

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

  • التحسينات المتعمقة غير عامة ومحددة بالسياق ، ومن ثم يصعب تحسينها في خوارزمية عامة.

هذه ليست خدعة صالحة. تنص القاعدة 80/20 على عدم إضافة تعقيد غير محدود (مثل التحسين السابق لأوانه) للرمز الذي لا يتطلب ذلك عند وضع ملف التعريف. يتمتع المبرمج بحرية التحسين في 20٪ من الحالات بينما يتم التعامل مع 80٪ المتبقية من خلال التعقيد المحدود والحمل المعرفي لواجهات برمجة التطبيقات العامة.

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

حلول بديلة:

  • استخدام أبسط الهياكل بدلا من الهياكل المعقدة

    • على سبيل المثال ، استخدم الخريطة [int] Struct {} بدلاً من Set

يبدو أن Rob Pike (وأنا شاهدته أيضًا يوضح هذه النقطة في الفيديو) يفتقد إلى النقطة التي مفادها أن الحاويات العامة ليست كافية لعمل وظائف عامة. نحتاج إلى أن T في map[T] حتى نتمكن من تمرير نوع البيانات العامة في وظائف للمدخلات والمخرجات ولصالحنا struct . العوامل العامة فقط على معلمات نوع الحاوية غير كافية تمامًا للتعبير عن واجهات برمجة التطبيقات العامة وواجهات برمجة التطبيقات العامة المطلوبة للتعقيد المحدود والحمل المعرفي والحصول على الانتظام في النظام البيئي للغة. كما أنني لم أر المستوى المتزايد لإعادة البناء (وبالتالي انخفاض قابلية التركيب للوحدات التي لا يمكن إعادة بنائها بسهولة) التي تتطلبها التعليمات البرمجية غير العامة ، وهو ما تتحدث عنه مشكلة التعبير التي ذكرتها في رسالتي الأولى.

في قسم الأساليب العامة :

قوالب الحزم
هذا هو الأسلوب الذي تستخدمه Modula-3 و OCaml و SML (ما يسمى بـ "funators") و Ada. بدلاً من تحديد نوع فردي للتخصص ، تكون الحزمة بأكملها عامة. أنت تخصص الحزمة عن طريق تثبيت معلمات النوع عند الاستيراد.

قد أكون مخطئا ولكن هذا لا يبدو صحيحا تماما. يمكن أيضًا أن تُرجع مفاعلات ML (التي يجب عدم الخلط بينها وبين مفاعلات FP) ناتجًا يظل نوعًا محددًا. لن تكون هناك طريقة لاستخدام الخوارزميات في وظائف عامة أخرى ، لذلك لن تتمكن الوحدات النمطية العامة من إعادة الاستخدام (عن طريق الاستيراد بأنواع محددة إلى) وحدات عامة أخرى. يبدو أن هذه محاولة للإفراط في التبسيط ثم تفويت تمامًا نقطة الأدوية الجنيسة ، وإعادة استخدام الوحدة ، وما إلى ذلك.

بالأحرى ما أفهمه هو أن هذه الحزمة (المعروفة أيضًا باسم الوحدة النمطية) تُمكّن معلمات النوع القدرة على تطبيق معلمة (معلمات) النوع على مجموعة من struct ، interface ، و func .

نظام كتابة أكثر تعقيدًا
هذا هو النهج الذي اتبعه هاسكل ورست.
[...]
سلبيات:

  • يصعب وضعها في لغة أبسط (https://groups.google.com/d/msg/golang-nuts/smT_0BhHfBs/MWwGlB-n40kJ)

نقلاً عن ianlancetaylor في المستند المرتبط:

إذا كنت تعتقد ذلك ، فمن الجدير بالذكر أن جوهر
رمز الخريطة والشريحة في وقت تشغيل Go ليس عامًا بمعنى
باستخدام نوع تعدد الأشكال. إنه عام بمعنى أنه ينظر إليه
اكتب معلومات الانعكاس لمعرفة كيفية التنقل ومقارنة النوع
القيم. لذلك لدينا دليل من الوجود على أن الكتابة مقبولة
كود "عام" في Go عن طريق كتابة كود غير متعدد الأشكال يستخدم الكتابة
انعكاس المعلومات بكفاءة ، ثم لف هذا الرمز
أسلوب التحويل البرمجي الآمن من النوع وقت التجميع (في حالة الخرائط والشرائح
لوحة الغلاية هذه ، بالطبع ، مقدمة من قبل المترجم).

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

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

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

ولكن إذا قام مترجم من بناء جملة من نوع الطابعة بمحاكاة الطريقة اليدوية الحالية التي يمكن أن يستخدمها Go لنمذجة الأدوية الجنيسة (تحرير: الذي قرأته للتو أن حالات andrewcmyers معقولة ) ، فقد يكون هذا أقل صعوبة ويجد تآزرًا مفيدًا. على سبيل المثال ، أدركت أنه يمكن محاكاة نوعين من أنواع المعلمات باستخدام interface على struct الذي يحاكي tuple ، أو ذكر jba فكرة لاستخدام interface في السياق . يبدو أن struct يتم كتابته بشكل هيكلي بدلاً من كتابته اسميًا ما لم يتم إعطاء اسم بـ type ؟ لقد أكدت أيضًا أن طريقة interface يمكنها إدخال interface آخر لذا قد يكون من الممكن التحويل من HKT في مثال الفرز الذي كتبته في رسالتي السابقة هنا. لكني أحتاج إلى التفكير في هذا أكثر عندما لا أشعر بالنعاس.

أعتقد أنه من العدل أن نقول إن معظم فريق Go لا يحبون C ++
القوالب ، حيث تم وضع طبقات لغة تورينج كاملة فوقها
لغة تورينج كاملة أخرى مثل اللغتين
صيغ مختلفة تماما ، والبرامج في كلتا اللغتين
مكتوبة بطرق مختلفة جدا. تعمل قوالب C ++ كتنبيه
حكاية لأن التنفيذ المعقد قد عمت الكل
مكتبة قياسية ، مما تسبب في أن تصبح رسائل خطأ C ++ مصدرًا لـ
عجب ودهشة. هذا ليس مسارًا ستتبعه Go على الإطلاق.

أشك في أن أي شخص سوف يختلف! فائدة monomorphisation متعامدة مع الجوانب السلبية لمحرك Turing الكامل للأدوية metaprogramming.

راجع للشغل ، يبدو لي أن خطأ تصميم قوالب C ++ هو نفس الجوهر التوليدي لعيب موانع ML التوليدية (على عكس التطبيقية). ينطبق مبدأ القوة الأقل.


كتب ianlancetaylor :

إنه لأمر مخيب للآمال أن نرى Go يصبح أكثر وأكثر تعقيدًا من خلال إضافة أنواع معلمات مضمنة.

على الرغم من التكهنات في هذه القضية ، أعتقد أن هذا غير مرجح للغاية.

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

أعتقد أنه من المرجح أن يحدث شوكة إلى ناقل ، جزئيًا لأن لدي تمويل لتنفيذه وأنا مهتم بفعل ذلك. ومع ذلك ، ما زلت أقوم بتحليل الموقف.

قد يؤدي ذلك إلى كسر النظام البيئي ، ولكن على الأقل يمكن أن يظل Go خالصًا لمبادئه البسيطة. وبالتالي لتجنب كسر النظام البيئي والسماح ببعض الابتكارات الأخرى التي أرغب بها ، ربما لن أجعلها مجموعة شاملة وأطلق عليها اسم Zero بدلاً من ذلك.

كتب pciet :

تصويتي هو لا للأدوية العامة للتطبيقات ، نعم لمزيد من الوظائف العامة المضمنة مثل append و copy التي تعمل على أنواع أساسية متعددة. ربما يمكن إضافة sort و search لأنواع المجموعات؟

قد يؤدي توسيع هذا القصور الذاتي إلى منع ميزة الأدوية الجنيسة الشاملة من الوصول إليها على الإطلاق. أولئك الذين يريدون الأدوية الجنيسة من المرجح أن يغادروا إلى مراعي أكثر خضرة. وكرر andrewcmyers هذا:

سيكون من المخيب للآمال رؤية Go يصبح أكثر وأكثر تعقيدًا من خلال إضافة أنواع معلمات مضمنة. سيكون من الأفضل فقط إضافة دعم اللغة للمبرمجين لكتابة أنواع معلمات خاصة بهم.

@ shelby3

Afaik ، الطريقة الوحيدة لعمل حاويات / مجموعات غير متجانسة في Go now هي استيعاب جميع الأنواع في واجهة فارغة {} والتي تتخلص من معلومات الكتابة ، وأفترض أنها تتطلب فحصًا لنوع وقت التشغيل والقوالب ، وهو نوع من 2 يهزم نقطة كتابة ثابتة.

راجع نمط الغلاف في التعليقات أعلاه للتحقق من النوع الثابت لمجموعات الواجهة {} في Go.

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

هل يمكنك شرح هذا أكثر؟ بالنسبة لأنواع المجموعات ، فإن وجود واجهة تحدد السلوك العام الضروري للعناصر المحتواة يبدو من المعقول كتابة الوظائف إليه.

pciet هذا الكود يفعل حرفيا الشيء الذي كان @ shelby3 يصفه ويفكر فيه المضاد. نقلا عنك من قبل:

هذا يعطي أمثلة في التوثيق والاختبار التي تكتب نمط الغلاف (شكرًاpierrre) لسلامة نوع وقت التجميع ولديه أيضًا فحص الانعكاس لسلامة نوع وقت التشغيل.

إنك تأخذ تعليمة برمجية تفتقر إلى معلومات الكتابة ، وعلى أساس نوع بنوع ، تضيف الممثلين وفحص نوع وقت التشغيل باستخدام الانعكاس. هذا هو بالضبط ما كان يشكو @ shelby3 منه. إنني أميل إلى تسمية هذا النهج بـ "monomorphization يدويًا" وهو بالضبط نوع العمل الروتيني الممل الذي أعتقد أنه الأفضل للمترجم.

هذا النهج له عدد من العيوب:

  • يتطلب أغلفة من النوع بنوع ، يتم صيانتها يدويًا أو باستخدام أداة تشبه go generate
  • (إذا تم ذلك يدويًا بدلاً من أداة) فرصة لارتكاب أخطاء في النموذج المعياري والتي لن يتم اكتشافها حتى وقت التشغيل
  • يتطلب إرسالًا ديناميكيًا بدلاً من الإرسال الثابت ، وهو أبطأ ويستخدم ذاكرة أكبر
  • يستخدم انعكاس وقت التشغيل بدلاً من تأكيدات نوع وقت الترجمة ، وهو أيضًا بطيء
  • غير قابل للإنشاء: يعمل بالكامل على أنواع محددة دون أي فرص لاستخدام حدود تشبه الطبقة (أو حتى تشبه الواجهة) على الأنواع ، ما لم تقم بتسليم طبقة أخرى من المراوغة لكل واجهة غير فارغة تريد أيضًا تجريدها

هل يمكنك شرح هذا أكثر؟ بالنسبة لأنواع المجموعات ، فإن وجود واجهة تحدد السلوك العام الضروري للعناصر المحتواة يبدو من المعقول كتابة الوظائف إليه.

الآن في كل مكان تريد استخدام حد بدلاً من أو بالإضافة إلى نوع ملموس ، عليك كتابة نفس نموذج التحقق من الكتابة لكل نوع واجهة أيضًا. إنه يضاعف فقط الانفجار (ربما الاندماجي) لأغلفة النوع الثابت التي يجب عليك كتابتها.

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

type Foo interface {
    ...
}

type Bar interface {
    ...
}

كيف نعبّر ، باستخدام فحص من النوع الثابت البحت ، عن رغبتنا في نوع ينفذ كلاً من Foo و Bar ؟ على حد علمي ، فإن هذا غير ممكن في Go (باستثناء اللجوء إلى فحوصات وقت التشغيل التي قد تفشل ، وتجنب الأمان من النوع الثابت).

باستخدام نظام الأدوية الجنيسة القائم على النوع يمكننا التعبير عن هذا على النحو التالي:

func baz<T Foo + Bar>(t T) {
    ...
}

تضمين التغريدة

كيف نعبر ، باستخدام فحص نوع ثابت تمامًا ، عن أننا نريد نوعًا ينفذ كلاً من Foo و Bar؟

ببساطة مثل ذلك:

type T interface {
    Foo
    Bar
}

func baz(t T) { ... }

sbinet أنيق ، TIL

أنا شخصياً أعتبر انعكاس وقت التشغيل ميزة خاطئة ، لكن هذا أنا فقط ... يمكنني أن أخوض في السبب إذا كان أي شخص مهتمًا.

أعتقد أن أي شخص يطبق الأدوية الجنيسة من أي نوع يجب أن يقرأ "عناصر البرمجة" لستيبانوف عدة مرات أولاً. سيؤدي ذلك إلى تجنب الكثير من مشاكل Not Invented Here وإعادة اختراع العجلة. بعد قراءة هذا يجب أن يكون واضحًا لماذا "مفاهيم C ++" و "Haskell Typeclasses" هي الطريقة الصحيحة للقيام بالأدوية الجنيسة.

أرى أن هذه المشكلة تبدو نشطة مرة أخرى
هذا هو ملعب اقتراح الفراشة
https://go-li.github.io/test.html
فقط الصق البرامج التجريبية من هنا
https://github.com/go-li/demo

شكرا جزيلا لتقييمك لهذه المعلمة المفردة
وظائف الأدوية.

نحافظ على ملف gccgo و
كان هذا المشروع مستحيل بدونك ، لذلك نحن
أراد أن يساهم مرة أخرى.

كما نتطلع إلى أي نوع من الأدوية الجنيسة التي تعتمدها ، استمر في العمل الرائع!

anlhord أين تفاصيل التنفيذ حول هذا؟ أين يمكن للمرء أن يقرأ عن بناء الجملة؟ ما الذي تم تنفيذه؟ ما الذي لم يتم تنفيذه؟ ما هي مواصفات هذا التطبيق؟ ما هي إيجابيات وسلبيات ذلك؟

يحتوي رابط الملعب على أسوأ مثال ممكن على هذا:

package main

import "fmt"

func main() {
    fmt.Println("Hello, playground")
}

لا يخبرني هذا الرمز بأي شيء عن كيفية استخدامه وما الذي يمكنني اختباره.

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

آمل أن يساعدك هذا في فهم المشاكل المتعلقة بتعليقك.

joho كتب:

هل ستكون هناك قيمة للأدبيات الأكاديمية لأي إرشادات حول طرق التقييم؟

الورقة الوحيدة التي قرأتها حول هذا الموضوع هي هل يستفيد المطورون من الأنواع العامة ؟ (paywall آسف ، قد جوجل طريقك إلى تنزيل pdf) الذي قال ما يلي

وبالتالي ، تفسير متحفظ للتجربة
هو أن الأنواع العامة يمكن اعتبارها مقايضة
بين خصائص التوثيق الإيجابية و
خصائص التمدد السلبية.

أفترض أن OOP والتصنيف الفرعي (على سبيل المثال الفئات في Java و C ++) لن يتم اعتبارهما على محمل الجد لأن Go لديه بالفعل فئة نوع مثل interface (بدون معلمة النوع العام الصريحة T ) ، Java هي تم الاستشهاد به على أنه ما لا يجب نسخه ، ولأن الكثيرين جادلوا بأنهم نمط مضاد. لقد ربطت ببعض هذه الحجة لأعلى. يمكننا التعمق في هذا التحليل إذا كان أي شخص مهتمًا.

لم أدرس بعد بحثًا جديدًا مثل نظام الجنس المذكور أعلاه . أنا حذر من أنظمة "حوض المطبخ" التي تحاول مزج العديد من النماذج (على سبيل المثال ، التصنيف الفرعي ، الميراث المتعدد ، OOP ، خطية السمات ، implicit ، فئات الطباعة ، أنواع الملخصات ، إلخ) ، بسبب الشكاوى حول Scala وجود العديد من حالات الزاوية في الممارسة العملية ، على الرغم من أن ذلك قد يتحسن مع Scala 3 (المعروف أيضًا باسم Dotty و DOT calculus). أشعر بالفضول إذا كان جدول المقارنة الخاص بهم يقارن بـ Scala 3 التجريبي أو الإصدار الحالي من Scala؟

إذن ، ما تبقى هو أدوات ML وطبقات Haskell من حيث الأنظمة العامة التي أثبتت جدواها ، والتي تعمل على تحسين القابلية للتمدد والمرونة بشكل كبير مقارنةً بالتصنيف الفرعي OOP +.

لقد قمت بتدوين بعض المناقشات الخاصة التي أجريتها مع keean حول وحدات functor ML مقابل فئات الحروف. يبدو أن النقاط البارزة هي:

  • نوع البيانات _ نموذج الجبر_ (لكن بدون البديهيات المحددة ) وتنفيذ كل نوع بيانات لكل واجهة بطريقة واحدة فقط. وبالتالي تمكين الاختيار الضمني لعمليات التنفيذ من قبل المترجم دون تعليق توضيحي في موقع الاستدعاء.

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

  • تعتبر موانع ML أكثر قوة / مرونة من الفئات الطباعية ولكن هذا يأتي على حساب المزيد من التعليقات التوضيحية وربما المزيد من تفاعلات حالة الزاوية. ووفقًا لـ keean ، فإنها تتطلب أنواعًا تابعة (للأنواع المرتبطة ) وهو نظام نوع أكثر تعقيدًا. يعتقد keean أن تعبير ستيبانوف عن العمومية كجبر_ بالإضافة إلى تصنيفات حروف قوية ومرنة بما فيه الكفاية ، لذلك يبدو أن هذا هو المكان المناسب لأحدث التقنيات ، والتي أثبتت جدواها (في Haskell والآن في Rust). ومع ذلك ، لا يتم فرض البديهيات بواسطة فئات الطباعة.

  • لقد اقترحت إضافة اتحادات للحاويات غير المتجانسة مع فئات الحروف لتمتد على طول محور آخر من مشكلة التعبير ، على الرغم من أن هذا يتطلب الثبات أو النسخ (فقط للحالات التي يتم فيها استخدام القابلية للتمدد غير المتجانسة) والتي من المعروف أن لها O (log n) تباطؤ مقارنة بالحتمية المتغيرة غير المقيد.

كتب larsth :

قد يكون من المثير للاهتمام أن يكون لديك واحد أو أكثر من transpilers التجريبية - شفرة مصدر Go Genics إلى Go 1.xy مصدر الشفرة.

ملاحظة: أشك في أن Go سيتبنى مثل هذا النظام المعقد للكتابة ، لكنني أفكر في استخدام مترجم إلى بناء جملة Go الحالي كما ذكرت في رسالتي السابقة (انظر التعديل في الأسفل). وأريد نظامًا عامًا قويًا جنبًا إلى جنب مع ميزات Go المرغوبة جدًا. يبدو أن أدوية Typeclass on Go هي ما أريده.

كتب bcmills عن اقتراحه حول دوال وقت الترجمة من أجل التعميم:

لقد سمعت أن نفس الحجة المستخدمة للدعوة إلى تصدير أنواع interface بدلاً من الأنواع الملموسة في Go APIs ، واتضح أن العكس هو أكثر شيوعًا: التجريد المبكر يفرط في أنواع ويعيق امتداد واجهات برمجة التطبيقات. (على سبيل المثال ، انظر # 19584.) إذا كنت تريد الاعتماد على هذا الخط من الجدل ، أعتقد أنك بحاجة إلى تقديم بعض الأمثلة الملموسة.

من المؤكد أن تجريدات نظام النوع تتخلى بالضرورة عن بعض درجات الحرية وأحيانًا نتخلص من تلك القيود بعبارة "غير آمنة" (أي في انتهاك للتجريد الثابت بشكل ثابت) ، ومع ذلك يجب مقايضة ذلك مقابل فوائد فصل معياري مع متغيرات مشروحة بإيجاز.

عند تصميم نظام عام ، من المحتمل أن نرغب في زيادة انتظام النظام البيئي وإمكانية التنبؤ به كأحد الأهداف الرئيسية ، خاصة إذا تم أخذ فلسفة Go الأساسية في الاعتبار (على سبيل المثال ، يمثل المبرمجون العاديون أولوية).

ينطبق مبدأ القوة الأقل. يجب الموازنة بين قوة / مرونة الدوال "المخفية في" وقت التجميع للإنتاجية مقابل قدرتها على إلحاق الضرر على سبيل المثال بإمكانية قراءة شفرة المصدر في النظام البيئي (حيث يكون الفصل المعياري مهمًا للغاية لأن القارئ لا يفعل ذلك. لا داعي لقراءة كمية غير محدودة من التعليمات البرمجية بسبب التبعيات المتعدية الضمنية ، من أجل فهم وحدة / حزمة معينة!). الحل الضمني لمثيلات تطبيق فئة الطباعة لديها هذه المشكلة إذا لم يتم الالتزام بالجبر الخاص بهم .

بالتأكيد ، لكن هذا صحيح بالفعل بالنسبة للعديد من القيود الضمنية في Go بشكل مستقل عن أي آلية برمجة عامة.

على سبيل المثال ، قد تتلقى الوظيفة معلمة من نوع الواجهة وتستدعي طرقها في البداية بالتسلسل. إذا تغيرت هذه الوظيفة لاحقًا لاستدعاء هذه الأساليب بشكل متزامن (عن طريق توليد goroutines إضافية) ، فإن القيد "يجب أن يكون آمنًا للاستخدام المتزامن" لا ينعكس في نظام النوع.

لكن afaik Go لم يحاول تصميم فكرة تجريدية لتوحيد تلك التأثيرات. يحتوي الصدأ على مثل هذا التجريد (الذي أعتقد أنه مبالغة في استخدام pita / tsuris / الحد من بعض / معظم حالات الاستخدام وأنا أجادل من أجل تجريد نموذج أحادي الخيط بسهولة ولكن للأسف لا يدعم Go تقييد جميع goroutines التي تم إنتاجها على نفس الموضوع ) . وتتطلب هاسكل تحكمًا أحاديًا في التأثيرات بسبب فرض وظائف صافية للشفافية المرجعية .


alercah كتب:

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

متفق. القدرة على كسر الشفرة خلسة في الوحدات النمطية الأخرى لأن ثوابت الأنواع لم يتم شرحها بشكل واضح هو أمر خبيث بشكل فاضح.


كتب andrewcmyers :

لكي أكون واضحًا ، لا أعتبر إنشاء رمز على غرار الماكرو ، سواء تم ذلك باستخدام أدوات gen أو cpp أو gofmt -r أو أدوات ماكرو / قالب أخرى ، ليكون حلاً جيدًا لمشكلة الأدوية العامة حتى لو تم توحيده. لديها نفس مشاكل قوالب C ++: كثرة التعليمات البرمجية ، ونقص التحقق من النوع المعياري ، وصعوبة تصحيح الأخطاء. يزداد الأمر سوءًا عندما تبدأ ، كما هو طبيعي ، في بناء كود عام من حيث الكود العام الآخر. في رأيي ، المزايا محدودة: ستبقي الحياة بسيطة نسبيًا لكتاب مترجم Go كما أنها تنتج كودًا فعالًا - ما لم يكن هناك ضغط ذاكرة التخزين المؤقت للتعليمات ، وهو موقف متكرر في البرامج الحديثة!

keean يبدو أنه يتفق معك.

@ shelby3 شكرا على التعليقات. هل يمكنك في المرة القادمة إجراء التعليقات / التعديلات مباشرة في المستند نفسه. من الأسهل تتبع الأماكن التي تحتاج إلى إصلاح ، ومن الأسهل التأكد من أن جميع الملاحظات تحصل على استجابة مناسبة.

يبدو أن قسم النظرة العامة يشير إلى أن استخدام Java العالمي لمراجع الملاكمة للحالات ...

تمت إضافة تعليق لتوضيح أنه لا يُقصد به أن يكون قائمة شاملة. إنه موجود بشكل أساسي حتى يحصل الناس على جوهر المقايضات المختلفة. القائمة الكاملة للنهج المختلفة أدناه.

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

يدور هذا البيان حول ما يحدث لهياكل البيانات العامة على المدى الطويل. بعبارة أخرى ، غالبًا ما تنتهي بنية البيانات العامة بجمع جميع الاستخدامات المختلفة - بدلاً من وجود تطبيقات متعددة أصغر لأغراض مختلفة. فقط كمثال ، انظر إلى https://www.scala-lang.org/api/2.12.3/scala/collection/immutable/List.html.

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

تميل الهياكل العامة وواجهات برمجة التطبيقات التي تعمل عليها إلى أن تكون مجردة أكثر من واجهات برمجة التطبيقات المبنية لهذا الغرض ...

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

تمت إضافة ملاحظة حول الحمل المعرفي للعديد من واجهات برمجة التطبيقات المماثلة.

عمليات إعادة التنفيذ ذات الحالة الخاصة ليست غير محدودة في الممارسة العملية. سترى فقط عددًا ثابتًا من التخصص.

هذه ليست خدعة صالحة.

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

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

ما نحصل عليه هنا حقًا هو انتظام اللغة وواجهات برمجة التطبيقات العامة ، وليس الإضرار بذلك.

بالتأكيد من الناحية العملية ، قد تحتاج إلى هذا النمط من التحسين فقط لأقل من 1٪ من الحالات.

حلول بديلة:

لا يُقصد بالحلول البديلة أن تكون بديلاً عن الأدوية الجنيسة. بل قائمة بالحلول المحتملة لأنواع مختلفة من المشاكل.

قوالب الحزم

قد أكون مخطئا ولكن هذا لا يبدو صحيحا تماما. يمكن أيضًا أن تُرجع مفاعلات ML (التي يجب عدم الخلط بينها وبين مفاعلات FP) ناتجًا يظل نوعًا محددًا.

هل يمكنك تقديم صياغة أوضح وإذا لزم الأمر تقسيمها إلى نهجين مختلفين؟

egonelbre شكرًا أيضًا على الرد حتى أتمكن من معرفة النقاط التي أحتاجها لتوضيح أفكاري بشكل أكبر.

هل يمكنك في المرة القادمة إجراء التعليقات / التعديلات مباشرة في المستند نفسه.

أتمنى اعتذارًا أن أتمكن من الامتثال ولكني لم أستخدم أبدًا ميزات المناقشة في Google Doc ، وليس لدي وقت لتعلمها ، وأنا أيضًا أفضل أن أكون قادرًا على الربط بمناقشاتي على Github للرجوع إليها في المستقبل.

فقط كمثال ، انظر إلى https://www.scala-lang.org/api/2.12.3/scala/collection/immutable/List.html.

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

أنا حذر من أنظمة "حوض المطبخ" التي تحاول مزج العديد من النماذج (على سبيل المثال ، التصنيف الفرعي ، الوراثة المتعددة ، OOP ، خطية السمات ، implicit ، فئات الطباعة ، أنواع الملخصات ، إلخ) ، بسبب الشكاوى حول Scala وجود العديد من حالات الزاوية في الممارسة العملية ، على الرغم من أن ذلك قد يتحسن مع Scala 3 (المعروف أيضًا باسم Dotty و DOT calculus).

لا أعتقد أن مكتبة مجموعة Scala ستكون ممثلة للمكتبات التي تم إنشاؤها من أجل PL مع أنواع الحروف فقط لتعدد الأشكال. Afair ، تستخدم مجموعات Scala النمط المضاد للميراث ، الذي تسبب في التسلسلات الهرمية المعقدة ، جنبًا إلى جنب مع مساعدين مثل $ implicit CanBuildFrom مما أدى إلى تفجير ميزانية التعقيد. وأعتقد أنه إذا تم الالتزام بنقطة @ keean حول أن عناصر ستيبانوف في البرمجة جبرًا ، فيمكن إنشاء مكتبة مجموعات أنيقة. كان هذا هو أول بديل رأيته لمكتبة المجموعات القائمة على functor (FP) (أي عدم نسخ Haskell ) على أساس الرياضيات أيضًا. أريد أن أرى هذا عمليًا ، وهذا أحد أسباب تعاوني / مناقشته معه في تصميم PL جديد. واعتبارًا من هذه اللحظة ، أخطط أن تكون هذه اللغة قابلة للترجمة مبدئيًا (على الرغم من أنني كنت أحاول منذ سنوات إيجاد طريقة لتجنب القيام بذلك). لذلك نأمل أن نكون قادرين على التجربة قريبًا لنرى كيف يعمل.

تصوري هو أن مجتمع / فلسفة Go يفضلون الانتظار لمعرفة ما ينجح في الممارسة واعتماده لاحقًا بمجرد إثباته ، بدلاً من التسرع وتلويث اللغة بالتجارب الفاشلة. لأنه كما كررت ، فإن كل هذه الادعاءات المجردة ليست بناءة للغاية (باستثناء ربما منظري التصميم PL). كما أنه من غير المعقول أيضًا تصميم نظام متماسك للأدوية من قبل اللجنة.

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

وأعتقد أنه سيساعد عدم خلط العديد من النماذج المختلفة المتاحة للمبرمج بنفس اللغة. يبدو أنه ليس ضروريًا ( keean وأنا بحاجة لإثبات هذا الادعاء). أعتقد أننا كلانا ينسب إلى الفلسفة القائلة بأن ميزانية التعقيد محدودة وأن ما تتركه من PL هو بنفس أهمية الميزات المضمنة.

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

متفق. ومن الصعب أيضًا على الجميع اتباع النقاط المجردة. الشيطان في التفاصيل والنتائج الفعلية في البرية.

بالتأكيد من الناحية العملية ، قد تحتاج إلى هذا النمط من التحسين فقط لأقل من 1٪ من الحالات.

يحتوي Go بالفعل على interface للعموم ، بحيث يمكنه التعامل مع الحالات التي لا تحتاج فيها إلى تعدد الأشكال البارامترية على النوع T لمثيل الواجهة التي يوفرها موقع الاتصال.

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

قد تكون على صواب. لدي أيضًا شكوكي حول مدى استخدامي لأي ميزة لغوية معينة. أعتقد أن التجريب لمعرفة الطريقة التي سأواصل بها. تصميم PL هو عملية تكرارية ، ثم تكمن المشكلة في محاربة القصور الذاتي الذي يتطور مما يجعل من الصعب تكرار العملية. لذلك أعتقد أن روب بايك كان محقًا في الفيديو حيث يقترح كتابة برامج تكتب رمزًا للبرامج (بمعنى الانتقال إلى أدوات التوليد وأجهزة التحويل) لتجربة الأفكار واختبارها.

عندما نتمكن من إظهار أن مجموعة معينة من الميزات تتفوق في الممارسة (ونأمل أيضًا أن تكون شعبية الاستخدام) على تلك الموجودة حاليًا في Go ، فربما يمكننا رؤية بعض التوافق في الآراء حول إضافتها إلى Go. أشجع الآخرين أيضًا على إنشاء أنظمة تجريبية يمكن تحويلها إلى نظام Go.

هل يمكنك تقديم صياغة أوضح وإذا لزم الأمر تقسيمها إلى نهجين مختلفين؟

أضف صوتي إلى أولئك الذين يريدون تثبيط محاولة وضع بعض ميزات النماذج المبسطة بشكل مفرط في Go وادعاء أن هذه هي الأدوية الجنيسة. IOW ، أعتقد أن نظام الأدوية الجنيسة الذي يعمل بشكل مناسب والذي لن ينتهي به الأمر إلى خمول سيئ يتعارض بشكل أساسي مع الرغبة في الحصول على تصميم مفرط التبسيط للأدوية. Afaik ، نظام الأدوية الجنيسة يحتاج إلى تصميم شامل مدروس جيدًا ومثبت جيدًا. بترديدًا لما كتبه larsth ، أشجع أولئك الذين لديهم مقترحات جادة على بناء محول التردد أولاً (أو تنفيذه في مفترق واجهة gccgo الأمامية) ثم تجربة الاقتراح حتى نتمكن جميعًا من فهم حدوده بشكل أفضل. لقد شجعتني على قراءة الأخبار التي تقول إن ianlancetaylor لا تعتقد أنه ستتم إضافة تلوث من القصور الذاتي السيئ إلى Go. أما بالنسبة لشكواي الخاصة بشأن اقتراح معلمات مستوى الحزمة ، اقتراحي لمن يقترحه على الإطلاق ، يرجى التفكير فيما إذا كان يجب إنشاء مترجم يمكننا جميعًا استخدامه للعب معه ومن ثم يمكننا جميعًا التحدث عن أمثلة لما نحب وما لا نفعله. ر لا يعجبني في ذلك. بخلاف ذلك ، نتحدث عن بعضنا البعض لأنني ربما لا أفهم الاقتراح بشكل صحيح كما هو موضح بشكل تجريدي. يجب ألا أفهم الاقتراح ، لأنني لا أفهم كيف يمكن إعادة استخدام الحزمة المحددة في حزمة أخرى والتي تم تحديدها أيضًا. IOW ، إذا كانت الحزمة تأخذ معلمات ، فإنها تحتاج إلى إنشاء مثيل للحزم الأخرى ذات المعلمات أيضًا. ولكن يبدو أن الاقتراح كان ينص على أن الطريقة الوحيدة لإنشاء مثيل لحزمة ذات معلمات كانت بنوع ملموس ، وليس معلمات كتابة.

اعتذار طويل جدا. تريد التأكد من أنني لم يساء فهمها.

@ shelby3 آه ، ثم أساءت فهم الشكوى الأولية. أولاً ، يجب أن أوضح أن الأقسام في "مقاربات Generics" ليست مقترحات محددة. إنها مقاربات أو ، بعبارة أخرى ، قرارات تصميم أكبر قد يتخذها المرء في نهج محدد للأدوية العامة. ومع ذلك ، فإن المجموعات مدفوعة بقوة بالتطبيقات الحالية أو المقترحات الملموسة / غير الرسمية. أيضًا ، أظن أن هناك ما لا يقل عن 5 أفكار كبيرة لا تزال مفقودة من تلك القائمة.

بالنسبة لنهج "قوالب الحزم" ، هناك نوعان مختلفان منه (راجع المناقشات المرتبطة في المستند):

  1. الحزم العامة القائمة على "الواجهة" ،
  2. الحزم العامة صراحة.

بالنسبة إلى 1. لا تتطلب الحزمة العامة القيام بأي شيء خاص - على سبيل المثال ، ستصبح الحزمة العامة container/ring قابلة للاستخدام للتخصص. تخيل "التخصص" هنا حيث يتم استبدال جميع مثيلات الواجهة في الحزمة بالنوع الملموس (وتجاهل الواردات الدائرية). عندما تخصص الحزمة نفسها حزمة أخرى ، يمكنها استخدام "الواجهة" كتخصص - ويترتب على ذلك أن هذا الاستخدام سيكون متخصصًا أيضًا.

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

لذا ، نعم ، يمكنك استخدام حزمة معلمة واحدة في حزمة أخرى.

بترديدًا لما كتبهlarsth ، أشجع أولئك الذين لديهم مقترحات جادة على بناء محول التردد أولاً (أو تنفيذه في مفترق واجهة gccgo الأمامية) ثم تجربة الاقتراح حتى نتمكن جميعًا من فهم حدوده بشكل أفضل.

أعلم أن هذا لم يكن موجهاً صراحةً إلى هذا النهج ، لكن لديه 4 نماذج أولية مختلفة لاختبار الفكرة. بالطبع ، هم ليسوا مترجمين كاملين ، لكنها كافية لاختبار القليل من الأفكار. على سبيل المثال ، لست متأكدًا مما إذا كان أي شخص قد نفذ حالة "استخدام الحزمة ذات المعلمات من أخرى".

تبدو الحزم ذات المعاملات مثل وحدات ML (ووحدات ML هي المعلمات يمكن أن تكون حزمًا أخرى). هناك طريقتان يمكن أن تعمل بهما "تطبيقية" أو "إنتاجية". الدوافع التطبيقية هي مثل القيمة أو النوع المستعار. يجب إنشاء المفاعلات التوليدية وتختلف كل حالة عن الأخرى. هناك طريقة أخرى للتفكير في هذا ، وهي أن تكون الحزمة قابلة للتطبيق ، يجب أن تكون نقية (لا توجد متغيرات قابلة للتغيير على مستوى الحزمة). إذا كانت هناك حالة على مستوى الحزمة ، فيجب أن تكون توليد لأن هذه الحالة تحتاج إلى التهيئة ، ومن المهم أي "مثيل" للحزمة التوليدية التي تمررها بالفعل كمعامل إلى حزم أخرى والتي بدورها يجب أن تكون مولدة. على سبيل المثال ، تعتبر حزم Ada إنتاجية.

تكمن مشكلة نهج الحزمة التوليدية في أنه ينشئ الكثير من النماذج المعيارية ، حيث تقوم بإنشاء مثيل للحزم ذات المعلمات. يمكنك إلقاء نظرة على الأدوية الجنيسة Ada لترى كيف يبدو هذا.

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

الدوافع التطبيقية هي مثل القيمة أو النوع المستعار. يجب إنشاء المفاعلات التوليدية وتختلف كل حالة عن الأخرى. هناك طريقة أخرى للتفكير في هذا ، وهي أن تكون الحزمة قابلة للتطبيق ، يجب أن تكون نقية (لا توجد متغيرات قابلة للتغيير على مستوى الحزمة). إذا كانت هناك حالة على مستوى الحزمة ، فيجب أن تكون توليد لأن هذه الحالة تحتاج إلى التهيئة ، ومن المهم أي "مثيل" للحزمة التوليدية التي تمررها بالفعل كمعامل إلى حزم أخرى والتي بدورها يجب أن تكون مولدة. على سبيل المثال ، تعتبر حزم Ada إنتاجية.

شكرًا لك على المصطلحات الدقيقة ، أحتاج إلى التفكير في كيفية دمج هذه الأفكار في المستند.

أيضًا ، لا يمكنني رؤية سبب عدم إمكانية الحصول على "اسم مستعار تلقائي للنوع لحزمة تم إنشاؤها" - بمعنى ما بين نهج "functorative" و "Genative functor". من الواضح ، عندما تحتوي الحزمة على شكل من أشكال الحالة ، يمكن أن يصبح تصحيح الأخطاء وفهمها معقدًا.

تكمن مشكلة نهج الحزمة التوليدية في أنه ينشئ الكثير من النماذج المعيارية ، حيث تقوم بإنشاء مثيل للحزم ذات المعلمات. يمكنك إلقاء نظرة على الأدوية الجنيسة Ada لترى كيف يبدو هذا.

وبقدر ما أراه ، فإنه سينشئ محتوى معياريًا أقل من قوالب C ++ ولكن أكثر من فئات النوع. هل لديك برنامج حقيقي جيد لـ Ada يوضح المشكلة؟ _ (بالواقع ، أعني الكود الذي يستخدمه / كان شخصًا ما يستخدمه في الإنتاج.) _

بالتأكيد ، ألق نظرة على لوحة Ada go الخاصة بي: https://github.com/keean/Go-Board-Ada/blob/master/go.adb

على الرغم من أن هذا هو تعريف فضفاض إلى حد ما للإنتاج ، إلا أن الكود تم تحسينه ، ويعمل مثل إصدار C ++ ، ومصدره المفتوح ، وقد تم تحسين الخوارزمية على مدار عدة سنوات. يمكنك إلقاء نظرة على إصدار C ++ أيضًا: https://github.com/keean/Go-Board/blob/master/go.cpp

يوضح هذا (أعتقد) أن Ada Genics هي حل أكثر إتقانًا من قوالب C ++ (ولكن هذا ليس صعبًا) ، من ناحية أخرى ، من الصعب القيام بالوصول السريع إلى هياكل البيانات في Ada بسبب القيود المفروضة على إرجاع مرجع .

إذا كنت ترغب في إلقاء نظرة على نظام الأدوية الجنيسة للغة حتمية ، أعتقد أن Ada هي واحدة من أفضل الأنظمة التي يجب النظر إليها. من المؤسف أنهم قرروا الانتقال إلى نموذج متعدد وإضافة جميع عناصر OO إلى Ada. Ada هي لغة باسكال محسنة ، وباسكال كانت لغة صغيرة وأنيقة. كانت الأدوية الجنيسة باسكال وأدا ستظل لغة صغيرة جدًا ، لكنها كانت ستكون أفضل بكثير في رأيي. نظرًا لأن تركيز Ada تحول إلى نهج OO ، فمن الصعب العثور على وثائق جيدة وأمثلة عن كيفية القيام بنفس الأشياء باستخدام الأدوية الجنيسة.

على الرغم من أنني أعتقد أن الفئات الطباعية لها بعض المزايا ، إلا أنه يمكنني التعايش مع الأدوية الجنيسة بأسلوب Ada ، إلا أن هناك بعض المشكلات التي تمنعني من استخدام Ada على نطاق أوسع ، وأعتقد أنها تجعل القيم / الأشياء خاطئة (أعتقد أن عددًا قليلاً جدًا من اللغات تفهم هذا الأمر بشكل صحيح ، يعد 'C' واحدًا من المتغيرات الوحيدة) ، من الصعب العمل مع المؤشرات (متغيرات الوصول) وإنشاء تجريدات مؤشر آمن ، ولا يوفر طريقة لاستخدام الحزم مع تعدد أشكال وقت التشغيل (يوفر نموذج كائن لهذا ، لكنه يضيف نموذجًا جديدًا بالكامل بدلاً من محاولة إيجاد طريقة لتعدد أشكال وقت التشغيل باستخدام الحزم).

الحل لتعدد أشكال وقت التشغيل هو جعل الحزم من الدرجة الأولى بحيث يمكن تمرير مثيلات توقيعات الحزم كوسائط دالة ، وهذا للأسف يتطلب أنواعًا تابعة (انظر العمل المنجز على أنواع الكائنات المعتمدة لـ Scala لإزالة الفوضى التي أحدثوها مع نظامهم الأصلي من النوع).

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

كتب keean :

أنا شخصياً أعتبر انعكاس وقت التشغيل ميزة خاطئة ، لكن هذا أنا فقط ... يمكنني أن أخوض في السبب إذا كان أي شخص مهتمًا.

يمكّن محو الكتابة "النظريات مجانًا" ، والتي لها آثار عملية . قابل للكتابة (وربما حتى يمكن قراءته بسبب العلاقات المتعدية مع التعليمات البرمجية الضرورية؟) يجعل انعكاس وقت التشغيل من المستحيل ضمان الشفافية المرجعية في أي رمز ، وبالتالي فإن بعض تحسينات المترجم غير ممكنة ولا يمكن كتابة monads آمنة. أدرك أن Rust ليس لديها حتى ميزة الثبات حتى الآن. OTOH ، يتيح الانعكاس تحسينات أخرى لم تكن ممكنة إذا لم يتم كتابتها بشكل ثابت.

لقد ذكرت أيضًا ما يلي:

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


كتب keean :

[...] لكي تكون الحزمة قابلة للتطبيق ، يجب أن تكون نقية (لا توجد متغيرات قابلة للتغيير على مستوى الحزمة)

ولا يجوز استخدام أي دوال غير نقية لتهيئة المتغيرات الثابتة.

egonelbre كتب:

لذا ، نعم ، يمكنك استخدام حزمة معلمة واحدة في حزمة أخرى.

ما كان يدور في خاطري على ما يبدو هو "حزم معلمات من الدرجة الأولى" ووقت التشغيل المتناسب (المعروف أيضًا باسم الديناميكي) الذي ذكرتهkeean لاحقًا ، لأنني افترضت أن الحزم المحددة تم اقتراحها بدلاً من فئات الطباعة أو OOP.

EDIT: ولكن هناك معنيان محتملان للوحدات النمطية "من الدرجة الأولى": الوحدات النمطية كقيم من الدرجة الأولى كما هو الحال في Successor ML و MixML مميزة عن الوحدات النمطية كقيم من الدرجة الأولى مع أنواع من الدرجة الأولى كما في 1ML ، والمقايضة الضرورية في العودية النمطية (أي الخلط ) بينهما.

كتب keean :

الحل لتعدد أشكال وقت التشغيل هو جعل الحزم من الدرجة الأولى بحيث يمكن تمرير مثيلات توقيعات الحزم كوسائط دالة ، وهذا للأسف يتطلب أنواعًا تابعة (انظر العمل المنجز على أنواع الكائنات المعتمدة لـ Scala لإزالة الفوضى التي أحدثوها مع نظامهم الأصلي من النوع).

ماذا تقصد بالأنواع التابعة؟ (تحرير: أفترض الآن أنه كان يقصد الكتابة "غير المعتمدة على القيمة" ، أي " الوظائف التي يعتمد نوع نتيجتها على [وقت التشغيل؟] الوسيطة [نوع]") بالتأكيد لا تعتمد على قيم على سبيل المثال int بيانات وحدات F-ing أن الأنواع "التابعة" ليست ضرورية تمامًا لنمذجة وحدات ML في النظام F ω . هل أفرط في التبسيط إذا افترضت أن rossberg أعادت صياغة نموذج الكتابة لإزالة جميع متطلبات monomorphisation؟

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

ألا يوجد أيضًا نموذج معياري مع دوال ML التطبيقية؟ لا يوجد توحيد معروف للفئات الطباعية ووحدات تعلم الآلة (الوحدات) التي تحتفظ بالإيجاز دون إدخال قيود ضرورية لمنع (راجع أيضًا ) النمط المضاد المتأصل لمعيار التفرد العالمي لحالات تنفيذ النوع.

يمكن أن تقوم Typeclasses فقط بتنفيذ كل نوع بطريقة واحدة وتتطلب خلاف ذلك newtype غلاف معياري للتغلب على القيد. إليك مثال آخر على طرق متعددة لتنفيذ خوارزمية. لقد عمل Afaics ،keean على حل هذا القيد في مثال فرز فئة الطباعة الخاصة به عن طريق تجاوز التحديد الضمني بأنواع Relation التي تم تحديدها بشكل صريح باستخدام التفاف data لتسمية العلاقات المختلفة بشكل عام على نوع القيمة ، لكني أشك ما إذا كانت هذه التكتيكات عامة لجميع المتغيرات النمطية. ومع ذلك ، فإن الحل الأكثر عمومية (والذي يمكن أن يساعد في التخفيف من مشكلة نمطية التفرد العالمي يمكن أن يقترن بقيد اليتيمة كتحسين للإصدار المقترح للقرار اليتيوم من خلال استخدام عدم التقصير للتطبيقات التي يمكن أن تكون يتيمة) قد يكون لديك interface ، والتي عندما لا يتم تحديد الإعدادات الافتراضية للمطابقة الضمنية العادية ، ولكن عند تحديدها (أو عندما لا يتم تحديدها لا تتطابق مع أي 2 أخرى) ، فإنها تحدد التطبيق الذي له نفس القيمة في قائمة القيم المخصصة المفصولة بفواصل (لذا فهذه مطابقة نمطية أكثر عمومية من تسمية مثيل implement محدد). القائمة المفصولة بفاصلة بحيث يمكن تمييز التطبيق بأكثر من درجة حرية واحدة ، كما لو كان له تخصصان متعامدان. يمكن تحديد التخصص المطلوب غير الافتراضي إما في إعلان الوظيفة أو موقع الاستدعاء. في موقع الاتصال ، على سبيل المثال f<non-default>(…) .

فلماذا نحتاج إلى وحدات معلمة إذا كان لدينا فئات طباعة؟ Afaics فقط من أجل (← رابط مهم للنقر) الاستبدال لأن إعادة استخدام الأنواع لهذه الأغراض لا تتناسب بشكل جيد مع ذلك ، على سبيل المثال ، نريد أن تكون وحدة الحزمة قادرة على توسيع ملفات متعددة ونريد أن نكون قادرين على فتح محتويات ضمنيًا الوحدة في النطاق بدون نموذج مرجعي إضافي . لذلك ، ربما يكون المضي قدمًا باستخدام معلمات الحزمة _syntactical-only_-only (وليس الدرجة الأولى) خطوة أولى معقولة يمكنها معالجة العمومية على مستوى الوحدة النمطية بينما تظل مفتوحة للتوافق مع الوظائف وعدم تداخلها في حالة إضافة فئات نمطية لاحقًا لمستوى الوظيفة عامة. وحدات الماكرو هذه مكتوبة على سبيل المثال أو مجرد استبدال نحوي (يُعرف أيضًا باسم "المعالج المسبق"). إذا تم كتابتها ، فإن الوحدات النمطية تضاعف وظائف فئات الطباعة ، وهو أمر غير مرغوب فيه من وجهة نظر تقليل النماذج / المفاهيم المتداخلة لـ PL وحالات الزاوية المحتملة بسبب تفاعلات التداخل (مثل تلك التي تحدث عند محاولة تقديم كل من Functions ML و typeclasses) ). تعتبر الوحدات النمطية أكثر نمطية لأن التعديلات التي يتم إجراؤها على أي تنفيذ مغلف داخل الوحدة النمطية التي لا تقوم بتعديل التواقيع المصدرة لا يمكن أن تتسبب في أن يصبح مستهلكو الوحدة غير متوافقين (بخلاف مشكلة النمط المانعة للنمطية المذكورة أعلاه الخاصة بمثيلات التطبيق المتداخلة للطباعة). أنا مهتم بقراءة أفكار @ keean حول هذا الموضوع.

[...] مع استثناءات التكرار متعدد الأشكال والأنواع الوجودية (وهي في الأساس متغيرات لا يمكنك استبعادها ، يمكنك فقط استخدام الواجهات التي يؤكدها المتغير).

لمساعدة القراء الآخرين. من خلال "العودية متعددة الأشكال" ، أعتقد أنه يشير إلى الأنواع ذات التصنيف الأعلى ، على سبيل المثال ، عمليات الاسترجاعات المحددة المحددة في وقت التشغيل حيث لا يمكن للمجمع تحويل جسم وظيفة رد الاتصال بشكل أحادي لأنه غير معروف في وقت الترجمة. الأنواع الوجودية هي كما ذكرت من قبل مكافئة لكائنات سمة Rust ، والتي تعد إحدى الطرق للوصول إلى حاويات غير متجانسة مع ربط لاحق في مشكلة التعبير من class الميراث الظاهري للفئة الفرعية ، ولكن ليس مفتوحًا للتمديد في التعبير مشكلة مثل النقابات مع هياكل البيانات غير القابلة للتغيير أو النسخ 3 التي لها تكلفة أداء O (log n) .

1 الذي لا يتطلب HKT في المثال أعلاه ، لأن SET لا يتطلب النوع elem نوع معلمة من النوع العام set ، أي أنه ليس set<elem> .

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

3 ملاحظة التحول باستخدام هياكل البيانات غير القابلة للتغيير لا يتطلب بالضرورة نسخ بنية البيانات بالكامل ، إذا كانت بنية البيانات ذكية بما يكفي لعزل التاريخ مثل القائمة المرتبطة بشكل فردي.

سيكون تنفيذ func pick(a CollectionOfT, count uint) []T مثالًا جيدًا لتطبيق الأدوية الجنيسة (من https://github.com/golang/go/issues/23717):

// pick returns a slice (len = n) of pseudorandomly chosen elements 
// in unspecified order from c which is an array, slice, or map.
for i, e := range pick(c, n) {

نهج الواجهة {} هنا معقد.

لقد علقت عدة مرات على هذه المشكلة أن إحدى المشكلات الرئيسية في نهج قالب C ++ هي اعتماده على دقة التحميل الزائد كآلية لبرمجة وقت الترجمة.

يبدو أن Herb Sutter قد توصل إلى نفس النتيجة: يوجد الآن اقتراح مثير للاهتمام لبرمجة وقت الترجمة في C ++ .

لديها بعض العناصر المشتركة مع كل من حزمة Go reflect واقتراحي السابق لوظائف وقت الترجمة في Go .

مرحبا.
لقد كتبت اقتراحًا للأدوية ذات القيود المفروضة على Go. يمكنك قراءتها هنا . ربما يمكن إضافته كوثيقة من 15292. يتعلق الأمر في الغالب بالقيود ويقرأ كتعديل لمعلمات نوع تايلور في Go .
الغرض منه هو أن يكون مثالًا على طريقة عملية (على ما أعتقد) للقيام بالأدوية الجنيسة "اكتب آمنة" في Go ، ونأمل أن تضيف شيئًا إلى هذه المناقشة.
يرجى ملاحظة أنه على الرغم من أنني قرأت (معظم) هذا الموضوع الطويل جدًا ، إلا أنني لم أتبع جميع الروابط الموجودة فيه ، لذلك ربما قدم آخرون اقتراحات مماثلة. إذا كان الأمر كذلك ، فأنا أعتذر.

ر. مركز حقوق الانسان.

زواج الدرّاجات النحوية:

constraint[T] Array {
    :[#]T
}

ممكن ان يكون

type [T] Array constraint {
    _ [...]T
}

الذي يبدو أشبه بالذهاب إلي. :-)

عدة عناصر هنا.

شيء واحد هو استبدال : بـ _ واستبدال # بـ ... .
أفترض أنه يمكنك فعل ذلك إذا كان مفضلًا.

شيء آخر هو استبدال constraint[T] Array بـ type[T] Array constraint .
يبدو أن هذا يشير إلى أن القيود هي أنواع ، والتي لا أعتقد أنها صحيحة. بشكل رسمي ، القيد هو _المسجل_ على مجموعة من جميع الأنواع ، أي. تعيين من مجموعة الأنواع إلى المجموعة { true ، false }.
أو إذا كنت تفضل ذلك ، يمكنك التفكير في القيد على أنه مجرد _مجموعة من_ الأنواع.
إنه ليس نوع _a_.

ر. مركز حقوق الانسان.

لماذا لا يكون هذا constraint مجرد interface ؟

type [T io.Writer] List struct { 
    element T; 
    next *List[T];
}

قد تكون الواجهة أكثر فائدة كقيد مع الاقتراح التالي: # 23796 والذي بدوره سيعطي أيضًا بعض المزايا للاقتراح نفسه.

أيضًا ، إذا تم قبول اقتراح أنواع المجموع في شكل ما (19412 #) ، فيجب استخدام تلك الأنواع لتقييد النوع.

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

أخيرًا ، بالنسبة لجزء الزفاف ، أعتقد أنه يجب إدراج القيود في نهاية التعريف ، لتجنب الازدحام (يبدو أن الصدأ لديه فكرة جيدة هنا):

// similar to the map[T]... syntax
// also no constraint
type List[T] struct {
    element T
    next *List[T]
}

// with constraint
type List[T] struct {
    element T
    next *List[T]
} where T is io.Writer | encoding.BinaryMarshaler

type BigConstraint constraint {
     io.Writer
     SomeFunc() int
     AnotherFunc()
     AField int64
     StringField string
}


// with predefined constraint
type List[T, U] struct {
    element T
    val U
    next *List[T, U]
} where T is BigConstraint | encoding.BinaryMarshaler,
    U is io.Reader

urandom : أعتقد أن الميزة الكبيرة لـ go هي أن يكون لديك واجهات مطبقة ضمنيًا وليس صريحًا. اقتراح surlykke في هذا التعليق أعتقد أنه أقرب بكثير إلى بناء جملة Go الأخرى في الروح.

surlykke أعتذر إذا كان الاقتراح يحتوي على إجابة لأي من هؤلاء.

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

"الانتقاء" الموصوف سابقًا لديه مشكلة مماثلة حيث تختلف الفهرسة في الخريطة عن الفهرسة في شريحة. في حالة الخريطة ، إذا كان هناك تحويل إلى شريحة أولاً ، فيمكن استخدام رمز الانتقاء نفسه ، ولكن كيف يتم ذلك؟

المجموعات هي استخدام آخر:

// An unordered collection of comparable items.
type [T Comparable] Set []T

func (a Set) Diff(from Set) Set {
    // the implementation is the same as one with
    //     type Comparable interface { Equal(Comparable) bool }
    //     type Set []Comparable
}

// compile error
d := Set[int]{1, 2}.Diff(Set[string]{“abc”, “def”})

// Go 1, easier to read but runtime error
d := Set{1, 2}.Diff(Set{“abc”, “def”})

بالنسبة لحالة نوع المجموعة ، لست مقتنعًا بأن هذا يمثل فوزًا كبيرًا على Go 1 Genics نظرًا لوجود مقايضات في قابلية القراءة.

أوافق على أن معلمات النوع يجب أن يكون لها شكل من أشكال القيود. وإلا فسنكرر أخطاء قوالب C ++. السؤال هو ، إلى أي مدى يجب أن تكون القيود معبرة؟

في أحد طرفيه ، يمكننا فقط استخدام الواجهات. لكن كما أشرت ، لا يمكن التقاط الكثير من الأنماط المفيدة بهذه الطريقة.

ثم هناك فكرتك ، وفكرتك المماثلة ، التي تحاول تحديد مجموعة من القيود المفيدة وتقديم صياغة جديدة للتعبير عنها. بصرف النظر عن مشكلة إضافة المزيد من بناء الجملة ، ليس من الواضح أين تتوقف. كما أشرت ، فإن اقتراحك يلتقط العديد من الأنماط ، ولكن ليس كل شيء بأي حال من الأحوال.

في الطرف الآخر هي الفكرة التي أقترحها في هذا المستند . يستخدم كود Go نفسه كلغة مقيدة. يمكنك التقاط أي قيد تقريبًا بهذه الطريقة ، ولا يتطلب صياغة جديدة.

تضمين التغريدة
انها مطولة قليلا. ربما إذا كان Go يحتوي على بنية lambda ، فسيكون ذلك أكثر قبولًا. من ناحية أخرى ، يبدو أن أكبر مشكلة تحاول حلها هي التحقق مما إذا كان النوع يدعم عامل تشغيل من نوع ما. قد يكون الأمر أسهل إذا كان لدى Go فقط واجهات محددة مسبقًا للعديد من المشغلين:

func equal[T](x, y T) bool
    where T is runtime.Equitable {
    return x == y
}

func copyable[T](x, y []T) int {
    return copy(x, y)
}

أو شيء من هذا القبيل.

إذا كانت المشكلة تتعلق بتوسيع البنيات ، فربما تكمن المشكلة في طريقة اللغة في إنشاء أنواع المحولات. على سبيل المثال ، أليس الانتفاخ مرتبطًا بالفرز. واجه السبب الكامل وراء https://github.com/golang/go/issues/16721 و sort.Slice؟
بالنظر إلى https://github.com/golang/go/issues/21670#issuecomment -325739411 ، قد تكون فكرة Sajmani عن وجود حرفية للواجهة هي المكون الضروري لمعلمات النوع للعمل بسهولة مع المكونات المدمجة.
انظر إلى التعريف التالي لـ Iterator:

type [T] Iterator interface {
    Next() (elem T, done bool)
}

إذا كانت print دالة تتكرر ببساطة على قائمة وتطبع محتوياتها ، فإن المثال التالي يستخدم القيم الحرفية للواجهة لإنشاء واجهة مُرضية لـ print .

func SliceIterator(slice []T) Iterator {
    i := 0
    return Iterator{
        Next: func() (elem int, done bool) {
            v := slice[i]
            if i+1 == len(slice) {
                return v, true
            }
            i++
            return v, false
        },
    }
}

func main() {
    arr := []int{1,2,3,4,5}
    // SliceIterator works for an arbitrary slice
    print(SliceIterator(arr))
}

يمكن للمرء القيام بذلك بالفعل إذا أعلنوا عالميًا عن الأنواع التي تتمثل مسؤوليتها الوحيدة في تلبية واجهة. ومع ذلك ، فإن هذا التحويل من دالة إلى طريقة يجعل تلبية الواجهات (وبالتالي "القيود") أسهل. نحن لا نلوث إعلانات المستوى الأعلى بمحولات بسيطة (مثل "widgetsByName" في الفرز).
من الواضح أن الأنواع المعرفة من قبل المستخدم يمكنها أيضًا الاستفادة من هذه الميزة أيضًا ، كما يتضح من مثال LinkedList هذا:

type ListNode struct {
    v string
    next *ListNode
}
func (l *ListNode) Iterator() Iterator {
    ptr := l
    return Iterator{
        Next: func() (elem int, done bool) {
            v := ptr.v
            if ptr.next == nil {
                return v, true
            }
            ptr = ptr.next
            return v, false
        },
    }
}

@ geovanisouza92 : القيود كما وصفتها أكثر تعبيرا من الواجهات (الحقول ، المشغلون). لقد فكرت بإيجاز في تمديد الواجهات بدلاً من إدخال قيود ، لكنني أعتقد أن هذا سيكون تغييرًا شديد التطفل على عنصر موجود في Go.

pciet لست متأكدًا تمامًا مما تقصده بـ "مستوى التطبيق". يحتوي Go على وظيفة len مضمنة والتي يمكن تطبيقها على مصفوفة ، ومؤشر إلى مصفوفة ، وشريحة ، وسلسلة ، وقناة ، لذلك ، في اقتراحي ، إذا كانت معلمة النوع مقيدًا بأن تحتوي على واحدة من هذه لأنها النوع الأساسي ، يمكن تطبيق len عليه.

pciet حول مثالك مع واجهة / قيد Comparable . لاحظ أنه إذا حددت (متغير الواجهة):

type Comparable interface { Equal(Comparable) bool }
type Set []Comparable

ثم يمكنك وضع أي شيء ينفذ Comparable في Set . قارن ذلك بـ:

constraint [T] Comparable { Equal(t T) bool }
type [T Comparable[T]] Set []T
...
type FooSet Set[Foo] // Where Foo satisfies constraint Comparable

حيث يمكنك فقط وضع قيم من النوع Foo في FooSet . هذا هو نوع أقوى من السلامة.

urandom مرة أخرى ، لست من محبي:

type MyConstraint constraint {....}

كما لا أعتقد كونه من نوع. أيضًا ، بالتأكيد لن أسمح بما يلي:

var myVar MyConstraint

الأمر الذي لا معنى له بالنسبة لي. مؤشر آخر على أن القيود ليست أنواعًا.

urandom On bikeshedding: أعتقد أنه يجب الإعلان عن القيود بجوار معلمات النوع. ضع في اعتبارك وظيفة عادية ، مُعرَّفة على النحو التالي:

func MyFunc(i) {
     if (i>0) fmt.Println("It's positive")
} with i being an integer

لا يمكنك قراءة هذا من اليسار إلى اليمين. بدلاً من ذلك ، عليك أولاً قراءة func MyFunc(i) لتحديد أنه تعريف دالة. ثم عليك القفز إلى النهاية لمعرفة ما هو i ، ثم العودة إلى جسم الوظيفة. ليست مثالية ، IMO. ولا أرى كيف يجب أن تكون التعريفات العامة مختلفة.
لكن من الواضح أن هذه المناقشة متعامدة مع المناقشة حول ما إذا كان يجب أن يكون لدى Go قيود أو أدوية جنيسة.

تضمين التغريدة
أنا بخير مع أنه ليس من النوع. الشيء الأكثر أهمية هو أن لديهم اسمًا بحيث يمكن الإشارة إليهم بأنواع متعددة.

بالنسبة للوظائف ، إذا اتبعنا صيغة الصدأ ، فسيكون:

func MyFunc[I](i I) int64
     where I is being an integer {
   return 42
}

لذلك لن يخفي أشياء مثل اسم الوظيفة أو معلماتها ، ولن تحتاج إلى الذهاب إلى نهاية جسم الوظيفة لمعرفة القيد على الأنواع العامة

surlykke للأجيال القادمة ، هل يمكنك تحديد مكان إضافة اقتراحك إلى:
https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX4pi-HnNjkMEgyAHX4N4

إنه مكان رائع "لتجميع" جميع المقترحات.

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

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

في اقتراح دوال وقت الترجمة ، نظرًا لأننا نعلم أن هذه الإعلانات يتم إنشاؤها في وقت الترجمة ، فإن مفتاح النوع لا يفرض أي تكلفة لوقت التشغيل.

سيناريو عملي: إذا أخذنا في الاعتبار حالة حزمة الرياضيات / البتات ، فإن إجراء تأكيد نوع للاتصال بـ OnesCount لكل uintXX سيتغلب على نقطة وجود مكتبة فعالة لمعالجة البتات. ومع ذلك ، إذا تم تحويل تأكيدات النوع إلى ما يلي

func OnesCount(x T) int {
    switch x.(type) {
    case uint:
        // separate uint functionality...
    case uint8:
        // separate uint8 functionality...
    case uint16:
        // separate uint16 functionality...
    case uint32:
        // separate uint32 functionality...
    case uint64:
        // separate uint64 functionality...
    }
}

دعوة ل

var x uint8 = 255
bits.OnesCount(x)

عندئذٍ تستدعي الوظيفة المُنشأة التالية (الاسم ليس مهمًا هنا):

func $OnesCount_uint8(x uint8) {
    // separate uint8 functionality...
}

jba هذا اقتراح مثير للاهتمام ، لكنه بالنسبة لي يسلط الضوء في الغالب على حقيقة أن تعريف الوظيفة البارامترية نفسها عادة ما تكون كافية لتحديد قيودها.

إذا كنت ستستخدم "عوامل التشغيل المستخدمة في دالة" كقيود ، فما الميزة التي تشتريها لك لكتابة دالة ثانية تحتوي على مجموعة فرعية من العوامل المستخدمة في الأولى؟

bcmills أحدهما هو المواصفات والآخر هو التنفيذ. إنها نفس ميزة الكتابة الثابتة: يمكنك اكتشاف الأخطاء في وقت مبكر.

إذا كان التنفيذ هو المواصفات ، قوالب à la C ++ ، فمن المحتمل أن يؤدي أي تغيير في التنفيذ إلى كسر التابعين. قد لا يتم اكتشاف ذلك حتى وقت لاحق ، عندما يعيد المعالون الترجمة ، ولا يكون للمكتشفين سياق لفهم رسالة الخطأ. مع المواصفات في نفس الحزمة ، يمكنك اكتشاف الكسر محليًا.

mandolyte لست متأكدًا تمامًا من مكان إضافته - ربما فقرة أسفل "مقاربات Generics" تسمى "Generics مع قيود"؟
لا يبدو أن المستند يحتوي على الكثير حول تقييد معلمات النوع ، لذلك إذا أضفت فقرة حيث سيتم ذكر اقتراحي ، فيمكن إدراج طرق أخرى للقيود هناك أيضًا.

surlykke النهج العام في المستند هو إجراء تغيير ما يبدو صحيحًا وسأحاول قبوله ودمجه وتنظيمه مع بقية المستند. لقد أضفت قسمًا هنا . لا تتردد في إضافة أشياء فاتني.

egonelbre هذا لطيف جدا. شكرا!

تضمين التغريدة
يعجبني اقتراحك ، لكنني أعتقد أنه طريقة ثقيلة جدًا بالنسبة لـ golang. يذكرني الكثير من القوالب في c ++. أعتقد أن المشكلة الرئيسية هي أنه يمكنك كتابة كود معقد حقًا باستخدامه.
لتحديد ما إذا كان هناك تداخل بين مثيلين للواجهة العامة لأن مجموعة الأنواع المقيدة ستكون مهمة صعبة تتسبب في إبطاء أوقات الترجمة. الشيء نفسه بالنسبة لتوليد الكود.

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

يجب أن أوافق بشدة على أنه لا ينبغي لنا أن نتعامل مع قيود ضمنية من جسم الوظيفة. تُعتبر على نطاق واسع واحدة من أهم ميزات أخطاء قوالب C ++:

  • القيود ليست واضحة للعيان. بينما يمكن لـ godoc نظريًا تعداد جميع القيود في التوثيق ، إلا أنها غير مرئية في الكود المصدري باستثناء ضمنيًا.
  • لهذا السبب ، من الممكن تضمين قيد إضافي عن طريق الخطأ يكون مرئيًا فقط عند محاولة استخدام الوظيفة بطريقة غير متوقعة. من خلال طلب تحديد واضح للقيود ، يجب أن يعرف المبرمج بالضبط ما هي القيود التي يقوم بإدخالها.
  • فهو يتخذ القرار بشأن أنواع القيود المسموح بها بشكل أكثر تخصصًا. على سبيل المثال ، هل يسمح لي بتعريف الوظيفة التالية؟ ما هي القيود الفعلية على T و U و V هنا؟ إذا طلبنا من المبرمج تحديد القيود بشكل صريح ، فنحن إذن متحفظون في نوع القيود التي نسمح بها (مما يسمح لنا بتوسيع ذلك ببطء وبشكل متعمد). إذا حاولنا أن نكون متحفظين على أي حال ، فكيف نعطي رسالة خطأ لوظيفة كهذه؟ "خطأ: لا يمكن تعيين uv () إلى T لأنه يفرض قيدًا غير قانوني"؟
func[T, U, V] Foo(u U, v V) {
  var t T = u.v(V) + 1;
}
  • إن استدعاء وظائف عامة في وظائف عامة أخرى يجعل المواقف المذكورة أعلاه أسوأ ، حيث تحتاج الآن إلى النظر في جميع قيود كاليس لفهم قيود الوظيفة التي تكتبها أو تقرأها.
  • يمكن أن يكون تصحيح الأخطاء صعبًا للغاية ، لأن رسائل الخطأ يجب إما ألا توفر معلومات كافية للعثور على مصدر القيد ، أو يجب أن تتسبب في تسرب التفاصيل الداخلية للوظيفة. على سبيل المثال ، إذا كان لدى F بعض المتطلبات على نوع T ، وكان مؤلف F يحاول معرفة من أين جاء هذا المطلب ، فإنهم يرغبون في أن يقوم المترجم تنبيههم بالضبط إلى العبارة التي تؤدي إلى ظهور القيد (خاصةً إذا كانت تأتي من مستدعي عام). لكن مستخدم F لا يريد هذه المعلومات ، وبالفعل ، إذا تم تضمينها في رسائل الخطأ ، فإننا نسرب تفاصيل تنفيذ F في رسائل الخطأ من مستخدميها ، والتي هي تجربة مستخدم مروعة.

تضمين التغريدة

على سبيل المثال ، هل يسمح لي بتعريف الوظيفة التالية؟

func[T, U, V] Foo(u U, v V) {
  var t T = u.v(V) + 1;
}

رقم u.v(V) خطأ في بناء الجملة لأن V هو نوع ، والمتغير t غير مستخدم.

ومع ذلك ، يمكنك تحديد هذه الوظيفة ، والتي قد تكون الوظيفة التي كنت تقصدها:

func[T, U, V] Foo(u U, v V) {
    var _ T = u.v(v) + 1;
}

ما هي القيود الفعلية على T و U و V هنا؟

  • النوع V غير مقيد.
  • يجب أن يحتوي النوع U على طريقة v تقبل معلمة واحدة أو متغيرات من نوع ما يمكن تخصيصه من V ، لأنه يتم استدعاء u.v باستخدام وسيطة واحدة من النوع V .

    • يمكن أن يكون U.v حقلاً من نوع الوظيفة ، ولكن يمكن القول إن هذا يجب أن يتضمن طريقة ؛ انظر # 23796.

  • يجب أن يكون النوع الذي تم إرجاعه بواسطة U.v رقميًا ، لأنه تمت إضافة الثابت 1 إليه.
  • يجب تخصيص نوع الإرجاع U.v إلى T ، لأنه تم تعيين u.v(…) + 1 لمتغير من النوع T .
  • يجب أن يكون النوع T رقميًا ، لأن نوع الإرجاع U.v رقمي ويمكن تخصيصه لـ T .

(جانبا: يمكنك القول بأن U و V يجب أن يكون لهما القيد "قابل للنسخ" لأن وسيطات هذه الأنواع يتم تمريرها بالقيمة ، لكن نظام النوع غير العام الحالي لا يفرض هذا القيد أيضًا. هذه مسألة تتعلق بمقترح منفصل.)

إذا طلبنا من المبرمج تحديد القيود بشكل صريح ، فنحن إذن متحفظون في نوع القيود التي نسمح بها (مما يسمح لنا بتوسيع ذلك ببطء وبشكل متعمد).

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

يحدث الغموض الأكثر إثارة للاهتمام بالنسبة للمعاني الحرفية ، حيث نحتاج إلى توضيح ما بين أنواع الهياكل والأنواع المركبة:

func[T] Foo() (t T) {
    x := 42;
    t = T{x: "some string"}  // Is x an index, or a field name?
    _ = x
}

إذا حاولنا أن نكون متحفظين على أي حال ، فكيف نعطي رسالة خطأ لوظيفة كهذه؟ "خطأ: لا يمكن تعيين uv () إلى T لأنه يفرض قيدًا غير قانوني"؟

لست متأكدًا تمامًا مما تطلبه ، لأنني لا أرى قيودًا متضاربة في هذا المثال. ماذا تقصد ب "القيد غير المشروع"؟

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

لا يمكن التعبير عن كل قيد ذي صلة بواسطة نظام النوع (راجع أيضًا https://github.com/golang/go/issues/22876#issuecomment-347035323). يتم فرض بعض القيود من خلال الذعر وقت التشغيل ؛ يتم فرض بعضها بواسطة كاشف السباق ؛ أخطر القيود موثقة فقط ولم يتم الكشف عنها على الإطلاق.

كل هذه "التفاصيل الداخلية تسرب" إلى حد ما. (راجع أيضًا https://xkcd.com/1172/.)

على سبيل المثال ، إذا كان [...] مؤلف F يحاول معرفة من أين جاء هذا المطلب ، فإنهم يريدون من المترجم أن ينبههم بالضبط إلى العبارة التي تؤدي إلى القيد (خاصة إذا كانت تأتي من مستدعي عام). لكن مستخدم F لا يريد هذه المعلومات [.]

يمكن؟ هذه هي الطريقة التي يستخدم بها مؤلفو واجهة برمجة التطبيقات التعليقات التوضيحية من النوع في اللغات المستنبطة من النوع مثل Haskell و ML ، ولكنها تؤدي أيضًا إلى أسفل حفرة من الأنواع البارامترية العميقة ("ذات الترتيب الأعلى") بشكل عام.

على سبيل المثال ، افترض أن لديك هذه الوظيفة:

func [F, Arg, Result] InvokeAsync(f F, x Arg) (<-chan Result) {
    c := make(chan result, 1)
    go func() { c <- f(x) }()
    return c
}

كيف تعبر عن القيود الصريحة على النوع Arg ؟ أنها تعتمد على إنشاء مثيل محدد لـ F . يبدو أن هذا النوع من التبعية مفقود في العديد من المقترحات الأخيرة بشأن القيود.

رقم uv (V) خطأ في بناء الجملة لأن V هو نوع والمتغير t غير مستخدم.

ومع ذلك ، يمكنك تحديد هذه الوظيفة ، والتي قد تكون الوظيفة التي كنت تقصدها:

نعم ، كان هذا هو القصد ، اعتذاري.

يجب أن يكون النوع T رقميًا ، لأن نوع الإرجاع U.v رقمي ويمكن تخصيصه لـ T .

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

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

كنت أعني "القيود التي نسمح بها" كما في اللغة. مع وجود قيود صريحة ، يسهل علينا تحديد نوع القيود التي نرغب في السماح للمستخدمين بكتابتها ، بدلاً من مجرد قول القيد هو "كل ما يجعل الأشياء مجمعة". على سبيل المثال ، يتضمن المثال الخاص بي Foo أعلاه نوعًا إضافيًا ضمنيًا منفصلًا عن T أو U أو V ، نظرًا لأنه يجب علينا مراعاة نوع الإرجاع u.v . لا تتم الإشارة إلى هذا النوع صراحة بأي شكل من الأشكال في تصريح f ؛ الخصائص التي يجب أن تحتوي عليها ضمنية تمامًا. وبالمثل ، هل نحن على استعداد للسماح بأنواع أعلى مرتبة ( forall )؟ لا أستطيع أن أتوصل إلى مثال من أعلى رأسي ، لكنني أيضًا لا أستطيع أن أقنع نفسي بأنه لا يمكنك ضمنيًا كتابة نوع مرتب أعلى مرتبة.

مثال آخر هو ما إذا كان ينبغي أن نسمح للدالة بالاستفادة من بناء الجملة المحمّل. إذا كانت الدالة المقيدة ضمنيًا تؤدي for i := range t لبعض t من النوع العام T ، فإن بناء الجملة يعمل إذا كان T هو أي مصفوفة ، شريحة ، قناة ، أو الخريطة. لكن الدلالات مختلفة تمامًا ، خاصة إذا كان T نوع قناة. على سبيل المثال ، إذا كان t == nil (والذي يمكن أن يحدث طالما أن T عبارة عن مصفوفة) ، فإن التكرار إما لا يفعل شيئًا ، نظرًا لعدم وجود عناصر في شريحة أو خريطة صفرية ، أو كتل إلى الأبد نظرًا لأن هذا هو ما يحصل عليه على قنوات nil . هذا مسدس كبير ينتظر أن يحدث. وبالمثل يفعل m[i] = ... ؛ إذا كنت أنوي أن أكون خريطة m ، فسوف أحتاج إلى الحماية من كونها شريحة في الواقع لأن الكود قد يصيب بالذعر في مهمة خارج النطاق بخلاف ذلك.

في الواقع ، أعتقد أن هذا يفسح المجال أمام حجة أخرى ضد القيود الضمنية: قد يكتب مؤلفو واجهة برمجة التطبيقات عبارات مصطنعة فقط لإضافة قيود. على سبيل المثال ، يمنع for _, _ := range t { break } القناة مع استمرار السماح بالخرائط والشرائح والمصفوفات ؛ x = append(x) يفرض x الحصول على نوع شريحة. يسمح var _ = make(T, 0) بالشرائح والخرائط والقنوات ولكن ليس المصفوفات. سيكون هناك كتاب وصفات حول كيفية إضافة قيود ضمنيًا حتى لا يتمكن شخص ما من استدعاء وظيفتك بنوع لم تكتب رمزًا صحيحًا له. لا يمكنني حتى التفكير في طريقة لكتابة رمز يتم تجميعه فقط لأنواع الخرائط ما لم أعرف نوع المفتاح أيضًا. ولا أعتقد أن هذا افتراضي على الإطلاق. تتصرف الخرائط والشرائح بشكل مختلف تمامًا بالنسبة لمعظم التطبيقات

لست متأكدًا تمامًا مما تطلبه ، لأنني لا أرى قيودًا متضاربة في هذا المثال. ماذا تقصد ب "القيد غير المشروع"؟

أعني قيدًا لا تسمح به اللغة ، على سبيل المثال إذا قررت اللغة عدم السماح بالقيود ذات التصنيف الأعلى.

لا يمكن التعبير عن كل قيد ذي صلة بواسطة نظام النوع (انظر أيضًا # 22876 (تعليق)). يتم فرض بعض القيود من خلال الذعر وقت التشغيل ؛ يتم فرض بعضها بواسطة كاشف السباق ؛ أخطر القيود موثقة فقط ولم يتم الكشف عنها على الإطلاق.

كل هذه "التفاصيل الداخلية تسرب" إلى حد ما. (راجع أيضًا https://xkcd.com/1172/.)

لا أرى حقًا كيف يأتي # 22876 في هذا ؛ يحاول استخدام نظام الكتابة للتعبير عن نوع مختلف من القيد. سيكون صحيحًا دائمًا أننا لا نستطيع التعبير عن بعض القيود على القيم ، أو على البرامج ، حتى مع نظام نوع من التعقيد التعسفي. لكننا نتحدث فقط عن القيود المفروضة على الأنواع هنا. يحتاج المترجم إلى أن يكون قادرًا على الإجابة على السؤال "هل يمكنني إنشاء مثيل لهذا العام بالنوع T ؟" مما يعني أنه يجب أن يفهم القيود ، سواء كانت ضمنية أو صريحة. (لاحظ أن بعض اللغات ، مثل C ++ و Rust ، لا يمكنها تحديد هذا السؤال بشكل عام لأنه يمكن أن يعتمد على الحساب التعسفي وبالتالي ينتقل إلى مشكلة التوقف ، لكنها لا تزال تعبر عن القيود التي يجب استيفائها.)

ما أعنيه هو أشبه بـ "ما رسالة الخطأ التي يجب أن يقدمها المثال التالي؟"

func [U] DirectlyConstrained(U t) {
    t.DoSomething();
}
func [T] IndirectlyConstrained(T t) {
    DirectlyConstrainted(t);
}
func Illegal() {
    IndirectlyConstrained(4);
}

يمكننا القول Error: cannot call IndirectlyConstrained with [T = int]; T must have a method with signature func (T t) DoSomething() . رسالة الخطأ هذه مفيدة لمستخدم IndirectlyConstrained ، لأنها تحدد بوضوح القيود التي يفوتونها. لكنها لا تقدم أي معلومات لشخص يحاول تصحيح سبب وجود هذا القيد في IndirectlyConstrained ، وهي مشكلة كبيرة في قابلية الاستخدام إذا كانت دالة كبيرة. يمكننا إضافة Note: this constraint exists on T because IndirectlyConstrained calls DirectlyConstrained with [U = T] on line N ، لكننا الآن نسرب تفاصيل تنفيذ IndirectlyConstrained . علاوة على ذلك ، لم نوضح سبب وجود هذا القيد في IndirectlyConstrained ، فهل نضيف Note: this constraint exists on U because DirectlyConstrained calls t.DoSomething() on line M آخر؟ ماذا لو جاء القيد الضمني من بعض المستويات الأربعة أسفل مكدس الاستدعاءات؟

علاوة على ذلك ، كيف يمكننا تنسيق رسائل الخطأ هذه للأنواع غير المدرجة صراحة كمعلمات؟ على سبيل المثال ، إذا كان في المثال أعلاه ، فإن IndirectlyConstrained يستدعي DirectlyConstrained(t.U()) . كيف نشير حتى إلى النوع؟ في هذه الحالة يمكننا أن نقول the type of t.U() ، لكن القيمة لن تكون بالضرورة نتيجة تعبير واحد ؛ يمكن أن تكون مبنية على عبارات متعددة. بعد ذلك ، سنحتاج إما إلى تجميع تعبير بالأنواع الصحيحة لوضعه في رسالة الخطأ ، تعبير لا يظهر مطلقًا في الكود ، أو سنحتاج إلى إيجاد طريقة أخرى للإشارة إليه والتي قد تكون أقل وضوحًا بالنسبة لـ المتصل المسكين الذي انتهك القيد.

كيف تعبر عن القيود الصريحة على نوع Arg؟ إنها تعتمد على إنشاء مثيل محدد لـ F. يبدو أن هذا النوع من التبعية مفقود من العديد من المقترحات الحديثة للقيود.

أفلِت F واستخدم النوع f be func (Arg) Result . نعم ، إنه يتجاهل الوظائف المتنوعة ، لكن باقي وظائف Go تتجاهلها أيضًا. يمكن عمل اقتراح لجعل varargs funcs قابلاً للتخصيص للتوقيعات المتوافقة بشكل منفصل.

بالنسبة للحالات التي نطلب فيها حقًا حدودًا من النوع ذي الترتيب الأعلى ، فقد يكون أو لا يكون من المنطقي تضمينها في الأدوية العامة الإصدار 1. تجبرنا القيود الصريحة على أن نقرر صراحة ما إذا كنا نريد دعم الأنواع ذات الترتيب الأعلى ، وكيف. إن عدم الاهتمام حتى الآن هو أحد الأعراض ، كما أعتقد ، لحقيقة أن Go ليس لديها طريقة حاليًا للإشارة إلى خصائص الأنواع المضمنة. إنه سؤال مفتوح عام حول كيف سيسمح أي نظام للأدوية العامة بالوظائف العامة على جميع الأنواع الرقمية ، أو جميع أنواع الأعداد الصحيحة ، ومعظم المقترحات لم تركز كثيرًا على هذا.

يرجى تقييم تطبيق الأدوية الخاصة بي في مشروعك القادم
http://go-li.github.io/

يمكننا القول Error: cannot call IndirectlyConstrained with [T = int]; T must have a method with signature func (T t) DoSomething() . لا توفر رسالة الخطأ هذه [...] أي معلومات لشخص يحاول تصحيح الأخطاء بسبب وجود هذا القيد في IndirectlyConstrained ، وهي مشكلة كبيرة في قابلية الاستخدام إذا كانت دالة كبيرة.

أريد أن أشير إلى الافتراض الكبير الذي تقوم به هنا: أن رسالة الخطأ من go build هي الأداة _ فقط_ المتاحة للمبرمج لتشخيص المشكلة.

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

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

somefile.go:123: Argument `4` to DirectlyConstrained has type `int`,
    but DirectlyConstrained requires a type `T` with method `DoSomething()`.
    (For more detail, run `guru contraints path/to/somefile.go:#1033`.)

يمنح ذلك مستخدم الحزمة العامة المعلومات التي يحتاجون إليها من أجل تصحيح أخطاء موقع الاتصال الفوري ، ولكن _أيضًا_ يوفر مسار تنقل لمدير الحزمة (والأهم من ذلك ، بيئة التحرير الخاصة بهم!) لمزيد من البحث.

يمكننا إضافة Note: this constraint exists on T because IndirectlyConstrained calls DirectlyConstrained with [U = T] on line N ، لكننا الآن نسرب تفاصيل تنفيذ IndirectlyConstrained .

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

أوافق تمامًا على أنه يجب علينا إخفاء المعلومات غير ذات الصلة - افتراضيًا - ، لكن هذا لا يعني أنه يجب علينا إخفائها بشكل مطلق.

إذا كانت الدالة المقيدة ضمنيًا تعمل مع i := range t لبعض t من النوع العام T ، فإن بناء الجملة يعمل إذا كان T هو أي مصفوفة أو شريحة أو قناة ، أو الخريطة. لكن الدلالات مختلفة تمامًا ، خاصة إذا كان T نوع قناة.

أعتقد أن هذه هي الحجة الأكثر إقناعًا لقيود النوع ، لكن هذا لا يتطلب قيودًا صريحة لتكون في أي مكان قريب من الإسهاب مثل ما يقترحه بعض الأشخاص. لإزالة الغموض عن مواقع الاتصال ، يبدو أنه من الكافي تقييد معلمات النوع بشيء أقرب إلى reflect.Kind . لا نحتاج إلى وصف العمليات التي تم توضيحها بالفعل من الكود ؛ بدلاً من ذلك ، نحتاج فقط إلى قول أشياء مثل " T هو نوع شريحة". يؤدي هذا إلى مجموعة أبسط من القيود:

  • يجب تصنيف النوع الخاضع لعمليات الفهرس على أنه خطي أو ترابطي ،
  • يجب تصنيف نوع يخضع لعمليات range على أنه لا شيء فارغ أو لا يحظر ،
  • يجب تسمية النوع ذي القيم الحرفية على أنه يحتوي على حقول أو فهارس ، و
  • (ربما) يحتاج النوع الذي يحتوي على عمليات رقمية إلى تسميته كنقطة ثابتة أو عائمة.

يؤدي ذلك إلى لغة تقييد أضيق بكثير ، ربما شيء مثل:

TypeConstraint = "sliceable" | "map" | "chan" | "struct" | "integer" | "float" | "type"

مع أمثلة مثل:

func[T:integer, U, V] Foo(u U, v V) {
    var _ T = u.v(v) + 1;
}
func [S:sliceable, T] append(s S, x ...T) S {
    dst := s
    if cap(s) - len(s) < len(x) {
        dst = make(S, len(s), nextSizeClass(cap(s)))
        copy(dst, s)
    }
    copy(dst[len(s):cap(s)], x)
    return dst[:len(s)+len(x)]
}

أشعر أننا قد خطونا خطوة كبيرة نحو عام مخصص من خلال تقديم اسم مستعار من النوع.
النوع المستعار يجعل الأنواع الفائقة (أنواع الأنواع) ممكنة.
يمكننا التعامل مع الأنواع مثل القيم في استخدام.

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

يتوافق كل نوع من الأنواع ، باستثناء أنواع البنية والواجهة والوظيفة ، مع نوع محدد مسبقًا.

  • منطقي
  • سلسلة
  • Int8، Uint8، Int16، Uint16، Int32، Uint32، Int64، Uint64، Int، Uint، Uintptr
  • فلوت 32 ، فلوت 64
  • مجمع 64 ، مجمع 128
  • مصفوفة ، شريحة ، خريطة ، قناة ، مؤشر ، مؤشر غير آمن

هناك بعض الأنواع الأخرى التي تم تحديدها مسبقًا ، مثل Comaprable و Numeric و Interger و Float و Complex و Container وما إلى ذلك. يمكننا استخدام Type أو * للدلالة على النوع من جميع الأنواع.

تبدأ أسماء جميع الأنواع المضمنة بحرف كبير.

يتوافق كل نوع من البنية والواجهة والوظيفة مع النوع.

يمكننا أيضًا الإعلان عن الأنواع المخصصة:

genre Addable = Numeric | String
genre Orderable = Interger | Float | String
genre Validator = func(int) bool // each parameter and result type must be a specified type.
genre HaveFieldsAndMethods = {
    width  int // we must use a specific type to define the fields.
    height int // we can't use a genre to define the fields.
    Load(v []byte) error // each parameter and result type must be a specified type.
    DoSomthing()
}
genre GenreFromStruct = aStructType // declare a genre from a struct type
genre GenreFromInterface = anInterfaceType // declare a genre from an interface type
genre GenreFromStructInterface = aStructType + anInterfaceType
genre ComparableStruct = HaveFieldsAndMethods & Comprable
genre UncomparableStruct = HaveFieldsAndMethods &^ Comprable

لجعل الشرح التالي متسقًا ، يلزم تعديل النوع.
يُرمز إلى معدّل النوع بـ Const . فمثلا:

  • Const Integer هو نوع (يختلف عن Integer ) ويجب أن يكون مثيله قيمة ثابتة يجب أن يكون النوع عددًا صحيحًا. ومع ذلك ، يمكن النظر إلى القيمة الثابتة كنوع خاص.
  • Const func(int) bool هو نوع (يختلف عن func(int) bool ) ويجب أن يكون مثيله قيمة دالة delcared. ومع ذلك ، يمكن اعتبار إعلان الوظيفة كنوع خاص.

(حل التعديل صعب بعض الشيء ، ربما هناك حلول تصميم أخرى أفضل.)

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

تصريح صندوق (افترض أن الكود التالي تم الإعلان عنه في الحزمة lib ):

crate Example [T Float, S {width, height T}, N Const Integer] [*, *, *] {
    type MyArray [N]T

    func Add(a, b T) T {
        return a+b
    }

    type M struct {
        x T
        y S
    }

    func (m *M) Area() T {
        m.DoSomthing()
        return m.y.width * m.y.height
    }

    func (m *M) Perimeter() T {
        return 2 * Add(m.y.width, m.y.height)
    }

    export M, Add, MyArray
}

باستخدام الصندوق أعلاه.

import "lib"

// We can use AddFunc as a normal delcared function.
// Its genre is "Const func (a, b T) T"
type Rect, AddFunc, Array = lib.Example[float32, struct{x, y float32}, 100]

func demo() {
    var r Rect
    a, p = r.Area(), r.Perimeter()
    _ = AddFunc(a, p)
}

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

ما تسميه "النوع" يسمى في الواقع "النوع" ، وهو معروف جيدًا في
مجتمع البرمجة الوظيفية. ما تسميه الصندوق مقيد
نوع ممتلئ ML.

في الأربعاء ، 4 أبريل 2018 ، 12:41 ظهرًا كتب dotaheor [email protected] :

أشعر أننا قد خطونا خطوة كبيرة نحو عام مخصص من خلال تقديم
اكتب الاسم المستعار.
النوع المستعار يجعل الأنواع الفائقة (أنواع الأنواع) ممكنة.
يمكننا التعامل مع الأنواع مثل القيم في استخدام.

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

كل نوع من الأنواع ، باستثناء أنواع البنية والواجهة والوظيفة ،
يتوافق مع النوع المحدد مسبقًا.

  • منطقي
  • سلسلة
  • Int8، Uint8، Int16، Uint16، Int32، Uint32، Int64، Uint64، Int، Uint،
    Uintptr
    & Float32 ، Float64
  • مجمع 64 ، مجمع 128
  • مصفوفة ، شريحة ، خريطة ، قناة ، مؤشر ، مؤشر غير آمن

هناك بعض الأنواع الأخرى المحددة مسبقًا ، مثل Comaprable و Numeric و
Interger ، Float ، Complex ، Container ، إلخ. يمكننا استخدام Type أو * تشير
النوع من جميع الأنواع.

تبدأ أسماء جميع الأنواع المضمنة بحرف كبير.

يتوافق كل نوع من البنية والواجهة والوظيفة مع النوع.

يمكننا أيضًا الإعلان عن الأنواع المخصصة:

النوع قابل للإضافة = رقمي | سلسلة
النوع قابل للطلب = Interger | تعويم | سلسلة
النوع Validator = func (int) bool // يجب أن يكون كل معلمة ونوع نتيجة من النوع المحدد.
النوع HaveFieldsAndMethods = {
width int // يجب أن نستخدم نوعًا محددًا لتحديد الحقول.
ارتفاع int // لا يمكننا استخدام النوع لتحديد الحقول.
تحميل (v [] بايت) خطأ // يجب أن يكون كل معلمة ونوع نتيجة من النوع المحدد.
DoSomthing ()
}
النوع GenreFromStruct = aStructType // يعلن عن نوع من نوع هيكلي
النوع GenreFromInterface = anInterfaceType // يعلن عن نوع من نوع واجهة
النوع GenreFromStructInterface = aStructType | نوع واجهة المستخدم

لجعل الشرح التالي متسقًا ، يلزم تعديل النوع.
يتم الإشارة إلى معدّل النوع بواسطة Const. فمثلا:

  • عدد صحيح Const هو نوع ومثيله يجب أن يكون قيمة ثابتة
    أي نوع يجب أن يكون عددًا صحيحًا.
    ومع ذلك ، يمكن النظر إلى القيمة الثابتة كنوع خاص.
  • Const func (int) bool هو نوع ومثيله يجب أن يكون delcared
    قيمة الوظيفة.
    ومع ذلك ، يمكن اعتبار إعلان الوظيفة كنوع خاص.

(حل التعديل صعب بعض الشيء ، ربما هناك تصميمات أخرى أفضل
حلول.)

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

تصريح صندوق (افترض أن الكود التالي مُعلن في lib
حزمة):

مثال قفص [T Float، S {width، height T}، N Const Integer] [* ، * ، *] {
اكتب MyArray [N] T.

func Add (a، b T) T {
العودة أ + ب
}

// نوع نطاق الصندوق. يمكن استخدامها فقط في الصندوق.

// M هو نوع من النوع G.
اكتب M هيكل {
س ت
ذ س
}

func (م * م) المساحة () T {
م القيام بشيء ()
إعادة عرضي * myheight
}

func (م * م) محيط () تي {
إرجاع 2 * إضافة (mywidth، myheight)
}

تصدير M ، Add ، MyArray
}

باستخدام الصندوق أعلاه.

استيراد "ليب"

// يمكننا استخدام AddFunc كوظيفة delcared عادية.
اكتب Rect، AddFunc، Array = lib.Example (float32، Struct {x، y float32})

func demo () {
فار ص مستطيل
أ ، ع = ص منطقة () ، ص محيط ()
_ = AddFunc (أ ، ع)
}

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

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-378665695 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AGGWB78BrjN0BxRfroH-jRNy4mCXgSwCks5tlPfMgaJpZM4IG-xv
.

أشعر أن هناك بعض الاختلافات بين النوع والنوع.

بالمناسبة ، إذا كان الصندوق يُرجع نوعًا واحدًا فقط ، فيمكننا استخدام دعوته كنوع مباشرة.

package lib

// export a type
crate List [T *] * {
    type List struct {
        ...
    }

    export List
}

استخدمه:

import "lib"

var l lib.List[int]

ستكون هناك بعض قواعد "خصم النوع" ، تمامًا مثل "خصم النوع" في النظام الحالي.

dotaheor ، DemiMarie هو الصحيح. يبدو مفهوم "النوع" تمامًا مثل "النوع" من نظرية النوع. (تصادف أن اقتراحك يتطلب قاعدة ملزمة فرعية ، لكن هذا ليس نادرًا.)

تعرّف الكلمة الرئيسية genre في اقتراحك الأنواع الجديدة على أنها أنواع فائقة من الأنواع الحالية. تُعرِّف الكلمة الأساسية crate الكائنات بـ "توقيعات الصناديق" ، وهي نوع ليس نوعًا فرعيًا من Type .

كنظام رسمي ، يبدو أن اقتراحك يشبه ما يلي:

قفص :: = χ | ⋯
اكتب :: = τ | χ | int | bool | ⋯ | func(τ) | func(τ) τ | []τ | χ[τ₁, …]

CrateSig :: = [κ₁, …] ⇒ [κₙ, …]
النوع :: = κ | exactly τ | kindOf κ | Map | Chan | ⋯ | Const κ | Type | CrateSig

لإساءة استخدام بعض تدوين نظرية النوع:

  • اقرأ "⊢" على أنها "يستلزم".
  • اقرأ “ k1k2 ” حيث أن “ k1 هو نوع فرعي من k2 ”.
  • اقرأ ":" على أنها "من النوع".

ثم تبدو القواعد كما يلي:

τ : exactly τ
exactly τkindOf exactly τ
kindOf exactly τType

τ : κ₁κ₁κ₂τ : κ₂

τ₁ : Typeτ₂ : TypekindOf exactly map[τ₁]τ₂Map
MapType

κ₁κ₂Const κ₁Const κ₂

[...]
(وهكذا ، لجميع الأنواع المدمجة)


تمنح تعريفات النوع أنواعًا ، وتنهار الأنواع الأساسية إلى أنواع الأنواع المضمنة:

type τ₁ τ₂τ₂ : κτ₁ : kindOf κ

kindOf kindOf κkindOf κ
kindOf MapMap
[...]


يحدد genre علاقات الأنواع الفرعية الجديدة:
genre κ = κ₁ | κ₂κ₁κ
genre κ = κ₁ | κ₂κ₂κ

(يمكنك تحديد Numeric وما شابه من حيث | .)

genre κ = κ₁ & κ₂ ∧ ( κ₃κ₁ ) ∧ ( κ₃κ₂ ) ⊢ κ₃κ


قاعدة توسيع الصندوق متشابهة:
type τₙ, … = χ[τ₁, …] ∧ ( χ : [κ₁, …] ⇒ [κₙ, …] ) ∧ ( τ₁ : κ₁ ) ∧ ⋯ ⊢ τₙ : κₙ

هذا كله يتحدث فقط عن الأنواع ، بالطبع. إذا كنت تريد تحويله إلى نظام كتابة ، فأنت بحاجة أيضًا إلى قواعد الكتابة. 🙂


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

تدور المشكلات المثيرة للاهتمام والشائكة التي يقدمها Go بشكل أساسي حول فحص النوع الديناميكي. كيف يجب أن تتفاعل معلمات الكتابة مع تأكيدات النوع والانعكاس؟

(على سبيل المثال ، هل يجب أن يكون من الممكن تحديد واجهات مع أساليب من الأنواع البارامترية؟ إذا كان الأمر كذلك ، فماذا يحدث إذا قمت بتأكيد قيمة تلك الواجهة بمعامل جديد في وقت التشغيل؟)

في ملاحظة ذات صلة ، هل كان هناك نقاش حول كيفية جعل التعليمات البرمجية عامة على الأنواع المضمنة والأنواع المعرفة من قبل المستخدم؟ مثل إنشاء رمز يمكنه التعامل مع التعدادات الكبيرة والأعداد الصحيحة البدائية؟

في ملاحظة ذات صلة ، هل كان هناك نقاش حول كيفية جعل التعليمات البرمجية عامة على الأنواع المضمنة والأنواع المعرفة من قبل المستخدم؟ مثل إنشاء رمز يمكنه التعامل مع التعدادات الكبيرة والأعداد الصحيحة البدائية؟

يمكن للآليات القائمة على النوع ، مثل Genus و Familia ، القيام بذلك بكفاءة. راجع ورقة PLDI 2015 الخاصة بنا للحصول على التفاصيل.

تضمين التغريدة
أعتقد أن "النوع" == "مجموعة السمات".

[تعديل]
ربما يكون traits أفضل لوحة مفاتيح.
يمكننا أن نرى كل نوع هو أيضا مجموعة سمات.

يتم تحديد معظم السمات لنوع واحد فقط.
لكن السمة الأكثر تعقيدًا قد تحدد العلاقة بين نوعين.

[تحرير 2]
لنفترض أن هناك مجموعتين من السمات A و B ، يمكننا القيام بالعمليات التالية:

A + B: union set
A - B: difference set
A & B: intersection set

يجب أن تكون مجموعة السمات لنوع الوسيطة مجموعة فائقة لنوع المعلمة المقابل (مجموعة سمات).
يجب أن تكون مجموعة السمات لنوع النتيجة مجموعة فرعية من نوع النتيجة المقابل (مجموعة سمات).

(IMHO)

ما زلت أعتقد أن إعادة ربط الأسماء المستعارة من النوع هو السبيل للذهاب ، لإضافة الأدوية الجنيسة إلى Go. لا يحتاج إلى تغيير كبير في اللغة. الحزم المعممة بهذه الطريقة ، لا يزال من الممكن استخدامها في Go 1.x. وليست هناك حاجة لإضافة قيود لأنه من الممكن القيام بذلك عن طريق تعيين النوع الافتراضي للاسم المستعار للنوع ، إلى شيء يفي بالفعل بهذه القيود. وأهم جانب في إعادة ربط الأسماء المستعارة هو أن الأنواع المركبة المضمنة (الشرائح والخرائط والقنوات) لا تحتاج إلى تغيير وتعميم.

@ dc0d

كيف يجب أن تحل الأسماء المستعارة محل الأدوية الجنيسة؟

sighoya Rebinding Type يمكن أن تحل الأسماء المستعارة محل الأدوية العامة (وليس فقط كتابة الأسماء المستعارة). لنفترض أن الحزمة تقدم بعض الأسماء المستعارة لنوع الحزمة مثل:

package likedlist

type T = interface{}

type LinkedList struct {
    // ...
}

إذا تم توفير Type Alias ​​Rebinding (ومرافق المترجم) ، فمن الممكن استخدام هذه الحزمة لإنشاء قوائم مرتبطة لأنواع خرسانية مختلفة ، بدلاً من الواجهة الفارغة:

package main

import (
    "likedlist"
)

type intLL = likedlist.LinkedList(likedlist.T = int)
type stringLL = likedlist.LinkedList(likedlist.T = string)

func main() {}

إذا استخدمنا الاسم المستعار على هذا النحو ، فإن الطريقة التالية تكون أنظف.

// pkg.go
package pkg

type ListNode struct {
    prev, next *ListNode
    element    ?Element
}

func Add(x, y ?T) ?T {
    return x+y
}



// main.go
package main

import "pkg"

type intList = pkg.ListNode[Element=int]
func stringAdd = pkg.Add[T=string]

func main() {
}

@ dc0d وكيف بالضبط سيتم تنفيذ ذلك؟ الكود جميل لكنه لا يخبرنا بأي شيء عن كيفية عمله في الداخل. وبالنظر إلى تاريخ مقترحات الأدوية الجنيسة ، فإن Go أمر مهم للغاية ، وليس فقط كيف يبدو وشكلها.

dotaheor لا يتوافق مع Go 1.x.

creker لقد قمت بتطبيق أداة (تسمى goreuse ) تستخدم هذه التقنية لتوليد الكود وولدت كمفهوم لـ Type Alias ​​Rebinding.

يمكن العثور عليها هنا . يوجد فيديو مدته 15 دقيقة يشرح الأداة.

@ dc0d لذا فهو يعمل نوعًا ما مثل قوالب C ++ التي تولد تطبيقات متخصصة. لا أعتقد أنه سيتم قبوله كفريق Go (وبصراحة أنا والعديد من الأشخاص الآخرين هنا) يبدو أنه ضد أي شيء مشابه لقوالب C ++. إنه يزيد من الثنائيات ، ويبطئ التجميع ، وربما لن يكون قادرًا على إنتاج أخطاء ذات مغزى. علاوة على ذلك ، لا يتوافق مع الحزم الثنائية فقط التي يدعمها Go. وهذا هو سبب اختيار C ++ لكتابة القوالب في ملفات الرأس.

تضمين التغريدة

لذلك فهو يعمل نوعًا ما مثل قوالب C ++ التي تولد تطبيقات متخصصة لكل نوع مستخدم.

لا أعرف (لقد مر حوالي 16 عامًا منذ أن كتبت أي لغة C ++). ولكن من تفسيرك يبدو أن هذا هو الحال. ومع ذلك ، لست متأكدًا مما إذا كانا متماثلان أو كيف.

لا أعتقد أنه سيتم قبوله كفريق Go (وبصراحة أنا والعديد من الأشخاص الآخرين هنا) يبدو أنه ضد أي شيء مشابه لقوالب C ++.

من المؤكد أن لدى الجميع هنا أسبابًا وجيهة لتفضيلاتهم بناءً على أولوياتهم. الأول على قائمتي هو التوافق مع Go 1.x.

يزيد من الثنائيات ،

قد يكون.

يبطئ التجميع ،

أشك بشدة في ذلك (حيث يمكن تجربته بـ goreuse ).

علاوة على ذلك ، لا يتوافق مع الحزم الثنائية فقط التي يدعمها Go.

أنا غير متاكد. هل هناك طرق أخرى لتطبيق الأدوية الجنيسة تدعم هذا؟

ربما لن تكون قادرة على إنتاج أخطاء ذات مغزى.

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

@ dc0d

أفكر قليلا في ذلك.
بالإضافة إلى ذلك ، تم إنشاؤه داخليًا على واجهات ، الحرف "T" في مثالك

type T=interface{}

يتم التعامل معها على أنها متغير نوع قابل للتغيير ، ولكن يجب أن يكون اسمًا مستعارًا لنوع معين ، أي إشارة ثابتة إلى نوع.
ما تريده هو T Type ، ولكن هذا يعني إدخال الأدوية الجنيسة.

sighoya لست متأكدًا مما إذا كنت أفهم ما قلته.

تم إنشاؤه داخليًا على واجهات

غير صحيح. كما هو موضح في تعليقي الأصلي ، من الممكن استخدام أنواع معينة تفي بالقيد. على سبيل المثال ، يمكن الإعلان عن الاسم المستعار لنوع معلمة النوع على النحو التالي:

type T = int

والأنواع التي لديها عامل تشغيل + (أو - أو * ؛ يعتمد على ما إذا كان هذا العامل مستخدمًا في جسم الحزمة على الإطلاق) يمكن استخدامها كقيمة نوع يجلس في هذا النوع من المعلمات.

لذلك ، ليست الواجهات فقط هي التي يمكن استخدامها كحامل نائب لمعلمات النوع.

ولكن هذا يعني إدخال الأدوية الجنيسة.

هذه _is_ طريقة لتقديم / تنفيذ الأدوية الجنيسة في لغة Go نفسها.

@ dc0d

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

لن يكسب إعداد "النوع T = Int" الكثير.

إذا كنت ستقول أن "النوع T" غير معلن / غير محدد أولاً والذي يمكن تعيينه لاحقًا ، حسنًا ، لديك شيء مثل الأدوية الجنيسة.

تكمن المشكلة في أن 'T' تحمل وحدة / حزمة على نطاق واسع وليست محلية لأي وظيفة أو بنية (حسنًا ، ربما يكون إعلان نوع متداخل في هيكل يمكن الوصول إليه من الخارج).

لماذا لا تكتب بدلا من ذلك ؟:

fun<type T>(t T)

أو

fun[type T](t T)

علاوة على ذلك ، نحتاج إلى نوع من آلات الاستدلال لاستنتاج الأنواع الصحيحة عند استدعاء وظيفة عامة أو بنية بدون تخصص معلمة النوع في البداية.

كتب @ dc0d

ويمكن فقط استخدام الأنواع التي تحتوي على عامل + (أو - أو * ؛ يعتمد على ما إذا كان هذا العامل مستخدمًا في نص الحزمة على الإطلاق) كقيمة نوع تقع في معلمة النوع هذه.

هل يمكنك توضيح المزيد عن هذا؟

تضمين التغريدة

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

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

هل يمكنك توضيح المزيد عن هذا؟

على سبيل المثال ، إذا تم تعريف الاسم المستعار لنوع معلمة نوع الحزمة مثل:

package genericadd

type T = int

func Add(a, b T) T { return a + b }

بعد ذلك ، يمكن تعيين جميع الأنواع الرقمية تقريبًا إلى T ، مثل:

package main

import (
    "genericadd"
)

var add = genericadd.Add(
    T = float64
)

func main() {
    var (
        a, b float64
    )

    println(add(a, b))
}

@ dc0d

ومع ذلك ، لست متأكدًا مما إذا كانا متماثلان أو كيف.

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

أنا غير متاكد. هل هناك طرق أخرى لتطبيق الأدوية الجنيسة تدعم هذا؟

لغات أخرى ليس لديها مشكلة في ذلك. على وجه الخصوص ، C # هو الأكثر دراية بالنسبة لي. لكنه يستخدم إنشاء كود وقت التشغيل الذي يستبعده فريق Go تمامًا. تعمل Java أيضًا ولكن تنفيذها ليس الأفضل ، على أقل تقدير. يمكن لبعض مقترحات ianlancetaylor التعامل مع الحزم الثنائية فقط مما أفهمه.

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

فقط لاختبار فهمي ... ضع في اعتبارك هذا الريبو لخوارزميات النسخ / اللصق [ هنا ]. ما لم ترغب في استخدام "int" ، لا يمكن استخدام الكود مباشرة. يجب نسخها ولصقها وتعديلها للعمل. وبالتعديلات ، أعني أنه يجب تغيير كل مثيل من "int" إلى أي نوع تحتاجه حقًا.

أسلوب الاسم المستعار للنوع يقوم بإجراء التعديلات مرة واحدة ، على سبيل المثال T ، وإدراج سطر "type T int". ثم سيحتاج المترجم إلى إعادة ربط T بشيء آخر ، على سبيل المثال float64.

وبالتالي:
أ) أود أن أزعم أنه لن يكون هناك تباطؤ في المترجم ما لم تستخدم هذه التقنية بالفعل. لذلك هذا هو اختيارك.
ب) بالنظر إلى عناصر vgo الجديدة ، حيث يمكن استخدام إصدارات متعددة من نفس الكود ... مما يعني أنه يجب أن تكون هناك طريقة ما لإبعاد المصادر المستخدمة بعيدًا عن الأنظار ، فبالتأكيد يمكن للمجمع أن يتتبع ما إذا كان هناك اثنان يتم استخدام استخدامات نفس إعادة الربط وتجنب الازدواجية. لذلك أعتقد أن حجم الشفرة سيكون هو نفسه تقنيات النسخ / اللصق الحالية.

يبدو لي أنه بين الأسماء المستعارة و vgo القادمة ، تكاد أسس هذا النهج للأدوية الجنسية مكتملة ...

هناك بعض "الأشياء المجهولة" مدرجة في الاقتراح [ هنا ]. لذلك سيكون من اللطيف تجسيدها أكثر قليلاً.

mandolyte يمكنك إضافة مستوى آخر من المراوغة عن طريق تغليف الأنواع المتخصصة في بعض الحاويات العامة. بهذه الطريقة يمكن أن يظل تنفيذك كما هو. سيقوم المترجم بعد ذلك بعمل كل السحر. أعتقد أن اقتراح معلمات النوع إيان يعمل بهذه الطريقة.

أعتقد أن المستخدم يحتاج إلى الاختيار بين محو النوع و monomorphization.
هذا الأخير هو السبب في أن Rust يوفر تجريدًا بدون تكلفة. يجب أن تذهب أيضا.

يوم الإثنين ، 9 أبريل ، 2018 ، الساعة 8:32 صباحًا ، الساعة 8:32 صباحًا من Antonenko Artem [email protected]
كتب:

mandolyte https://github.com/mandolyte يمكنك إضافة مستوى آخر من
المراوغة عن طريق تغليف الأنواع المتخصصة في بعض الحاويات العامة. الذي - التي
الطريقة التي يمكن أن يظل تنفيذها كما هو. سيقوم المترجم بعد ذلك بعمل كل شيء
السحر. أعتقد أن اقتراح معلمات النوع إيان يعمل بهذه الطريقة.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-379735199 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AGGWB1v9h5kWmuHCBuoewTTSX751OHgrks5tm1TsgaJpZM4IG-xv
.

يبدو لي أن هناك ارتباكًا مفهومًا في هذه المناقشة حول المقايضة بين النمطية والأداء. تعد تقنية C ++ الخاصة بإعادة فحص النوع وإنشاء رمز عام في كل نوع يتم استخدامه من أجله سيئة للنمطية ، وسيئة للتوزيعات الثنائية ، وبسبب انتفاخ الكود ، فهي ضارة بالأداء. الجزء الجيد من هذا الأسلوب هو أنه يتخصص تلقائيًا في الكود الذي تم إنشاؤه للأنواع المستخدمة ، وهو أمر مفيد بشكل خاص عندما تكون الأنواع المستخدمة أنواعًا بدائية مثل int . تقوم Java بترجمة التعليمات البرمجية العامة بشكل متجانس ، ولكنها تدفع ثمنًا في الأداء ، خاصةً عندما تستخدم الشفرة النوع T[] .

لحسن الحظ ، هناك طريقتان لمعالجة هذا بدون نمطية C ++ وبدون إنشاء كود وقت التشغيل الكامل:

  1. إنشاء إنشاءات متخصصة للأنواع البدائية. يمكن القيام بذلك إما تلقائيًا أو عن طريق توجيه المبرمج. هناك حاجة إلى بعض الإرسال للوصول إلى إنشاء مثيل صحيح ، ولكن يمكن طيها في الإرسال المطلوب بالفعل بواسطة ترجمة متجانسة. سيعمل هذا بشكل مشابه لـ C # ، لكنه لا يتطلب إنشاء رمز وقت التشغيل الكامل ؛ قد يكون من المرغوب فيه القليل من الدعم الإضافي في وقت التشغيل لإعداد جداول الإرسال أثناء تحميل الكود.
  2. استخدم تطبيقًا عامًا واحدًا يتم فيه تمثيل مصفوفة من T فعليًا كمصفوفة من النوع البدائي عندما يتم إنشاء مثيل T كنوع بدائي. هذا النهج ، الذي استخدمناه في PolyJ و Genus و Familia ، يعمل على تحسين الأداء بشكل كبير بالنسبة إلى نهج Java ، على الرغم من أنه ليس بنفس سرعة التنفيذ المتخصص بالكامل.

@ dc0d

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

تعد الأسماء المستعارة للنوع طريقة خاطئة ، لأنها يجب أن تكون مرجعًا ثابتًا.
من الأفضل أن تكتب "T Type" مباشرة ثم ترى أنك تستخدم بالفعل الأدوية الجنيسة.

لماذا تريد استخدام متغير نوع عالمي 'T' للحزمة / الوحدة بأكملها ، فإن النوع المحلي vars في <> أو [] هو أكثر نمطية.

تضمين التغريدة

على وجه الخصوص ، C # هو الأكثر دراية بالنسبة لي. لكنه يستخدم إنشاء كود وقت التشغيل الذي يستبعده فريق Go تمامًا.

لأنواع المراجع ، ولكن ليس لأنواع القيم.

تضمين التغريدة

أعتقد أن المستخدم يحتاج إلى الاختيار بين محو النوع و monomorphization.
هذا الأخير هو السبب في أن Rust يوفر تجريدًا بدون تكلفة. يجب أن تذهب أيضا.

"Type Erasure" غامضة ، سأفترض أنك تعني محو معلمة النوع ، الشيء الذي توفره Java وهو أيضًا غير صحيح تمامًا.
Java لديها monomorphization ، لكنها أحادية الشكل (شبه) باستمرار إلى الحد الأعلى في القيد العام الذي يكون في الغالب كائن.
لتوفير أساليب وحقول من أنواع أخرى ، يتم تحويل الحد الأعلى داخليًا إلى النوع المناسب الذي يعتبر قبيحًا تمامًا.
إذا تم قبول مشروع Valhalla ، فستتغير الأشياء لأنواع القيم ولكن للأسف لن تتغير لأنواع المراجع.

Go ليس مضطرًا للذهاب إلى Java Way للأسباب التالية:

"التوافق الثنائي للحزم المترجمة غير مضمون بين الإصدارات"

في حين أن هذا غير ممكن في Java.

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

ما نوع الأداء الذي تتحدث عنه هنا؟

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

إذا كنت تقصد بكلمة "bloat code" و "performance" "حجم إدخال الرابط" و "وقت الربط" ، فإن المشكلة أيضًا واضحة إلى حد ما ، إذا كان بإمكانك وضع افتراضات معينة (معقولة) حول نظام الإنشاء الخاص بك. بدلاً من إصدار كل تخصص في كل وحدة ترجمة ، يمكنك بدلاً من ذلك إصدار قائمة بالتخصصات المطلوبة ، وجعل نظام الإنشاء يقوم بإنشاء مثيل لكل تخصص فريد بالضبط مرة واحدة قبل الارتباط ("نموذج Cfront"). IIRC ، هذه إحدى المشكلات التي تحاول وحدات C ++ النمطية معالجتها.

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


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

هذا في الواقع فشل في النمطية ، لكن لا علاقة له بنفخ الكود: بدلاً من ذلك ، يأتي من حقيقة أن أنواع وظائف (وطرق) Go لا تلتقط مجموعة كاملة كافية من القيود على حججها.

تضمين التغريدة

لأنواع المراجع ، ولكن ليس لأنواع القيم.

مما قرأته ، تقوم C # JIT بالتخصص في وقت التشغيل لكل نوع قيمة ومرة ​​واحدة لجميع أنواع المراجع. لا يوجد تخصص في وقت التحويل البرمجي (IL-time). وهذا هو سبب تجاهل نهج C # تمامًا - لا يرغب فريق Go في الاعتماد على إنشاء كود وقت التشغيل لأنه يحد من الأنظمة الأساسية التي يمكن تشغيل Go عليها. على وجه الخصوص ، في نظام iOS ، لا يُسمح لك بإنشاء رمز في وقت التشغيل. إنه يعمل وقد قمت بالفعل ببعض منه ولكن Apple لا تسمح به في AppStore.

كيف فعلتها؟

يوم الاثنين ، 9 أبريل ، 2018 ، 3:41 مساءً Antonenko Artem [email protected]
كتب:

sighoya https://github.com/sighoya

لأنواع المراجع ، ولكن ليس لأنواع القيم.

مما قرأته ، فإن C # JIT تخصص في وقت التشغيل لكل قيمة
اكتب ومرة ​​واحدة لجميع أنواع المراجع. ليس هناك وقت تجميع
تخصص. وهذا هو سبب تجاهل نهج C # تمامًا - فريق Go
لا تريد الاعتماد على إنشاء كود وقت التشغيل لأنه يحد من منصات Go
يمكن أن تعمل. على وجه الخصوص ، لا يُسمح لك بإنشاء رمز على نظام iOS
في وقت التشغيل. لقد نجحت وقد قمت بالفعل ببعضها ولكن Apple لا تسمح بذلك
في AppStore.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-379870005 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AGGWB-tslGeUSGXl2ZlEDLf0dCATUaYvks5tm7lvgaJpZM4IG-xv
.

أطلقت DemiMarie رمز البحث القديم الخاص بي فقط للتأكد (تم إسقاط هذا البحث لأسباب أخرى). مرة أخرى ، يضللني المصحح. أقوم بتخصيص صفحة ، وكتابة بعض التعليمات لها ، وحمايتها باستخدام PROT_EXEC والانتقال إليها. تحت المصحح يعمل. بدون تطبيق مصحح الأخطاء SIGKILLed برسالة CODESIGN في سجل الأعطال ، كما هو متوقع. لذلك ، لا يعمل حتى بدون AppStore. حجة أقوى ضد إنشاء كود وقت التشغيل إذا كان iOS مهمًا لـ Go.

أولاً ، سيكون من المفيد التفكير في قواعد البرمجة الخمس لروب بايك مرة أخرى.

الثانية (IMHO):

حول التجميع البطيء والحجم الثنائي ، كم عدد الأنواع العامة المستخدمة في الأنواع الشائعة من التطبيقات التي يتم تطويرها باستخدام Go (عادةً ما تكون صغيرة من القاعدة 3)؟ ما لم تكن المشكلة تحتاج إلى مستوى عالٍ من العلاقة الأساسية في المفاهيم الملموسة (عدد كبير من الأنواع) يمكن التغاضي عن النفقات العامة. حتى مع ذلك ، كنت أزعم أن هناك شيئًا ما خطأ في هذا النهج. عند تنفيذ نظام التجارة الإلكترونية ، لا أحد يحدد نوعًا منفصلاً لكل نوع من المنتجات واختلافاته وربما التخصيصات الممكنة.

الإسهاب هو شكل جيد من أشكال البساطة والألفة (على سبيل المثال في بناء الجملة) مما يجعل الأمور أكثر وضوحًا ونظافة. على الرغم من أنني أشك في أن حجم الشفرة سيكون أعلى باستخدام Type Alias ​​Rebinding ، إلا أنني أحب صياغة Go-ish المألوفة والإسهاب الواضح المصاحب لها. أحد أهداف Go هو سهولة القراءة (بينما أجد شخصيًا أنه من السهل نسبيًا وممتع الكتابة أيضًا).

لا أفهم كيف يمكن أن تضر الأداء لأنه في وقت التشغيل ، يتم استخدام أنواع محددة فقط والتي تم إنشاؤها في وقت الترجمة. لا يوجد وقت تشغيل إضافي.

قد يكون مصدر القلق الوحيد الذي أراه بخصوص إعادة ربط الاسم المستعار من النوع هو التوزيع الثنائي.

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

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

قد يكون مصدر القلق الوحيد الذي أراه بخصوص إعادة ربط الاسم المستعار من النوع هو التوزيع الثنائي.

هنا ما زلت غير متأكد. هل يجب أن يدعم اقتراح الأدوية الجنيسة الحزم الثنائية فقط أم يمكننا فقط أن نذكر أن الحزم الثنائية فقط لا تدعم الأدوية الجنيسة. سيكون أسهل بكثير ، هذا أمر مؤكد.

كما ذكرنا سابقًا ، إذا لم يكن المرء بحاجة إلى دعم التصحيح ، واحد
يمكن دمج عمليات إنشاء مثيل متطابقة.

يوم الثلاثاء ، 10 أبريل ، 2018 ، 5:46 صباحًا Kaveh Shahbazian [email protected]
كتب:

أولاً ، سيكون من المفيد التفكير في قواعد البرمجة الخمس لروب بايك
https://users.ece.utexas.edu/٪7Eadnan/pike.html مرة أخرى.

الثانية (IMHO):

حول التجميع البطيء والحجم الثنائي ، كم عدد الأنواع العامة المستخدمة في
الأنواع الشائعة من التطبيقات التي يتم تطويرها باستخدام Go ( n isعادة صغيرة من القاعدة 3)؟ ما لم تكن المشكلة بحاجة إلى مستوى عالٍ من
العلاقة الأساسية في المفاهيم الملموسة (عدد كبير من الأنواع) التي يمكن أن تحملها
يمكن التغاضي عنها. حتى مع ذلك ، كنت أزعم أن هناك شيئًا ما خطأ في ذلك
يقترب. عند تنفيذ نظام التجارة الإلكترونية ، لا أحد يعرّف ملفًا منفصلاً
اكتب لكل نوع من المنتجات وأشكاله المختلفة وربما الممكنة
التخصيصات.

الإسهاب هو شكل جيد من أشكال البساطة والألفة (على سبيل المثال في
النحو) مما يجعل الأمور أكثر وضوحًا ونظافة. بينما أشك في ذلك
سيكون رمز bloat أعلى باستخدام Type Alias ​​Rebinding ، فأنا أحب
بناء جملة Go-ish المألوف والإسهاب الواضح المصاحب له. واحد من
من السهل قراءة أهداف Go (بينما أجدها شخصيًا
سهل نسبيًا وممتع للكتابة أيضًا).

لا أفهم كيف يمكن أن يضر بالأداء لأنه في وقت التشغيل فقط
يتم استخدام أنواع محددة من الخرسانة التي تم إنشاؤها في
وقت الترجمة. لا يوجد وقت تشغيل إضافي.

قد يكون مصدر القلق الوحيد الذي أراه بخصوص إعادة ربط الاسم المستعار من النوع الثنائي هو النظام الثنائي
توزيع.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-380040032 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AGGWB6aDfoHz2wbsmu8mCGEt652G_VE9ks5tnH9xgaJpZM4IG-xv
.

لا تحتاج عمليات إنشاء مثيل إلى أن تكون "متطابقة" بمعنى "استخدام نفس الحجج" ، أو حتى "استخدام وسيطات من نفس النوع الأساسي". هم فقط بحاجة إلى أن يكونوا قريبين بدرجة كافية لينتجوا نفس الكود الذي تم إنشاؤه. (بالنسبة إلى Go ، يشير ذلك أيضًا إلى "أقنعة المؤشر نفسها".)

تضمين التغريدة

مما قرأته ، تقوم C # JIT بالتخصص في وقت التشغيل لكل نوع قيمة ومرة ​​واحدة لجميع أنواع المراجع. لا يوجد تخصص في وقت التحويل البرمجي (IL-time).

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

أعتقد أن النظام العام لـ c # سيكون جيدًا إذا قمنا بدلاً من ذلك بإنشاء رمز في وقت الترجمة.
لا يمكن إنشاء كود وقت التشغيل بمعنى c # مع go ، لأن go ليس vm.

@ dc0d

قد يكون مصدر القلق الوحيد الذي أراه بخصوص إعادة ربط الاسم المستعار من النوع هو التوزيع الثنائي.

هل يمكنك التفصيل قليلا.

sighoya خطأي ؛ لم أقصد التوزيع الثنائي ولكن الحزم الثنائية - والتي شخصياً ليس لدي أي فكرة عن مدى أهميتها.

تضمين التغريدة (MO) ما لم يتم العثور على سبب قوي ، يجب تجنب أي شكل من التحميل الزائد على بنيات لغة Go. أحد أسباب استخدام Type Alias ​​Rebinding هو تجنب التحميل الزائد للأنواع المركبة المضمنة مثل الشرائح أو الخرائط.

الإسهاب هو شكل جيد من أشكال البساطة والألفة (على سبيل المثال في بناء الجملة) مما يجعل الأمور أكثر وضوحًا ونظافة. على الرغم من أنني أشك في أن حجم الشفرة سيكون أعلى باستخدام Type Alias ​​Rebinding ، إلا أنني أحب صياغة Go-ish المألوفة والإسهاب الواضح المصاحب لها. أحد أهداف Go هو سهولة القراءة (بينما أجد شخصيًا أنه من السهل نسبيًا وممتع الكتابة أيضًا).

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

هناك ثلاث فئات من الأسماء يجب أن نبتكرها يوميًا:

  • لكيانات المجال / المنطق
  • أنواع بيانات سير عمل البرنامج / المنطق
  • الخدمات / أنواع البيانات المتداخلة / المنطق

كم مرة نجح مبرمج في تجنب تسمية أي شيء في الكود الخاص به؟

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

وتصبح الأسماء أكثر قوة عندما تصاحب ممارسة هيكلة الكود ؛ من حيث تخطيط الكود (ملف ، هيكل الدليل ، الحزم / الوحدات) والممارسات (أنماط التصميم ، تجريدات الخدمة - مثل REST ، إدارة الموارد - البرمجة المتزامنة ، الوصول إلى القرص الصلب ، الإنتاجية / زمن الوصول).

بالنسبة إلى النحو والإسهاب ، فأنا أفضل الإسهاب على الإيجاز الذكي (على الأقل في سياق Go) - مرة أخرى ، يُقصد بـ Go أن يكون سهل القراءة ، وليس بالضرورة من السهل كتابته (وهو أمر أجده جيدًا بشكل غريب في ذلك أيضًا) .

قرأت الكثير من تقارير الخبرة والاقتراحات حول سبب وكيفية تنفيذ الأدوية الجنيسة في Go.

هل تمانع إذا حاولت تنفيذها فعليًا في مترجم Go interpreter gomacro ؟

لدي بعض الخبرة في هذا الموضوع ، بعد أن أضفت أدوية عامة إلى لغتين في الماضي

  1. لغة مهجورة الآن قمت بإنشائها عندما كنت ساذجًا :) تم تحويلها إلى شفرة مصدر C
  2. Lisp المشترك مع أنواع cl-parametric الخاصة بمكتبتي - كما أنه يدعم التخصصات الجزئية والكاملة للأنواع والوظائف العامة

@ cosmos72 سيكون تقرير تجربة لطيف لرؤية نموذج أولي لتقنية تحافظ على سلامة النوع.

بدأت للتو العمل عليها. يمكنك متابعة التقدم على https://github.com/cosmos72/gomacro/tree/generics-v1

في الوقت الحالي ، أبدأ بمزيج (معدل قليلاً) من اقتراح إيان الثالث والرابع المدرج في https://github.com/golang/proposal/blob/master/design/15292-generics.md#Proposal

@ cosmos72 يوجد ملخص للمقترحات على الرابط أدناه. هل مزيجك واحد منهم؟
https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX4pi-HnNjkMEgyAHX4N4

لقد قرأت هذا المستند ، فهو يلخص العديد من الأساليب المختلفة للأدوية من خلال لغات البرمجة المختلفة.

في الوقت الحالي ، أنا ذاهب نحو تقنية "نوع التخصص" التي تستخدمها C ++ و Rust وغيرهما ، ربما مع القليل من "نطاقات القوالب المعلمة" لأن بناء الجملة الأكثر شيوعًا للأنواع الجديدة هو type ( Foo ...; Bar ...) وأنا أقوم بتوسيع إلى template[T1,T2...] type ( Foo ...; Bar ...) .
كما أنني أبقي الباب مفتوحًا لـ "التخصص المقيد".

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

المزيج الذي كنت أشير إليه هو بين https://github.com/golang/proposal/blob/master/design/15292/2013-10-gen.md و https://github.com/golang/proposal/blob/ ماجستير / تصميم / 15292 / 2013-12-type-params.md

تحديث: لتجنب إرسال رسائل غير مرغوب فيها إلى مشكلة Go الرسمية هذه بعد الإعلان الأولي ، فمن الأفضل مواصلة المناقشة الخاصة بـ gomacro في قضية gomacro رقم 24: إضافة أدوية عامة

التحديث 2: تم تجميع وظائف النموذج الأولى وتنفيذها بنجاح. انظر https://github.com/cosmos72/gomacro/tree/generics-v1

فقط للتسجيل ، من الممكن إعادة صياغة رأيي (حول الأدوية الجنيسة و Type Alias ​​Rebinding):

يجب إضافة العوامل العامة كميزة مترجم (إنشاء رمز ، قوالب ، إلخ) ، وليس ميزة لغة (التدخل في نظام نوع Go على جميع المستويات).

@ dc0d
لكن هل قوالب C ++ ليست ميزة مترجم ولغة؟

sighoya كانت آخر مرة كتبت فيها C ++ بشكل احترافي حوالي عام 2001. لذلك قد أكون مخطئًا. لكن بافتراض أن دلالات التسمية دقيقة - جزء "النموذج" - نعم (أو بالأحرى لا) ؛ قد تكون ميزة مترجم (وليست ميزة لغة) ، مصحوبة ببعض تراكيب اللغة ، والتي على الأرجح لا تفرط في تحميل أي بنيات لغة متضمنة في نظام الكتابة.

أنا أؤيد @ dc0d. إذا كنت تفكر في ذلك ، فلن تكون هذه الميزة أكثر من مولد رمز متكامل.

نعم: قد يزداد الحجم الثنائي وسيزداد ، لكننا نستخدم الآن مولدات الأكواد ، وهي متشابهة إلى حد كبير ولكن كميزة خارجية. إذا كان لا بد لي من إنشاء النموذج الخاص بي على النحو التالي:

type BinaryTreeOfStrings struct {
    left, right *BinaryTreeOfStrings;
    string content;
}

// Its methods here

type BinaryTreeOfBigInts struct {
    left, right *BinaryTreeOfBigInts;
    uint64 content;
}

// AGAIN the same methods but different type

... أرغب بشدة في ذلك ، بدلاً من النسخ أو استخدام أداة خارجية ، تصبح هذه الميزة جزءًا من المترجم نفسه.

يرجى الملاحظة:

  • نعم ، سيتم تكرار رمز النهاية. كما لو كنا نستخدم مولد. والثنائي سيكون أكبر.
  • نعم ، الفكرة ليست أصلية ولكنها مستعارة من C ++.
  • نعم ، وظائف MyTypeلا يتضمن أي شيء من النوع T (بشكل مباشر أو غير مباشر) سيتكرر أيضًا. يمكن تحسين ذلك (على سبيل المثال ، الطرق التي تحيل شيئًا من النوع T - أكثر من المؤشر إلى كائن استقبال الرسائل - سيتم إنشاؤه لكل T ؛ الطرق التي تحتوي على استدعاءات للطرق التي من شأنها يتم إنشاؤها لكل T ، سيتم إنشاؤها أيضًا لكل T ، بشكل متكرر - بينما الطرق التي يكون مرجعها الوحيد إلى T هو *T في المتلقي ، وغيرها من الطرق التي تستدعي فقط تلك الأساليب الآمنة وتفي بالمعايير نفسها ، يمكن إجراؤها مرة واحدة فقط). على أي حال ، IMO هذه النقطة كبيرة وأقل دقة: سأكون سعيدًا جدًا حتى لو لم يكن هذا التحسين موجودًا.
  • يجب أن تكون حجج النوع صريحة في رأيي. خاصة عندما يلبي كائن ما واجهات يحتمل أن تكون غير محدودة. مرة أخرى: منشئ الكود.

حتى الآن في تعليقي ، اقتراحي هو تنفيذه كما هو: كمولد كود مدعوم من مترجم ، بدلاً من أداة خارجية.

سيكون من المؤسف أن تتبع Go مسار C ++. ينظر الكثير من الناس إلى نهج C ++ على أنه فوضى حوّلت المبرمجين ضد الفكرة الكاملة للأدوية العامة: صعوبة تصحيح الأخطاء ، وعدم وجود نمطية ، وانتفاخ الكود. جميع حلول "منشئ الكود" هي في الحقيقة مجرد بدائل ماكرو - إذا كانت هذه هي الطريقة التي تريد بها كتابة التعليمات البرمجية ، فلماذا نحتاج حتى إلى دعم المترجم؟

andrewcmyers كان لدي هذا الاقتراح اكتب اسم مستعار Rebinding والذي نكتب فيه حزمًا عادية فقط وبدلاً من استخدام interface{} بشكل صريح نستخدمه فقط كـ type T = interface{} كمعامل عام على مستوى الحزمة. و هذا كل شيء.

  • نقوم بتصحيحه مثل الحزمة العادية - إنه رمز فعلي ، وليس كائنًا متوسط ​​العمر نصف العمر.
  • ليست هناك حاجة للتدخل في نظام النوع Go على جميع المستويات - فكر في إمكانية التخصيص وحدها.
  • إنه صريح. لا موجو مخفي. بالطبع قد يجد المرء عدم القدرة على ربط المكالمات العامة بسلاسة ، وهو عيب. أنا أراه كتعادل للأمام! تغيير النوع في مكالمتين متتاليتين ، في بيان واحد ليس Goish (IMO).
  • وأفضل ما في الأمر أنه متوافق مع الإصدارات السابقة مع سلسلة Go 1.x (x> = 8).

في حين أن الفكرة ليست جديدة ، فإن الطريقة التي يسمح بها Go بتنفيذها عملية وواضحة.

مكافأة إضافية: لا يوجد حمل زائد على المشغل في Go. ولكن من خلال تحديد القيمة الافتراضية للاسم المستعار من النوع (على سبيل المثال) type T = int ، ليست الأنواع الصالحة الوحيدة التي يمكن استخدامها لتخصيص هذه الحزمة العامة ، فهي أنواع رقمية لها تنفيذ داخلي لـ + عامل التشغيل

كما يمكن إجبار معلمة نوع الاسم المستعار على تلبية أكثر من واجهة واحدة فقط عن طريق إضافة بعض أنواع وعبارات المدقق.

الآن ، سيكون ذلك قبيحًا للغاية باستخدام أي تدوين صريح لنوع عام يحتوي على معلمة تنفذ واجهات Error و Stringer وهو أيضًا نوع رقمي يدعم عامل التشغيل + !

في الوقت الحالي ، نستخدم مولدات الأكواد ، وهي متشابهة إلى حد ما ولكنها ميزة خارجية.

يتمثل الاختلاف في أن الطريقة المقبولة على نطاق واسع للقيام بإنشاء الكود (عبر go generate ) تحدث في وقت الالتزام / التطوير ، وليس في وقت الترجمة. إن القيام بذلك في وقت الترجمة يعني أنك بحاجة إلى السماح بتنفيذ التعليمات البرمجية التعسفية في المترجم ، وقد تقوم المكتبات بتفجير أوقات التجميع حسب أوامر الحجم و / أو سيكون لديك تبعيات بناء منفصلة (على سبيل المثال ، لم يعد من الممكن إنشاء الكود فقط باستخدام Go أداة). أحب Go لدفع استدعاء البرمجة الوصفية إلى مطور المنبع.

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

سأقرأ كل الأشياء المذكورة أعلاه ، أعدك ، ومع ذلك سأضيف القليل أيضًا - يبدو GoLang SDK لـ Apache Beam مثالًا ساطعًا إلى حد ما / عرض المشاكل التي يجب على مصمم المكتبة تحملها لسحب أي شيء _ بشكل صحيح_ من المستوى العالي.

يوجد على الأقل تطبيقان تجريبيان لـ Go Genics. في وقت سابق من هذا الأسبوع قضيت بعض الوقت مع (1). كان من دواعي سروري أن أجد أن تأثير قابلية القراءة للشفرة كان ضئيلاً. ووجدت أن استخدام الوظائف المجهولة لتقديم اختبارات المساواة يعمل بشكل جيد ؛ لذلك أنا مقتنع بأن التحميل الزائد للمشغل غير ضروري. المشكلة الوحيدة التي وجدتها كانت في معالجة الأخطاء. المصطلح الشائع لـ "return nil، err" لن يعمل إذا كان النوع ، على سبيل المثال ، عددًا صحيحًا أو سلسلة. هناك عدد من الطرق للتغلب على هذا الأمر ، وكل ذلك بتكلفة معقدة. قد أكون غريبًا بعض الشيء ، لكني أحب معالجة أخطاء Go. هذا يقودني إلى ملاحظة أن حل Go Genics يجب أن يحتوي على كلمة رئيسية عامة للقيمة الصفرية لنوع ما. سوف يستبدلها المترجم ببساطة بصفر للأنواع العددية ، وسلسلة فارغة لأنواع السلسلة ، ولا شيء للبنى.

في حين أن هذا التنفيذ لم يفرض نهجًا على مستوى الحزمة ، سيكون من الطبيعي بالتأكيد القيام بذلك. وبالطبع ، لم يعالج هذا التطبيق جميع التفاصيل الفنية حول المكان الذي يجب أن تذهب إليه التعليمات البرمجية المحسّنة للمترجم (إذا كان في أي مكان) ، وكيف ستعمل مصححات الكود ، وما إلى ذلك.

كان من الجيد جدًا استخدام نفس رمز الخوارزمية للأعداد الصحيحة وشيء مثل النقطة:

type Point struct {
    x,y int
}

انظر (2) لاختباري وملاحظاتي.

(1) https://github.com/albrow/fo ؛ والآخر هو https://github.com/cosmos72/gomacro#generics المذكور أعلاه
(2) https://github.com/mandolyte/fo-experiments

mandolyte يمكنك استخدام *new(T) للحصول على القيمة الصفرية من أي نوع.

بناء لغة مثل الافتراضي (T) أو الصفر (T) (أول واحد هو واحد
في C # IIRC) سيكون واضحًا ، لكن OTOH أطول من * جديد (T) (على الرغم من أن أكثر
مؤدي).

2018-07-06 9:15 GMT-05: 00 Tom Thorogood [email protected] :

mandolyte https://github.com/mandolyte يمكنك استخدام * new (T) للحصول على ملف
قيمة صفرية من أي نوع.

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-403046735 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AlhWhQ5cQwnc3x_XUldyJXCHYzmr6aN3ks5uD3ETgaJpZM4IG-xv
.

-
هذا اختبار لتوقيعات البريد لاستخدامها في TripleMint

19642 لمناقشة القيمة الصفرية العامة

tmthrgd بطريقة ما فاتني تلك الحكاية الصغيرة. شكرا!

مقدمة

علم الوراثة يدور حول تخصص التركيبات القابلة للتخصيص. ثلاث فئات من التخصص هي:

  • أنواع التخصص ، Type<T> - an _array_؛
  • تخصص الحسابات ، F<T>(T) أو F<T>(Type<T>) - صفيف قابل للفرز_ ؛
  • تدوين متخصص ، _LINQ_ على سبيل المثال - كشوفات حساب select أو for في Go؛

بالطبع هناك لغات برمجة تقدم تركيبات أكثر عمومية. لكن لغات البرمجة التقليدية مثل _C ++ _ أو _C # _ أو _Java_ توفر تركيبات لغوية أكثر أو أقل تقتصر على هذه القائمة.

خواطر

يجب أن تكون الفئة الأولى من الأنواع / التركيبات العامة من النوع الحيادي.

تحتاج الفئة الثانية من الأنواع / التركيبات العامة إلى _act_ عند _property_ لمعلمة النوع. على سبيل المثال ، يجب أن تكون المصفوفة القابلة للترتيب قادرة على _ مقارنة_ الخاصية القابلة للمقارنة_ لعناصرها. بافتراض أن T.(P) خاصية T و A(T.(P)) هي عملية حساب / إجراء يعمل على تلك الخاصية ، يمكن تطبيق (A, .(P)) إما على كل عنصر فردي أو يتم الإعلان عنها كعملية حسابية متخصصة ، يتم تمريرها إلى الحساب الأصلي القابل للتخصيص. مثال على الحالة الأخيرة في Go هو واجهة sort.Interface التي تحتوي أيضًا على الوظيفة المنفصلة المقابلة sort.Reverse .

الفئة الثالثة من الأنواع / التركيبات العامة هي تدوينات لغوية من النوع المتخصص - يبدو أنها ليست شيئًا من نوع Go _ بشكل عام_.

أسئلة

يتبع ...

أي ملاحظات وصفية أكثر من الرموز التعبيرية هي موضع ترحيب كبير!

@ dc0d أوصي بدراسة "عناصر البرمجة" لسيبانوف قبل محاولة تعريف علم الوراثة. TL ؛ DR هو أننا نكتب كودًا ملموسًا لتبدأ به ، على سبيل المثال ، خوارزمية تقوم بفرز مصفوفة. لاحقًا نضيف أنواعًا أخرى من المجموعات مثل Btree ، وما إلى ذلك. لاحظنا أننا نكتب نسخًا عديدة من خوارزمية الفرز التي هي في الأساس نفسها ، لذلك نحدد مفهومًا ما ، على سبيل المثال "قابل للفرز". الآن نريد تصنيف خوارزميات الفرز ، ربما عن طريق نمط الوصول الذي تتطلبه ، لنقل فقط ، مرور واحد (دفق) ، إعادة توجيه عدة مرات فقط (قائمة مرتبطة بشكل فردي) ، ثنائية الاتجاه (قائمة مرتبطة بشكل مزدوج) ، وصول عشوائي ( مجموعة مصفوفة). عندما نضيف نوع مجموعة جديد ، نحتاج فقط إلى تحديد فئة "التنسيق" التي تقع ضمنها للوصول إلى جميع خوارزميات الفرز ذات الصلة. تشبه فئات الخوارزميات هذه إلى حد كبير واجهات "Go". كنت أتطلع إلى توسيع الواجهات في Go لدعم معلمات متعددة الأنواع ، وأنواع مجردة / مرتبطة. لا أعتقد أن الوظائف تحتاج إلى معلمات من النوع المخصص.

@ dc0d كمحاولة لتقسيم الأدوية الجنيسة إلى أجزاء مكونة لم أفكر في 3 ، "تدوين متخصص" ، كجزء منفصل خاص بها من قبل. ربما يمكن وصفه بأنه تعريف DSLs من خلال استخدام قيود النوع.

قد أزعم أن 1 و 2 هما "هياكل بيانات" و "خوارزميات" ، على التوالي. باستخدام هذه المصطلحات ، يكون من الواضح بعض الشيء سبب صعوبة الفصل بينها بوضوح ، نظرًا لأنها غالبًا ما تعتمد بشكل كبير على بعضها البعض. لكن Sort.Interface هو مثال جيد على المكان الذي يمكنك فيه رسم خط بين التخزين والسلوك (مع القليل من السكر الحديث لجعله أجمل) ، لأنه يشفر المتطلبات القابلة للفهرسة والمقارنة في الحد الأدنى من السلوك المطلوب لتنفيذ خوارزمية الفرز مع "مبادلة" و "أقل" (و لين). ولكن يبدو أن هذا ينقسم إلى هياكل بيانات أكثر تعقيدًا مثل الأشجار أو الأكوام ، وكلاهما يتطلب حاليًا بعض الالتواءات لتكوين سلوك خالص مثل واجهات Go.

يمكنني تخيل إضافة أدوية عامة صغيرة نسبيًا إلى الواجهات (أو غير ذلك) والتي يمكن أن تسمح بتنفيذ معظم هياكل بيانات الكتب المدرسية والخوارزميات بشكل نظيف نسبيًا دون أي تشوهات (مثل sort.Interface اليوم) ، ولكنها ليست قوية بما يكفي لتصميم DSLs. ما إذا كنا نريد حصر أنفسنا في مثل هذا التنفيذ المقيد للأدوية عندما نواجه كل مشكلة إضافة الأدوية الجنيسة على الإطلاق هو سؤال مختلف.

إن هياكل إحداثيات infogulch للأشجار الثنائية هي "إحداثيات متشعبة" ، وتوجد مكافئات للأشجار الأخرى. ومع ذلك ، يمكنك أيضًا عرض ترتيب الشجرة من خلال أحد الطلبات الثلاثة ، الطلب المسبق ، والترتيب ، والترتيب اللاحق. بعد اتخاذ قرار بشأن أحد هذه ، يمكن معالجة الشجرة على أنها إحداثيات ثنائية الاتجاه ، وستكون عائلة خوارزميات الفرز المحددة في إحداثيات ثنائية الاتجاه فعالة على النحو الأمثل.

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

(IMHO)

تضمين التغريدة

ربما يمكن وصفه بأنه تعريف DSLs من خلال استخدام قيود النوع

أنت محق. ولكن نظرًا لأنها جزء من مجموعة بنيات اللغة ، فإن تسمية IMO لها بـ DSL سيكون غير دقيق بعض الشيء.

1 و 2 ... غالبًا ما يعتمدان بشكل كبير

مرة أخرى صحيح. ولكن هناك العديد من الحالات التي يلزم فيها تمرير نوع الحاوية ، بينما لم يتم تحديد الاستخدام الفعلي بعد - في تلك المرحلة في البرنامج. لهذا السبب هناك حاجة إلى 1 ليتم دراستها بمفردها.

يُعد sort.Interface مثالًا جيدًا على المكان الذي يمكنك فيه رسم خط بين _storage_ و_السلوك_

احسنت القول؛

يبدو أن هذا ينهار على هياكل البيانات الأكثر تعقيدًا

هذا أحد أسئلتي: لتعميم معلمة النوع ووصفها من حيث القيود (مثل List<T> where T:new, IDisposable ) أو تقديم بروتوكول _ بروتوكول معمم ينطبق على جميع العناصر (من مجموعة ؛ من نوع معين)؟

تضمين التغريدة

يصبح السؤال أيًا من تصنيفات أنماط الوصول إلى البيانات المتاحة لخوارزميات الفرز هو الأمثل لهياكل البيانات لديك

صحيح. الوصول عن طريق الفهرس هو _ خاصية _ للشريحة (أو المصفوفة). لذا فإن المطلب الأول للحاوية القابلة للفرز (أو الحاوية القابلة للفرز _ Tree_ مهما كانت خوارزمية _tree_) هو توفير أداة مساعدة _ access & mutate (swap) _. الشرط الثاني هو أن العناصر يجب أن تكون قابلة للمقارنة. هذا هو الجزء المربك (بالنسبة لي) حول ما تسميه الخوارزميات: يجب تلبية المتطلبات على كلا الجانبين (في الحاوية وفي معلمة النوع). هذه هي النقطة التي لا أستطيع أن أتخيلها تنفيذًا عمليًا للأدوية الجنيسة في Go. يمكن وصف كل جانب من جوانب المشكلة بمصطلح الواجهات بشكل مثالي. ولكن كيف تجمع بين هذين الاثنين في تدوين فعال؟

تتطلب خوارزميات @ dc0d واجهات ، وتوفرها هياكل البيانات. هذا يكفي للتعميم الكامل ، شريطة أن تكون الواجهات قوية بما فيه الكفاية. يتم تحديد واجهات المعلمات حسب الأنواع ، لكنك تحتاج إلى متغيرات النوع.

بأخذ مثال "الفرز" ، فإن "Ord" هي خاصية من النوع المخزن في الحاوية ، وليس الحاوية نفسها. نمط الوصول هو خاصية للحاوية. تطبيقات الوصول البسيطة هي "مكررات" ، ولكن هذا الاسم يأتي من C ++ ، يفضل ستيبانوف "الإحداثيات" حيث يمكن تطبيقها على حاويات متعددة الأبعاد أكثر تعقيدًا.

في محاولة لتعريف الفرز ، نريد شيئًا كهذا:

bubble_sort : forall T U I => T U -> T U requires
   ForwardIterator<T>, Readable<T>, Writable<T>,
   Ord<U>,  ValueType(T) == U, Distance type(T) == I

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

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

urandom أنت تجرد الخوارزميات وليس المجموعات. لذلك تسأل ، ما هي أنماط الوصول إلى البيانات الموجودة في خوارزميات "الفرز" ، ثم تصنيفها. لذلك لا يهم ما إذا كانت الحاوية عبارة عن "شريحة" لا نحاول تحديد جميع العمليات التي قد ترغب في إجرائها على شريحة ، فنحن نحاول تحديد متطلبات الخوارزمية واستخدامها لتعريف الواجهة. الشريحة ليست خاصة ، إنها مجرد نوع T يمكننا تحديد مجموعة من العمليات عليه.

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

تضمين التغريدة
اعتقدت أن هذا ما قصدته. ولكن في سياق Go ، قد يعني ذلك أنه قد تكون هناك حاجة إلى إصلاح كبير لما يمكن أن تحدده الواجهات. أتخيل أن العديد من العمليات المضمنة ، مثل التكرارات ، أو المشغلين ، يجب أن يتم كشفها عبر الطرق من أجل جعل أشياء مثل sort.Slice أو math.Max عامة عبر الواجهات.

لذلك يجب أن تحصل على دعم للواجهة التالية (رمز زائف):

type [T] OrderedIterator interface {
   Len() int
   ValueAt(i int) *T
}

...
package sort

func [T] Slice(s [T]OrderedIterator, func(i, j int) bool) {
   ...
}

وجميع الشرائح سيكون عندها هذه الأساليب؟

urandom إن المكرر ليس تجريدًا لمجموعة ، ولكنه تجريد للإشارة / المؤشر في مجموعة. على سبيل المثال ، يمكن أن يكون لمكرر إعادة التوجيه طريقة واحدة "لاحقة" (أحيانًا "التالية"). إن القدرة على الوصول إلى البيانات في موقع المكرر ليست خاصية للمكرر (وإلا فسوف ينتهي بك الأمر مع نكهات القراءة / الكتابة / القابلة للتغيير للمكرر). من الأفضل تحديد "المراجع" بشكل منفصل كواجهات قابلة للقراءة وقابلة للكتابة وقابلة للتغيير:

type T ForwardIterator interface {
   type DistanceType D
   successor(x T) T
}

type T Readable interface {
   type ValueType U 
   source(x T) U
}

ملاحظة: النوع "T" ليس الشريحة ، ولكنه نوع المكرر على الشريحة. قد يكون هذا مجرد مؤشر عادي ، إذا اعتمدنا أسلوب C ++ لتمرير مكرر البداية والنهاية لوظائف مثل الفرز.

بالنسبة إلى مكرر الوصول العشوائي ، سننتهي بشيء مثل:

type T RandomIterator interface {
   type DistanceType D
   setPosition(x DistanceType)
}

لذا فإن المكرر / الإحداثيات هو تجريد للإشارة إلى مجموعة ، وليس المجموعة نفسها. يعبر اسم "الإحداثيات" عن هذا بشكل جيد ، إذا كنت تعتقد أن المكرر هو الإحداثي والمجموعة كخريطة.

ألا نبيع Go Short من خلال عدم الاستفادة من إغلاق الوظائف والوظائف المجهولة؟ يمكن أن يساعد وجود وظائف / طرق كنوع من الدرجة الأولى في Go. على سبيل المثال ، باستخدام بناء الجملة من albrow / fo ، قد يبدو فرز الفقاعة كما يلي:

type SortableContainer[C,T] struct {
    Less func(C,T,T) bool
    Swap func(C,int,int)
    Next func(C) (T,bool)
}

func (bs *SortableContainer[C,T]) BubbleSort(container C, e1,e2 T) {    
    swapCount := 1
    var item1, item2 T
    item1, ok1 = bs.Next()
    if !ok1 {return}
    item2, ok2 = bs.Next()
    if !ok2 {return}
    for swapCount > 0 {
        swapCount = 0
        for {
            if Less(item2, item1) { 
                bs.Swap(C,item2,item1)
                swapCount += 1
            }
        }
    }
}

يرجى التغاضي عن أي أخطاء ... لم تختبر على الإطلاق!

mandolyte لست متأكدًا مما إذا كان هذا قد تم توجيهه إلي؟ لا أرى حقًا أي فرق بين ما كنت أقترحه ومثالك ، باستثناء أنك تستخدم واجهات متعددة المعلمات ، وكنت أعطي أمثلة باستخدام أنواع مجردة / مرتبطة. لكي أكون واضحًا ، أعتقد أنك بحاجة إلى واجهات متعددة المعلمات وأنواع مجردة / مرتبطة بالعمومية الكاملة ، ولا يدعم أي منهما حاليًا Go.

أود أن أقترح أن تكون واجهاتك أقل عمومية من تلك التي اقترحتها ، لأنها تربط ترتيب الفرز ونمط الوصول وإمكانية الوصول في نفس الواجهة ، والتي ستؤدي بالطبع إلى تكاثر الواجهات ، على سبيل المثال أمران (أقل ، أكبر) ، ثلاثة أنواع وصول (للقراءة فقط ، للكتابة فقط ، قابلة للتغيير) وخمسة أنماط وصول (تمرير إلى الأمام ، تمرير متعدد ، ثنائي الاتجاه ، مفهرس ، عشوائي) تؤدي إلى 36 واجهة مقارنة بـ 11 فقط إذا كانت المخاوف منفصلة.

يمكنك تحديد الواجهات التي أقترحها باستخدام واجهات متعددة المعلمات بدلاً من الأنواع المجردة مثل هذا:

type I ForwardIterator interface {
   successor(x I) I
}
type R V Readable interface {
   source(x R) V
}
type V Ord interface {
   less(x V, y V) : bool
}

لاحظ أن الوحيد الذي يحتاج إلى معلمتين من النوع هو الواجهة القابلة للقراءة. ومع ذلك ، فإننا نفقد هذه القدرة على "احتواء" كائن مكرر على نوع الكائنات التي يتم تكرارها ، وهي مشكلة كبيرة حيث يتعين علينا الآن تحريك نوع "القيمة" في نظام الكتابة ، وعلينا تصحيحه . وهذا يؤدي إلى تكاثر معلمات النوع وهو أمر غير جيد ويزيد من احتمال حدوث أخطاء في الترميز. نفقد أيضًا القدرة على تحديد "DistanceType" في المكرر ، وهو أصغر نوع رقم ضروري لحساب العناصر في المجموعة ، وهو أمر مفيد للتعيين إلى int8 و int16 و int32 وما إلى ذلك ، لإعطاء النوع الذي تحتاجه عد العناصر دون تجاوز.

يرتبط هذا ارتباطًا وثيقًا بمفهوم "الاعتماد الوظيفي". إذا كان النوع يعتمد وظيفيًا على نوع آخر ، فيجب أن يكون نوعًا مجردًا / مرتبطًا. فقط إذا كان النوعان مستقلين ، يجب أن يكونا معلمات نوع منفصلة.

بعض المشاكل:

  1. لا يمكن استخدام صيغة f (x I) الحالية للواجهات متعددة المعلمات. لا أحب أن بناء الجملة هذا يخلط بين الواجهات (وهي قيود على الأنواع) والأنواع على أي حال.
  2. ستكون هناك حاجة إلى طريقة للإعلان عن الأنواع ذات المعلمات.
  3. قد تكون هناك حاجة إلى طريقة للإعلان عن الأنواع المرتبطة بالواجهات مع مجموعة معينة من معلمات النوع.

keean لست متأكدًا من فهمي لكيفية أو سبب ارتفاع عدد الواجهات. فيما يلي مثال عملي كامل: https://play.folang.org/p/BZa6BdsfBgZ (يعتمد على شريحة ، وليس حاوية عامة ، وبالتالي لا حاجة إلى طريقة Next ()).

إنه يستخدم بنية نوع واحد فقط ، ولا توجد واجهات على الإطلاق. يجب أن أقدم جميع الوظائف والإغلاقات المجهولة (ربما يكون هذا هو المكان الذي تكون فيه المقايضة؟). يستخدم المثال نفس خوارزمية فرز الفقاعة لفرز شريحة من الأعداد الصحيحة وشريحة من نقاط "(x ، y)" ، حيث تكون المسافة من الأصل هي أساس الدالة Less ().

على أي حال ، كنت آمل أن أوضح كيف يمكن أن تساعد الوظائف في نظام الكتابة.

mandolyte أعتقد أنني أسأت فهم ما كنت تقترحه. أرى أن ما تتحدث عنه هو "folang" الذي يحتوي بالفعل على بعض ميزات البرمجة الوظيفية اللطيفة المضافة إلى Go. ما قمت بتنفيذه هو في الأساس سباكة فئة نوع متعدد المعلمات يدويًا. أنت تقوم بتمرير ما يعرف بقاموس الوظائف إلى وظيفة الفرز. يقوم هذا بشكل صريح بما ستفعله الواجهة بشكل ضمني. ربما تكون هناك حاجة إلى هذا النوع من الميزات قبل الواجهات متعددة المعلمات والأنواع المرتبطة بها ، لكنك تواجه في النهاية مشكلات في تمرير كل هذه القواميس. أعتقد أن الواجهات توفر تعليمات برمجية أكثر وضوحًا وأكثر قابلية للقراءة.

يعد فرز شريحة مشكلة محلولة. هذا هو الكود الخاص بشريحة quicksort.go التي تم تنفيذها باستخدام لغة go-li (تحسين golang) .

func main(){
    var data = []int{5,3,1,8,9}

    Sort(data, func(a *int, b *int) int {
        return *a - *b
    })

    fmt.Println(data)
}

يمكنك تجربة هذا في الملعب

مثال كامل يمكنك لصقه في الملعب ، لأن استيراد حزمة الفرز السريع لا يعمل في الملعب.

@ go-li أنا متأكد من أنه يمكنك فرز شريحة ، سيكون فقيرًا بعض الشيء إذا لم تتمكن من ذلك. النقطة بشكل عام هي أنك ترغب في أن تكون قادرًا على فرز أي حاوية خطية بنفس الكود ، بحيث لا يتعين عليك كتابة خوارزمية الفرز إلا مرة واحدة ، بغض النظر عن الحاوية (بنية البيانات) التي تقوم بفرزها وبغض النظر عن نوع المحتوى.

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

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

وأتخيل أن مستخدمًا لمثل هذه الوظيفة سيرغب في إرجاع نوع ملموس ، وليس واجهة أخرى.

urandom على افتراض أننا لا نقصد القيام بذلك "في المكان" الذي سيكون غير آمن ، فما تريده هو وظيفة خريطة بها "مكرر قراءة" من نوع واحد و "مكرر كتابة" من نوع آخر ، والتي يمكن تعريفها بشيء مثل:

map<I, O, U>(first I, last I, out O, fn U) requires
   ForwardIterator<I>, Readable<I>,
   ForwardIterator<O>, Writable<O>,
   UnaryFunction<U>, Domain(U) == ValueType(I), Codomain(U) == ValueType(O)

من أجل الوضوح ، "ValueType" هو نوع مرتبط من الواجهات "Readable" و "Writable" ، و "Domain" و "Codomain" هي أنواع مرتبطة بواجهة "UnaryFunction". من الواضح أنه يساعد كثيرًا إذا كان المترجم قادرًا على اشتقاق واجهات لأنواع البيانات مثل "UnaryFunction". بينما يبدو هذا النوع من الانعكاس ، فهو ليس كذلك ، وكل هذا يحدث في وقت التجميع باستخدام الأنواع الثابتة.

keean كيف تصمم تلك القيود القابلة للقراءة والكتابة في سياق واجهات Go الحالية؟

أعني ، عندما يكون لدينا نوع A ونريد التحويل إلى النوع B ، فإن توقيع هذه الوظيفة الأحادية سيكون func (input A) B (صحيح؟) ، ولكن كيف يمكن ذلك يتم تصميمها باستخدام الواجهات فقط وكيف سيتم تصميم نموذج map العام (أو filter ، reduce ، إلخ.) للحفاظ على خط الأنابيب؟

@ geovanisouza92 أعتقد أن "عائلات النوع" ستعمل بشكل جيد حيث يمكن تنفيذها كآلية متعامدة في نظام الكتابة ، ثم دمجها في بناء جملة الواجهات كما هو الحال في هاسكل.

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

لذلك إذا حددنا:

ValueType MyIntArrayIterator -> Int

الدوال مخادعة قليلاً لكن الوظيفة لها نوع ، على سبيل المثال:

fn(x : Int) Float

نكتب هذا النوع:

Int -> Float

من المهم أن ندرك أن -> هو مجرد مُنشئ نوع infix ، مثل "[]" لأن Array هو مُنشئ نوع ، يمكننا بسهولة كتابة هذا ؛

Fn Int Float
Or
Fn<Int, Float>

اعتمادًا على تفضيلنا لبناء الجملة. الآن يمكننا أن نرى بوضوح كيف يمكننا تحديد:

Domain  Fn<Int, Float> -> Int
Codomain Fn<Int, Float> -> Float

الآن بينما يمكننا تقديم كل هذه التعريفات يدويًا ، يمكن بسهولة اشتقاقها بواسطة المترجم.

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

شكراkeean.

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

سيتم نقل هذه "العائلات النوع" لوقت التشغيل ، في حالة وجود بعض سياق الخطأ؟

ماذا عن الواجهات الفارغة ومفاتيح الكتابة والانعكاس؟


تحرير: أنا مجرد فضول ، لا أشكو.

@ giovanisouza92 حسنًا ، لم يلتزم أحد بالذهاب للحصول على الأدوية الجنيسة ، لذا أتوقع الشك. أسلوبي هو أنه إذا كنت ستفعل الأدوية الجنيسة ، فيجب أن تفعلها بشكل صحيح.

في المثال الخاص بي ، يتم تحديد "الخريطة" بواسطة المستخدم. لا يوجد شيء مميز حول هذا الموضوع ، وضمن الوظيفة ، يمكنك ببساطة استخدام طرق الواجهات التي طلبتها على هذه الأنواع تمامًا كما تفعل في Go الآن. الاختلاف الوحيد هو أنه يمكننا طلب نوع لتلبية واجهات متعددة ، يمكن أن تحتوي الواجهات على معلمات نوع متعددة (على الرغم من أن مثال الخريطة لا يستخدم هذا) وهناك أيضًا أنواع مرتبطة (وقيود على أنواع مثل المساواة في النوع '==' لكن هذا يشبه Prolog المساواة ويوحد الأنواع). هذا هو سبب وجود صيغة مختلفة لتحديد الواجهات التي تتطلبها الوظيفة. لاحظ أن هناك اختلافًا مهمًا آخر:

f(x I, y I) requires ForwardIterator<I>

ضد

f(x ForwardIterator, y ForwardIterator)

لاحظ أن هناك اختلافًا في الأخير 'x' و 'y' يمكن أن يكونا نوعين مختلفين يلبيان واجهة ForwardIterator ، بينما في الصيغة السابقة 'x' و 'y' يجب أن يكونا من نفس النوع (الذي يلبي مكرر إعادة التوجيه). يعد هذا أمرًا مهمًا حتى لا يتم تقييد الوظائف بشكل كافٍ ، ويسمح بنشر الأنواع الملموسة بشكل أكبر أثناء التجميع.

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

بالتفكير في Go وتعدد أشكال وقت التشغيل وعائلات الكتابة ، ربما نرغب في تقييد عائلة النوع نفسها بواجهة لتجنب الاضطرار إلى التعامل مع كل نوع مرتبط كواجهة فارغة في وقت التشغيل والتي ستكون بطيئة.

لذلك في ضوء هذه الأفكار ، سأقوم بتعديل اقتراحي أعلاه بحيث عند الإعلان عن واجهة ، فإنك تعلن عن واجهة / نوع لكل نوع مرتبط بحيث يجب أن توفر جميع تطبيقات تلك الواجهة نوعًا مرتبطًا يلبي تلك الواجهة. بهذه الطريقة يمكننا أن نعرف أنه من الآمن استدعاء أي طرق من تلك الواجهة على الأنواع المرتبطة في وقت التشغيل دون الحاجة إلى تبديل الكتابة من واجهة فارغة.

تضمين التغريدة
من أجل تعزيز النقاش ، اسمحوا لي أن أوضح التصور الخاطئ الذي أشعر أنه يشبه متلازمة لم يتم اختراعها هنا.

مكرر ثنائي الاتجاه (في صيغة T func (*T) *[2]*T ) له النوع func (*) *[2]* في صيغة go-li. في الكلمات ، يأخذ مؤشرًا إلى نوع ما ويعيد المؤشر إلى مؤشرين للعنصر التالي والسابق من نفس النوع. إنه النوع الأساسي الملموس الأساسي الذي تستخدمه قائمة مرتبطة بشكل مزدوج .

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

func Foreach(link func(*) *[2]*, list **, direction byte, f func(*)) {

    if nil == *list {
        return
    }

    var end *
    end = *list

    var e *
    e = (*link(*list))[direction]
    f(end)

    for (e != end) && ((*link(e))[direction] != nil) {
        var newe = (*link(e))[direction]
        f(e)
        e = newe
    }
    return
}

يمكن استخدام Foreach بطريقتين ، يمكنك استخدامه مع lambda في التكرار المتكرر للحلقة على عناصر القائمة أو المجموعة.

const forward = 1
const backwards = 0
Foreach(iterator, collection, forward, func(element *element_type){
    // do something with every element
})

أو يمكنك استخدامه لتعيين وظيفة وظيفية لكل عنصر مجموعة.

Foreach(iterator, collection, backwards, function_to_be_mapped_on_elements)

يمكن بالطبع أيضًا تصميم المكرر ثنائي الاتجاه باستخدام واجهات في go 1.
interface Iterator { Iter() [2]Iterator } تحتاج إلى تصميمه باستخدام واجهات من أجل التفاف ("مربع") النوع الأساسي. يقوم مستخدم التكرار بعد ذلك بتأكيد النوع المعروف بمجرد تحديد موقعه ورغبته في زيارة عنصر مجموعة معين. هذا من المحتمل أن يكون وقت التجميع غير آمن.

ما تصفه بعد ذلك هو الاختلافات بين النهج القديم والنهج القائم على الأدوية الجنيسة.

func modern(x func  (*) *[2]*, y func  (*) *[2]*){}

يتحقق هذا النهج من نوع وقت التجميع من أن المجموعتين لهما نفس النوع الأساسي ، بمعنى آخر ما إذا كانت التكرارات ترجع بالفعل نفس الأنواع الملموسة

func modern_T_syntax<T>(x func  (*T) *[2]*T, y func  (*T) *[2]*T){}

كما هو مذكور أعلاه ولكن استخدام حرف T المألوف يعني كتابة العنصر النائب بناء جملة

func legacy(x Iterator, y Iterator){}

في هذه الحالة ، يمكن للمستخدم أن يمرر على سبيل المثال قائمة مرتبطة بعدد صحيح كـ x وقائمة مرتبطة عائمة كـ y. قد يؤدي ذلك إلى حدوث أخطاء وقت تشغيل محتملة أو حالات من الذعر أو غير ذلك من عمليات فك الترابط الداخلية ، لكن كل هذا يتوقف على ما يمكن أن يفعله الإرث مع المتكررين.

الآن الاعتقاد الخاطئ. أنت تدعي أن إجراء التكرارات والقيام بأنواع عامة لفرز هؤلاء التكرارات سيكون هو السبيل للذهاب. سيكون هذا أمرًا سيئًا حقًا ، وإليك السبب

التكرار والقائمة المرتبطة وجهان لعملة واحدة. برهان: أي مجموعة تعرض مكرر تعلن عن نفسها كقائمة مرتبطة. لنفترض أنك بحاجة إلى فرز ذلك. ماذا؟

من الواضح أنك تقوم بحذف القائمة المرتبطة من قاعدة التعليمات البرمجية الخاصة بك واستبدالها بشجرة ثنائية. أو إذا كنت تريد أن تكون خياليًا ، استخدم شجرة بحث متوازنة مثل avl ، أحمر أسود ، كما هو مقترح ، لا أعرف عدد السنوات الماضية بواسطة Ian et all. لا يزال هذا لم يتم بشكل عام في golang. الآن سيكون هذا هو الطريق للذهاب.

حل آخر هو إجراء حلقة زمنية O (N) بسرعة فوق المكرر ، وجمع المؤشرات إلى العناصر في شريحة من المؤشرات العامة ، والمشار إليها []*T وفرز تلك المؤشرات العامة باستخدام تصنيف الشرائح الضعيف

يرجى إعطاء فرصة لأفكار الآخرين

@ go-li إذا أردنا تجنب متلازمة "لم يتم اختراعها" ، يجب أن ننظر إلى أليكس ستيبانوف للحصول على تعريف ، لأنه اخترع البرمجة العامة إلى حد كبير. وإليك كيف يمكنني تعريفه ، مأخوذ من صفحة "عناصر البرمجة" لستيبانوف:

Bidirectional iterator<T> =
    ForwardIterator<T>
/\ predecessor : T -> T
/\ predecessor takes constant time
/\ (forall i in T) successor(i) is defined =>
        predecessor(successor(i)) is defined and equals i
/\ (forall i in T) predecessor(i) is defined =>
        successor(predecessor(i)) is defined and equals i

هذا يعتمد على تعريف ForwardIterator:

ForwardIterator<T> =
    Iterator<T>
/\ regular_unary_function(successor)

لذلك ، لدينا واجهة تعلن عن وظيفة successor ووظيفة predecessor ، جنبًا إلى جنب مع بعض البديهيات التي يجب أن تمتثل لها لتكون صالحة.

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

id(x T) T

ربما من الجدير بالذكر أيضًا الفرق بين النوع البارامترى والنوع الكمي عالميًا. سيكون النوع البارامترى هو id<T>(x T) T بينما النوع المُقاس عالميًا هو id(x T) T (عادةً ما نحذف المُحدِّد الكمي العالمي الخارجي في هذه الحالة forall T ). مع الأنواع البارامترية ، يجب أن يحتوي نظام النوع على نوع T المقدم في موقع الاتصال مقابل id ، مع تحديد كمي عالمي ليس ضروريًا طالما تم توحيد T بنوع ملموس قبل انتهاء التجميع. هناك طريقة أخرى لفهم أن الوظيفة البارامترية ليست نوعًا بل قالبًا لنوع ، وهي فقط نوع صالح بعد استبدال T بنوع ملموس. مع الدالة المُقاسة عالميًا id لديها نوع forall T . T -> T يمكن تمريره بواسطة المترجم تمامًا مثل Int .

@ go-li

من الواضح أنك تقوم بحذف القائمة المرتبطة من قاعدة التعليمات البرمجية الخاصة بك واستبدالها بشجرة ثنائية. أو إذا كنت تريد أن تكون خياليًا ، استخدم شجرة بحث متوازنة مثل avl ، أحمر أسود ، كما هو مقترح ، لا أعرف عدد السنوات الماضية بواسطة Ian et all. لا يزال هذا لم يتم بشكل عام في golang. الآن سيكون هذا هو الطريق للذهاب.

لا يعني وجود هياكل بيانات مرتبة أنك لا تحتاج أبدًا إلى فرز البيانات.

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

أود أن أعترض على أي ادعاء بأن البرمجة العامة اخترعت بواسطة C ++. اقرأ Liskov et al. ورقة CACM لعام 1977 إذا كنت تريد أن ترى نموذجًا مبكرًا للبرمجة العامة التي تعمل بالفعل (النوع الآمن ، المعياري ، بدون انتفاخ الكود): https://dl.acm.org/citation.cfm؟id=359789 (انظر القسم 4 )

أعتقد أننا يجب أن نوقف هذا النقاش وننتظر فريق golang (russ) ليأتي ببعض منشورات المدونة ثم ننفذ حلاً 👍 (انظر vgo) سيفعلون ذلك فقط 🎉

https://peter.bourgon.org/blog/2018/07/27/a-response-about-dep-and-vgo.html

آمل أن تكون هذه القصة بمثابة تحذير للآخرين: إذا كنت مهتمًا بتقديم مساهمات كبيرة لمشروع Go ، فلا يمكن لأي قدر من العناية الواجبة المستقلة أن يعوض عن تصميم لم ينشأ من الفريق الأساسي.

يوضح هذا الموضوع كيف أن الفريق الأساسي غير مهتم بالمشاركة بنشاط في إيجاد حل مع المجتمع.

لكن في النهاية ، إذا تمكنوا من إيجاد حل بأنفسهم مرة أخرى ، فهذا جيد من قبلي ، فقط أنجزه 👍

andrewcmyers حسنًا ، ربما كان مصطلح "اخترع" نوعًا من الامتداد ، وربما يكون مثل David Musser في عام 1971 ، الذي عمل لاحقًا مع Stepanov في بعض المكتبات العامة لـ Ada.

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

هذه المشكلة توتر بالفعل تحت حدود قابلية GitHub للتوسع. يرجى إبقاء المناقشة مركزة هنا على القضايا الملموسة لمقترحات Go.

سيكون من المؤسف أن تتبع Go مسار C ++.

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

@ البدو البرمجيات

أنا أحب D ، ولكن هل يحتاج go إلى ميزات البرمجة الوصفية القوية التي توفرها D؟

لا أحب بناء جملة القالب أيضًا في لغة ++ C النابعة من العصر الحجري.

ولكن ماذا عن ParametricType العاديالمعيار الموجود في Java أو C # ، إذا لزم الأمر ، يمكن للمرء أيضًا زيادة التحميل باستخدام ParametricType

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

@ nomad-software لم أكن أقترح أن بناء جملة C ++ أو آلية القالب هي الطريقة الصحيحة للقيام بالأدوية الجنيسة. أكثر من أن "المفاهيم" كما حددها ستيبانوف تعامل الأنواع كجبر ، وهي الطريقة الصحيحة جدًا للقيام بالأدوية الجنيسة. انظر إلى فئات هاسكل من النوع لترى كيف يمكن أن يبدو هذا. تعتبر فئات نوع Haskell قريبة جدًا من قوالب ومفاهيم c ++ لغويًا ، إذا فهمت ما يجري.

لذا +1 لعدم اتباع بنية c ++ و +1 لعدم تنفيذ نظام نموذج من النوع غير الآمن :-)

keean السبب في بناء جملة D هو تجنب <,> تمامًا والالتزام بقواعد خالية من السياق. هذا جزء من وجهة نظري لاستخدام D كمصدر إلهام. يعد <,> اختيارًا سيئًا حقًا لبناء جملة المعلمات العامة.

@ nomad-software كما أشرت أعلاه (في تعليق مخفي الآن) تحتاج إلى تحديد معلمات النوع للأنواع البارامترية ، ولكن ليس للأنواع المحددة كميًا (ومن ثم فإن الاختلاف بين Rust و Haskell ، فإن طريقة التعامل مع الأنواع مختلفة بالفعل في نظام النوع). أيضًا مفاهيم C ++ == فئات نوع Haskell == واجهات Go ، على الأقل على المستوى المفاهيمي.

هل بناء الجملة D مفضل حقًا:

auto add(T)(T lhs, T rhs) {
    return lhs + rhs;

لماذا هذا أفضل من أسلوب C ++ / Java / Rust:

T add<T>(T lhs, T rhs) {
    return lhs + rhs;
}

أو نمط سكالا:

T add[T](T lhs, T rhs) {
    return lhs + rhs;
}

لقد فعلت بعض التفكير في بناء الجملة لمعلمات النوع. لم أكن أبدًا من المعجبين بـ "أقواس الزاوية" في C ++ و Java لأنها تجعل التحليل صعبًا للغاية وبالتالي تعيق تطوير الأدوات. الأقواس المربعة هي في الواقع خيار كلاسيكي (من CLU ، و System F ، ولغات مبكرة أخرى مع تعدد الأشكال البارامترية).

ومع ذلك ، فإن بناء جملة Go حساس للغاية ، ربما لأنه مقتضب بالفعل. الصيغ المحتملة القائمة على الأقواس المربعة أو الأقواس تخلق غموضًا نحويًا أسوأ من تلك التي أدخلتها أقواس الزاوية. لذلك على الرغم من ميولتي ، يبدو أن الأقواس الزاوية هي في الواقع أفضل خيار لـ Go. (بالطبع ، هناك أيضًا أقواس زاوية حقيقية لن تخلق أي غموض - ⟨⟩ - لكنها تتطلب استخدام أحرف Unicode).

بالطبع ، الصيغة الدقيقة المستخدمة لمعلمات النوع أقل أهمية من الحصول على الدلالات الصحيحة. في هذه المرحلة ، تعد لغة C ++ نموذجًا سيئًا. يقدم عمل مجموعتي البحثية حول الأدوية الجنيسة في Genus (PLDI 2015) و Familia (OOPSLA 2017) نهجًا آخر يوسع فئات الكتابة ويوحدها بالواجهات.

andrewcmyers أعتقد أن هاتين الورقتين مثيرتان للاهتمام ، لكنني سأقول أنهما ليس اتجاهًا جيدًا لـ Go ، حيث أن Genus موجه نحو الكائن ، و Go ليس كذلك ، و Familia توحد الأنواع الفرعية وتعدد الأشكال ، و Go ليس لديها أي منهما. أعتقد أن Go يجب أن يتبنى ببساطة إما تعدد الأشكال البارامتي أو القياس الكمي الشامل ، فهو لا يحتاج إلى تصنيف فرعي ، وفي رأيي هي لغة أفضل لعدم امتلاكها.

أعتقد أن Go يجب أن يبحث عن الأدوية الجنسية التي لا تتطلب توجيهًا للكائن ولا تتطلب تصنيفًا فرعيًا. يحتوي Go بالفعل على واجهات ، والتي أعتقد أنها آلية جيدة للأدوية. إذا كان بإمكانك أن ترى أن واجهات Go == c ++ مفاهيم == فئات Haskell من النوع ، يبدو لي أن طريقة إضافة العوامل العامة مع الحفاظ على نكهة "Go" هي توسيع الواجهات لأخذ معلمات من النوع المتعدد (أود أن أفعل مثل الأنواع المرتبطة على الواجهات أيضًا ، ولكن يمكن أن يكون امتدادًا منفصلاً لها يساعد في قبول معلمات النوع المتعددة). سيكون هذا هو التغيير الأساسي ، ولكن لتمكين ذلك ، يجب أن يكون هناك بناء جملة "بديل" للواجهات في تواقيع الوظائف ، بحيث يمكنك الحصول على معلمات النوع المتعددة للواجهات ، حيث يأتي بناء جملة قوس الزاوية بالكامل .

لا تعد واجهات Go فئات من النوع - فهي مجرد أنواع - ولكن توحيد الواجهات مع فئات النوع هو ما يوضح Familia طريقة للقيام به. لا ترتبط آليات Genus و Familia باللغات التي تكون موجهة بشكل كامل. تعمل واجهات Go بالفعل على جعل Go "موجهًا للكائنات" بالطرق المهمة ، لذلك أعتقد أنه يمكن تكييف الأفكار في شكل مبسط قليلاً.

تضمين التغريدة

لا تعد واجهات Go فئات من النوع - فهي مجرد أنواع

إنهم لا يتصرفون مثل الأنواع بالنسبة لي ، لأنهم يسمحون بتعدد الأشكال. الكائن في مصفوفة متعددة الأشكال مثل Addable [] لا يزال له نوعه الفعلي (مرئي من خلال انعكاس وقت التشغيل) ، لذا فهم يتصرفون تمامًا مثل فئات النوع أحادي المعامل. إن حقيقة أنها توضع في مكان النوع في توقيعات النوع هي ببساطة تدوين مختصر يحذف متغير النوع. لا تخلط بين التدوين و دلالات الألفاظ.

f(x : Addable) == f<T>(x : T) requires Addable<T>

هذه الهوية صالحة بالطبع فقط لواجهات معلمة واحدة.

الاختلاف الوحيد المهم بين الواجهات وفئات النوع أحادي المعامل هو أن الواجهات محددة محليًا ، لكن هذا مفيد لأنه يتجنب مشكلة التماسك العالمي التي يواجهها هاسكل مع فئات النوع الخاصة به. أعتقد أن هذه نقطة مثيرة للاهتمام في مساحة التصميم. ستمنحك الواجهات متعددة المعلمات كل قوة فئات النوع متعدد المعلمات مع الاستفادة من كونها محلية. ليست هناك حاجة لإضافة أي وراثة أو تصنيف فرعي إلى لغة Go (وهما السمتان الرئيسيتان اللتان تحددان OO على ما أعتقد).

IMHO:

لا يزال لديك نوع افتراضي سيكون أفضل من DSL المخصص للتعبير عن قيود النوع. مثل وجود دالة f(s T fmt.Stringer) وهي دالة عامة تقبل أي نوع يكون أيضًا / يفي بواجهة fmt.Stringer .

بهذه الطريقة يمكن الحصول على وظيفة عامة مثل:

func add(a, b T int) T int {
    return a + b
}

الآن تعمل الوظيفة add() مع أي نوع T مثل int s يدعم + المشغل.

@ dc0d أوافق على أنه يبدو جذابًا عند النظر إلى بناء جملة Go الحالي. ومع ذلك ، فهي ليست "كاملة" من حيث أنها لا يمكن أن تمثل جميع القيود اللازمة للأدوية الجنيسة ، وسيظل هناك دافع لتوسيع هذا إلى أبعد من ذلك. سيؤدي هذا إلى انتشار تراكيب مختلفة أرى أنها تتعارض مع هدف البساطة. وجهة نظري هي أن البساطة ليست بسيطة ، بل يجب أن تكون الأبسط ولكنها لا تزال تقدم القوة التعبيرية المطلوبة. أرى حاليًا أن قيود Go الرئيسية في القوة التعبيرية العامة هي عدم وجود واجهات متعددة المعلمات. على سبيل المثال ، يمكن تعريف واجهة المجموعة مثل:

type T U Collection interface {
   member(c T, v U) Bool
   insert(c T, v U) T
}

لذلك هذا منطقي أليس كذلك؟ نود أن نكتب واجهات على أشياء مثل المجموعات. لذا فإن السؤال هو كيف تستخدم هذه الواجهة في وظيفة. سيكون اقتراحي شيئًا مثل:

func[T, U] f(c T, e U) (Bool, T) requires Collection[T, U] {
   a := member(c, e)
   d := insert(c, e)
   return a, d
}

بناء الجملة هو مجرد اقتراح ومع ذلك ، فأنا لا أمانع حقًا في ماهية بناء الجملة ، طالما يمكنك التعبير عن هذه المفاهيم في اللغة.

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

type Collection interface (T interface{}, U interface{}) {
   member(c T, v U) Bool
   insert(c T, v U) T
}

الآن يساعد الجزء (T interface{}, U interface{}) في تحديد القيود. على سبيل المثال ، إذا كان الأعضاء يقصدون إرضاء fmt.Stringer ، فسيكون التعريف:

type Collection interface (T fmt.Stringer, U fmt.Stringer) {
   member(c T, v U) Bool
   insert(c T, v U) T
}

@ dc0d سيكون هذا مقيدًا مرة أخرى بمعنى أنك تريد التقييد بأكثر من معلمة من النوع ، ضع في اعتبارك:

type OrderedCollection[T, U] interface
   requires Collection[T, U], Ord[U] {...}

أعتقد أنني أرى من أين أتيت من خلال وضع المعلمة ، فقد يكون لديك:

type OrderedCollection interface(T, U)
   requires Collection(T, U), Ord(U) {...}

كما قلت ، لست منزعجًا جدًا من بناء الجملة ، حيث يمكنني التعود على معظم التركيبات. مما سبق أعتبر أنك تفضل الأقواس "()" للواجهات متعددة المعلمات.

keean لنفكر في واجهة heap.Interface . التعريف الحالي في المكتبة القياسية هو:

type Interface interface {
    sort.Interface
    Push(x interface{}) // add x as element Len()
    Pop() interface{}   // remove and return element Len() - 1.
}

الآن دعنا نعيد كتابته كواجهة عامة ، باستخدام النوع الافتراضي:

type Interface interface (T interface{}) {
    sort.Interface
    Push(x T) // add x as element Len()
    Pop() T   // remove and return element Len() - 1.
}

هذا لا يكسر أيًا من سلسلة أكواد Go 1.x الموجودة هناك. سيكون أحد التطبيقات هو اقتراحي الخاص بإعادة ربط الاسم المستعار. لكنني متأكد من أنه يمكن أن يكون هناك تطبيقات أفضل.

يسمح لنا وجود أنواع افتراضية بكتابة رمز عام يمكن استخدامه مع كود نمط Go 1.x. ويمكن أن تصبح المكتبة القياسية مكتبة عامة ، دون كسر أي شيء. هذا فوز كبير IMO.

@ dc0d لذا أنت تقترح تحسينًا تدريجيًا؟ ما تقترحه يبدو جيدًا بالنسبة لي كتحسين تدريجي ، ومع ذلك لا يزال لديه قوة تعبيرية عامة محدودة. كيف ستنفذ واجهات "Collection" و "OrderedCollection"؟

ضع في اعتبارك أن العديد من امتدادات اللغة الجزئية قد تؤدي إلى منتج نهائي أكثر تعقيدًا (مع عدة صيغ بديلة) بدلاً من تنفيذ الحل الكامل بأبسط طريقة ممكنة.

keean أنا لا أفهم الجزء requires Collection[T, U], Ord[U] . كيف يتم تقييد معلمات النوع T و U ؟

@ dc0d تعمل بنفس الطريقة كما في الوظيفة ، لكنها تنطبق على كل شيء. لذلك ، بالنسبة لأي زوج من الأنواع TU التي تعتبر مجموعة OrderedCollection ، فإننا نطلب أن يكون TU أيضًا مثيلًا للمجموعة وأن U هو Ord. لذلك في أي مكان نستخدم فيه OrderedCollection ، يمكننا استخدام طرق من Collection و Ord حسب الاقتضاء.

إذا كنا نحاول الحد الأدنى ، فهذه ليست مطلوبة ، لأنه يمكننا تضمين الواجهات الإضافية في أنواع الوظائف التي نحتاج إليها ، على سبيل المثال:

type OrderedCollection interface(T, U)
{
   first(c T) U
}

func[T] first(c T[]) T requires Collection(T[], T), Ord T
{...}

func[T] f(c T[]) requires OrderedCollection(T[], T), Collection(T[], T), Ord(T)
{...}

ولكن قد يكون هذا أكثر قابلية للقراءة:

type OrderedCollection interface(T, U) 
   requires Collection(T, U), Ord(U)
{
   first(c T) U
}

func[T] first(c T[]) T
{...}

func[T] f(c T[]) requires OrderedCollection(T[], T)
{...}

keean (IMO) طالما أن هناك قيمة افتراضية إلزامية لمعلمات النوع ، أشعر بالسعادة. بهذه الطريقة يمكن الحفاظ على التوافق مع الإصدارات السابقة مع سلسلة التعليمات البرمجية Go 1.x. هذه هي النقطة الرئيسية التي حاولت توضيحها.

تضمين التغريدة

لا تعد واجهات Go فئات من النوع - فهي مجرد أنواع

إنهم لا يتصرفون مثل الأنواع بالنسبة لي ، لأنهم يسمحون بتعدد الأشكال.

نعم ، تسمح بتعدد الأشكال الفرعي . Go لديه نوع فرعي عبر أنواع الواجهة. لا يحتوي على تسلسلات هرمية للأنواع الفرعية مُعلنة صراحة ، لكن هذا متعامد إلى حد كبير. ما يجعل Go ليس موضوعي المنحى بشكل كامل هو الافتقار إلى الميراث.

بدلاً من ذلك ، يمكنك عرض الواجهات كتطبيقات محددة كميًا لفئات النوع. أعتقد أن هذا ما يدور في ذهنك. هذا ما فعلناه في Genus and Familia.

تضمين التغريدة

نعم ، تسمح بتعدد الأشكال الفرعي.

اذهب بقدر ما أعرف أنه ثابت ، لا يوجد تباين أو تباين ، هذا يتحدث بقوة أن هذا ليس تصنيفًا فرعيًا. تعد أنظمة النوع متعدد الأشكال ثابتة ، لذا يبدو لي أن Go أقرب إلى هذا النموذج ، ويبدو أن معاملة الواجهات على أنها فئات نوع معلمة واحدة أكثر انسجامًا مع بساطة Go. يعد الافتقار إلى التباين والتناقض فائدة كبيرة للأدوية الجنيسة ، ما عليك سوى إلقاء نظرة على الارتباك الذي تحدثه هذه الأشياء في لغات مثل C #:

https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

أعتقد أن Go يجب أن يتجنب تمامًا هذا النوع من التعقيد. بالنسبة لي ، هذا يعني أننا لا نريد الأدوية الجنيسة والترتيب الفرعي في نفس النوع من النظام.

بدلاً من ذلك ، يمكنك عرض الواجهات كتطبيقات محددة كميًا لفئات النوع. أعتقد أن هذا ما يدور في ذهنك. هذا ما فعلناه في Genus and Familia.

نظرًا لأن Go لديه معلومات الكتابة في وقت التشغيل ، فلا داعي للتقدير الوجودي. في هاسكل ، تكون أنواع المحتويات بدون علبة (مثل الأنواع الأصلية "C") وهذا يعني أنه بمجرد وضع شيء ما في مجموعة وجودية لا يمكننا (بسهولة) استعادة نوع المحتويات ، كل ما يمكننا فعله هو استخدام الواجهات المتوفرة (فئات النوع ). يتم تنفيذ ذلك عن طريق تخزين مؤشر للواجهات بجانب البيانات الأولية. في Go ، يتم تخزين نوع البيانات بدلاً من ذلك ، تكون البيانات "محاصر" (كما في C # البيانات المعبأة وغير المعبأة). على هذا النحو ، لا يقتصر Go على الواجهات المخزنة مع البيانات لأنه من الممكن (باستخدام حالة من النوع) استرداد نوع البيانات في المجموعة ، وهو أمر ممكن فقط في Haskell من خلال تنفيذ "انعكاس" فئة الطباعة (على الرغم من صعوبة إخراج البيانات ، فمن الممكن إجراء تسلسل للنوع والبيانات ، لنقل السلاسل ، ثم إلغاء التسلسل خارج المربع الوجودي). لذا فإن الاستنتاج الذي توصلت إليه هو أن واجهات Go تتصرف تمامًا مثل فئات النوع ، إذا قدم Haskell فئة النوع 'Reflection' كمدمج. على هذا النحو لا يوجد مربع وجودي ، ولا يزال بإمكاننا كتابة الحالة على محتويات المجموعات ، ومع ذلك تتصرف الواجهات تمامًا مثل فئات النوع. يكمن الاختلاف بين Haskell و Go في دلالات البيانات المعبأة مقابل البيانات غير المعبأة ، والواجهات عبارة عن فئات نوع معلمة مفردة. في الواقع ، عندما يتعامل "Go" مع الواجهة كنوع ، فإن ما تفعله في الواقع هو:

Addable[] == exists T . T[] requires Addable[T], Reflection[T]

من الجدير بالذكر أن هذه هي الطريقة نفسها التي تعمل بها "السمات" في Rust.

يمكن أن يتجنب Go الوجود تمامًا (كونه مرئيًا للمبرمج) ، والتغاير والتناقض وهو أمر جيد ، وهذا سيجعل الأدوية الجنيسة أبسط وأكثر قوة في رأيي.

اذهب بقدر ما أعرف أنه ثابت ، لا يوجد تباين أو تباين ، هذا يتحدث بقوة أن هذا ليس تصنيفًا فرعيًا.

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

هل لي أن أقترح أن كلاكما على صواب؟ في تلك الواجهات تكافئ فئات النوع ، لكن فئات النوع هي شكل من أشكال التصنيف الفرعي. إن تعريفات التصنيف الفرعي التي وجدتها حتى الآن غامضة إلى حد ما وغير دقيقة وتتلخص في "A هو نوع فرعي من B ، إذا كان من الممكن استبدال أحدهما بالآخر". والتي ، IMO ، يمكن القول بسهولة أنها ترضي بفئات النوع .

لاحظ أن حجة التباين في حد ذاتها لا تعمل حقًا IMO. التباين هو خاصية لمنشئي النوع ، وليس لغة. ومن الطبيعي جدًا ألا تكون كل منشئات الأنواع في لغة ما متغيرة (على سبيل المثال ، تحتوي الكثير من اللغات ذات التصنيف الفرعي على مصفوفات قابلة للتغيير ، والتي يجب أن تكون ثابتة حتى تكون آمنة من النوع). لذلك لا أفهم لماذا لا يمكنك الحصول على تصنيف فرعي بدون مُنشئ النوع المتغير.

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

Merovius Variance هي خاصية مرتبطة بالتصنيف الفرعي. في اللغات التي لا تحتوي على تصنيف فرعي ، لا يوجد تباين. لكي يكون هناك تباين في المقام الأول ، يجب أن يكون لديك تصنيف فرعي ، والذي يقدم مشكلة التباين / التناقض لكتابة المنشئات. أنت محق في أنه في لغة بها تصنيف فرعي ، من الممكن أن تكون جميع منشئات النوع ثابتة.

من المؤكد أن فئات النوع ليست نوعًا فرعيًا ، لأن فئة النوع ليست نوعًا. ومع ذلك ، يمكننا عرض "أنواع الواجهة" في Go على أنها ما يسميه Rust "كائن سمة" بشكل فعال نوع مشتق من فئة النوع.

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

type (T, U) Collection interface {
    member : (c T, e U) Bool
    insert: (c T, e U) T
}

member(c int32[], e int32) Bool {...}
insert(c int32[], e int32) int32[] {...}

member(c float32[], e float32) Bool {...}
insert(c float32[], e float32) float32[] {...}

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

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

keean نظرًا لأنه يمكن استخدام الواجهات كأنواع ، وفئات النوع ليست أنواعًا ، فإن التفسير الأكثر طبيعية لدلالات Go هو أن الواجهات ليست فئات نوع. أفهم أنك تحاجج لتعميم الواجهات على أنها فئات من النوع ؛ أعتقد أنه اتجاه معقول لأخذ اللغة ، وفي الواقع لقد اكتشفنا بالفعل هذا النهج على نطاق واسع في عملنا المنشور.

فيما يتعلق بما إذا كان Go يحتوي على تصنيف فرعي ، يرجى مراعاة الكود التالي:

package main

type Cloneable interface {
    Clone() Cloneable
}

type CloneableZ interface {
    Clone() Cloneable
    zero() int
}

type S struct {}

func (t S) Clone() Cloneable {
    c := t
    return c
}

func (t S) zero() int {
    return 0
}

var x CloneableZ = S{}
var y Cloneable = x

func main() {
    print("ok\n")
}

توضح المهمة من x إلى y أنه يمكن استخدام نوع y حيث من المتوقع أن يكون نوع x . هذه علاقة نوع فرعي ، على النحو التالي: CloneableZ <: Cloneable ، وكذلك S <: CloneableZ . حتى إذا قمت بشرح الواجهات من حيث فئات النوع ، فستظل هناك علاقة فرعية قيد التشغيل هنا ، مثل S <: ∃T.CloneableZ[T] <: ∃T.Cloneable[T] .

لاحظ أنه سيكون من الآمن تمامًا أن تسمح Go للوظيفة Clone بإرجاع S ، ولكن يحدث Go لفرض قواعد تقييدية غير ضرورية للتوافق مع الواجهات: في الواقع ، نفس القواعد التي تستخدمها Java فرض في الأصل. لا يتطلب التصنيف الفرعي مُنشئات من النوع غير الثابت ، كما لاحظ Merovius .

andrewcmyers ماذا يحدث للواجهات متعددة المعلمات ، مثل تلك الضرورية لتلخيص المجموعات؟

علاوة على ذلك ، يمكن اعتبار التخصيص من x إلى y على أنه يوضح توريث الواجهة بدون أي نوع فرعي على الإطلاق. في هاسكل (التي من الواضح أنها لا تحتوي على تصنيف فرعي) ستكتب:

class Cloneable t => CloneableZ t where...

حيث لدينا x هو نوع يستخدم CloneableZ والذي بحكم تعريفه ينفذ أيضًا Cloneable ، لذلك من الواضح أنه يمكن تخصيصه لـ y .

للمحاولة والتلخيص ، يمكنك إما عرض الواجهة كنوع والانتقال للحصول على تصنيف فرعي محدود بدون متغير مشترك أو مُنشئ نوع مخالف ، أو يمكنك مشاهدتها على أنها "كائن سمة" ، أو ربما في Go نسميها " كائن الواجهة "، وهو عبارة عن حاوية متعددة الأشكال مقيدة بواجهة" فئة نوع ". في نموذج النوع لا يوجد نوع فرعي ، وبالتالي لا يوجد سبب للتفكير في التغاير والتناقض.

إذا التزمنا بنموذج التصنيف الفرعي ، فلا يمكننا الحصول على أنواع المجموعات ، ولهذا السبب كان على C ++ تقديم قوالب ، لأن التصنيف الفرعي الموجه للكائنات لا يكفي لتعريف مفاهيم مثل الحاويات بشكل عام. ننتهي بآليتين للتجريد ، الكائنات والتصنيف الفرعي ، والقوالب / السمات والأدوية ، والتفاعلات بين الاثنين تصبح معقدة ، انظر إلى C ++ و C # و Scala للامتحانات. ستكون هناك دعوات مستمرة لإدخال المُنشئات المتغايرة والمتناقضة لزيادة قوة الأدوية الجنيسة ، بما يتماشى مع تلك اللغات الأخرى.

إذا كنا نريد مجموعات عامة دون تقديم نظام منفصل للأدوية ، فعلينا التفكير في واجهات مثل فئات النوع. قد تعني الواجهات متعددة المعلمات عدم التفكير في التصنيف الفرعي ، وبدلاً من ذلك التفكير في وراثة الواجهة. إذا أردنا تحسين الأدوية الجنيسة في Go ، والسماح بتجريد أشياء مثل المجموعات ، ولا نريد تعقيد أنظمة أنواع اللغات مثل C ++ و C # و Scala وما إلى ذلك ، فإن الواجهات متعددة المعلمات ووراثة الواجهة هي الطريقة توجو.

تضمين التغريدة

ماذا يحدث للواجهات متعددة المعلمات ، مثل تلك الضرورية لتلخيص المجموعات؟

يرجى الاطلاع على أوراقنا حول Genus and Familia ، والتي تدعم قيود نوع متعدد العوامل. تقوم Familia بتوحيد هذه القيود مع الواجهات وتسمح للواجهات بتقييد أنواع متعددة.

إذا التزمنا بنموذج التصنيف الفرعي ، فلا يمكننا الحصول على أنواع المجموعات

لست متأكدًا تمامًا مما تقصده بـ "نموذج التصنيف الفرعي" ، ولكن من الواضح تمامًا أن Java و C # لهما أنواع تجميع ، لذا فإن هذا الادعاء ليس له معنى كبير بالنسبة لي.

عندما يكون لدينا x هو نوع ينفذ CloneableZ والذي بحكم التعريف يقوم أيضًا بتنفيذ Cloneable ، لذلك من الواضح أنه يمكن تخصيصه لـ y.

لا ، في المثال الذي قدمته ، x متغير و y متغير آخر. إذا علمت أن y هي نوع من أنواع CloneableZ وأن x من نوع Cloneable ، فهذا لا يعني أنه يمكنني التخصيص من y إلى x. هذا ما يفعله مثالي.

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

type Cloneable interface {
    Clone() Cloneable
}

type CloneableZ interface {
    Clone() Cloneable
    zero() int
}

type S struct {}

func (t S) Clone() Cloneable {
    c := t
    return c
}

type T struct { x int }

func (t T) Clone() Cloneable {
    c := t
    return c
}

func (t S) zero() int {
    return 0
}

var x CloneableZ = S{}
var y Cloneable = T{}
var a [2]Cloneable = [2]Cloneable{x, y}

تضمين التغريدة

لست متأكدًا تمامًا مما تقصده بـ "نموذج التصنيف الفرعي" ، ولكن من الواضح تمامًا أن Java و C # لهما أنواع تجميع ، لذا فإن هذا الادعاء ليس له معنى كبير بالنسبة لي.

ألقِ نظرة على سبب تطوير قوالب C ++ ، لم يكن نموذج التصنيف الفرعي OO قادرًا على التعبير عن المفاهيم العامة اللازمة لتعميم أشياء مثل المجموعات. كان على C # وجافا أيضًا تقديم نظام شامل منفصل عن الكائنات ، التصنيف الفرعي والميراث ، ثم كان عليهما تنظيف الفوضى الناتجة عن التفاعلات المعقدة للنظامين بأشياء مثل مُنشِئات النوع المتغاير والمخالف. مع الاستفادة من الإدراك المتأخر ، يمكننا تجنب تصنيف OO الفرعي ، وبدلاً من ذلك ننظر إلى ما يحدث إذا أضفنا واجهات (فئات النوع) إلى لغة مكتوبة ببساطة. هذا ما فعله Rust ، لذا فإن الأمر يستحق إلقاء نظرة عليه ، لكنه بالطبع معقد بسبب كل شيء مدى الحياة. Go لديه GC لذلك لن يكون لديه هذا التعقيد. اقتراحي هو أنه يمكن تمديد Go للسماح بواجهات متعددة المعلمات ، وتجنب هذا التعقيد.

فيما يتعلق بادعائك بأنه لا يمكنك فعل هذا المثال في هاسكل ، فإليك الكود:

{-# LANGUAGE ExistentialQuantification #-}

class ICloneable t where
    clone :: t -> t

class ICloneable t => ICloneableZ t where
    zero :: t

data S = S deriving Show

instance ICloneable S where
    clone x = x

data T = T Int deriving Show

instance ICloneable T where
    clone x = x

instance ICloneableZ T where
    zero = T 0

data Cloneable = forall a . (ICloneable a, Show a) => ToCloneable a

instance Show Cloneable where
    show (ToCloneable x) = show x

main = do
    x <- return S
    y <- return (T 27)
    a <- return [ToCloneable x, ToCloneable y]
    putStrLn (show a)

بعض الاختلافات المثيرة للاهتمام ، يشتق Go تلقائيًا هذا النوع data Cloneable = forall a . (ICloneable a, Show a) => ToCloneable a حيث أن هذه هي الطريقة التي تحول بها الواجهة (التي لا تحتوي على مساحة تخزين) إلى نوع (يحتوي على مساحة تخزين) ، كما يشتق Rust هذه الأنواع ويطلق عليها اسم "كائنات السمات" . في لغات أخرى مثل Java و C # و Scala ، نجد أنه لا يمكنك إنشاء واجهات ، والتي هي في الواقع "صحيحة" ، فالواجهات ليست أنواعًا ، ولا تحتوي على مساحة تخزين ، يقوم Go باشتقاق نوع الحاوية الوجودية تلقائيًا من أجلك حتى تتمكن من التعامل واجهة مثل نوع ، ويخفي Go هذا عنك عن طريق إعطاء الحاوية الوجودية نفس اسم الواجهة المشتقة منها. الشيء الآخر الذي يجب ملاحظته هو أن هذا [2]Cloneable{x, y} يجبر جميع الأعضاء على Cloneable ، في حين أن Haskell ليس لديها مثل هذه الإكراهات الضمنية ، وعلينا أن نجبر الأعضاء صراحةً بـ ToCloneable .

لقد أُشير أيضًا إلى أنه لا ينبغي اعتبار الأنواع الفرعية S و T من Cloneable لأن S و T ليست كذلك متوافق هيكليًا. يمكننا أن نعلن حرفيًا عن أي نوع مثيل لـ Cloneable (فقط من خلال الإعلان عن التعريف ذي الصلة للوظيفة clone in Go) وأن هذه الأنواع لا تحتاج إلى أي علاقة ببعضها البعض على الإطلاق.

يبدو أن معظم مقترحات Generics تتضمن رموزًا إضافية أعتقد أنها تضر بسهولة القراءة والشعور البسيط بـ Go. أود أن أقترح بناء جملة مختلفًا أعتقد أنه يمكن أن يعمل مع قواعد Go الموجودة جيدًا (حتى أنه يحدث لتسليط الضوء على بناء الجملة بشكل جيد في Github Markdown).

النقاط الرئيسية للاقتراح:

  • يبدو أن قواعد Go لديها دائمًا طريقة سهلة لتحديد وقت انتهاء إعلان النوع نظرًا لوجود رمز مميز أو كلمة رئيسية معينة نبحث عنها. إذا كان هذا صحيحًا في جميع الحالات ، فيمكن ببساطة إضافة وسيطات الكتابة بعد أسماء الأنواع نفسها.
  • مثل معظم المقترحات ، يعني نفس المعرف نفس النوع في أي إعلان عن الوظيفة. هذه المعرفات لا تفلت من الإعلان.
  • في معظم المقترحات ، يجب أن تعلن عن وسيطات من النوع العام ، لكنها ضمنية في هذا الاقتراح. يدعي بعض الناس أن هذا يضر بالقراءة أو الوضوح (التضمين أمر سيئ) ، أو يقيد القدرة على تسمية نوع ما ، يتبع الدحض:

    • عندما يتعلق الأمر بإيذاء قابلية القراءة ، أعتقد أنه يمكنك المجادلة في كلتا الحالتين ، الإضافيةأو [T] يضر بالقراءة بنفس القدر عن طريق إحداث الكثير من الضجيج النحوي.

    • يمكن أن يساعد التضمين عند استخدامها بشكل صحيح على أن تكون اللغة أقل إسهابًا. نحن نتجاهل إقرارات النوع باستخدام := طوال الوقت لأن المعلومات التي يخفيها ذلك ببساطة ليست مهمة بما يكفي لتوضيحها في كل مرة.

    • من المحتمل أن تكون تسمية نوع محدد (غير عام) a أو t ممارسة سيئة ، لذلك يفترض هذا الاقتراح أنه من الآمن الاحتفاظ بهذه المعرفات لتكون بمثابة وسيطات من النوع العام. على الرغم من أن هذا قد يتطلب ترحيل الإصلاح ربما؟

package main

import "fmt"

type LinkedList a struct {
  Head *Node a
  Tail *Node a
}

type Node a {
  Next *Node a
  Prev *Node a

  Value a
}

func main() {
  // Not sure about how recursive we could get with the inference
  ll := LinkedList string {
    // The string bit could be inferred
    Head: Node string { Value: "hello world" },
  }
}

func (l *LinkedList a) Append(value a) {
  newNode := &Node{Value: value}

  if l.Tail == nil {
    l.Head = newNode
    l.Tail = l.Head
    return
  }

  l.Tail.Next = newNode
  l.Tail = l.Tail.Next
}

هذا مأخوذ من Gist الذي يحتوي على مزيد من التفاصيل بالإضافة إلى أنواع المجموع المقترحة هنا: https://gist.github.com/aarondl/9b950373642fcf5072942cf0fca2c3a2

هذا ليس اقتراحًا مستبعدًا بالكامل ولا يُقصد به أن يكون كذلك ، فهناك الكثير من المشاكل التي يجب حلها لتكون قادرًا على إضافة الأدوية الجنيسة إلى Go. هذا واحد يعالج فقط بناء الجملة ، وآمل أن نتمكن من إجراء محادثة حول ما إذا كان ما هو مقترح ممكن / مرغوب فيه أم لا.

تضمين التغريدة
يبدو جيدًا بالنسبة لي ، باستخدام بناء الجملة هذا سيكون لدينا:

type Collection a b interface {
   member(c a, e b) Bool
   insert(c a, e b) a
}

func insert(c *LinkedList a, e a) *LinkedList a {
   c.Append(e)
   return c
}

keean هل لك أن تشرح نوع Collection قليلاً. لم أفهمها:

type Collection a b interface {
   member(c a, e b) Bool
   insert(c a, e b) a
}

@ dc0d Collection عبارة عن واجهة تجريد مجموعات _all_ ، لذلك يمكننا إجراء عمليات عامة مثل member و insert والتي ستعمل على أي مجموعة تحتوي على أي نوع بيانات. في ما سبق ، أعطيت مثالاً لتعريف "insert" لنوع LinkedList في المثال السابق:

func insert(c *LinkedList a, e a) *LinkedList a {
   c.Append(e)
   return c
}

يمكننا أيضًا تعريفه لشريحة

func insert(c []a, e a) []a {
   return append(c, e)
}

ومع ذلك ، لا نحتاج حتى إلى نوع الدوال البارامترية مع متغيرات النوع كما هو موضح في aarondl مع النوع متعدد الأشكال a لكي يعمل هذا ، كما يمكنك فقط تحديد الأنواع الملموسة:

func insert(c *LinkedList int, e int) *LinkedList int {
   c.Append(e)
   return c
}

func insert(c *LinkedList float, e float) *LinkedList float {
   c.Append(e)
   return c
}

func insert(c int[], e int) int[] {
   return append(c, e)
}

func insert(c float[], e float) float[] {
   return append(c, e)
}

لذا فإن Collection هو واجهة للتعميم على كل من نوع الحاوية ونوع محتوياتها ، مما يسمح بكتابة الوظائف العامة التي تعمل على جميع مجموعات الحاوية والمحتويات.

لا يوجد سبب يمنعك أيضًا من الحصول على شريحة من المجموعات []Collection حيث ستكون جميع المحتويات عبارة عن أنواع مجموعات مختلفة بأنواع قيم مختلفة ، مما يوفر member و insert تم تعريفه لكل مجموعة .

aarondl نظرًا لأن type LinkedList a هو بالفعل إعلان صالح للنوع ، يمكنني فقط رؤية طريقتين لجعل هذا التحليل غير غامض: جعل سياق القواعد حساسًا (الدخول في مشاكل تحليل C ، ugh) أو استخدام lookahead غير المحدود ( التي تميل قواعد go to تجنبها ، بسبب رسائل الخطأ السيئة في حالة الفشل). قد أكون قد أسيء فهم شيء ما ، لكن المنظمة البحرية الدولية التي تتحدث ضد نهج أقل رمزية.

keean Interfaces في Go تستخدم أساليب وليس وظائف. في الصيغة المحددة التي اقترحتها ، لا يوجد شيء يربط insert بـ *LinkedList للمترجم (في Haskell يتم ذلك عبر إعلانات instance ). من الطبيعي أيضًا أن تغير الأساليب القيمة التي تعمل عليها. لا شيء من هذا هو Show-Stopper ، فقط للإشارة إلى أن بناء الجملة الذي تقترحه لا يعمل بشكل جيد مع Go. ربما أكثر شيء من هذا القبيل

type Collection e interface {
    Element(e) book
    Insert(e)
}

func (l *(LinkedList e)) Element(el e) book {
    // ...
}

func (l* (LinkedList e)) Insert(el e) {
    // ...
}

والذي يوضح أيضًا بضعة أسئلة أخرى فيما يتعلق بكيفية تحديد نطاق معلمات النوع وكيفية تحليل ذلك.

aarondl ، هناك أيضًا المزيد من الأسئلة التي سأطرحها حول اقتراحك. على سبيل المثال ، لا يسمح بالقيود ، لذلك تحصل فقط على تعدد الأشكال غير المقيد. وهذا ، بشكل عام ، ليس مفيدًا حقًا ، حيث لا يُسمح لك بفعل أي شيء بالقيم التي تحصل عليها (على سبيل المثال ، لا يمكنك تنفيذ المجموعة بخريطة ، حيث لا تعد جميع الأنواع مفاتيح خرائط صالحة). ماذا يجب أن يحدث عندما يحاول شخص ما القيام بشيء من هذا القبيل؟ إذا كان خطأ وقت الترجمة ، فهل يشكو من إنشاء مثيل (رسائل خطأ C ++ في المقدمة) أم في التعريف (لا يمكنك فعل أي شيء أساسًا ، لأنه لا يوجد شيء يعمل مع جميع الأنواع)؟

keean ما زلت أخفق في فهم كيفية تقييد a ليكون قائمة (أو شريحة أو أي مجموعة أخرى). هل هذه قواعد خاصة للمجموعات تعتمد على السياق؟ إذا كان الأمر كذلك فما هي قيمتها؟ لا يمكن التصريح عن الأنواع المعرفة من قبل المستخدم بهذه الطريقة.

Merovius هل هذا يعني أن Go لا يمكنه القيام بالإرسال المتعدد ، ويجعل الوسيطة الأولى من "الوظيفة" خاصة؟ يشير هذا إلى أن الأنواع المرتبطة ستكون مناسبة بشكل أفضل من الواجهات متعددة المعلمات. شيء من هذا القبيل:

type Collection interface {
   type Element
   Member(e Element) Bool
   Insert(e Element) Collection
}

type IntSlice struct {
    value []Int,
}

type IntSlice.Element = Int

func (IntSlice) Member(e Int) Bool {...}
func (IntSlice) Insert(e Int) IntSlice {...}

func useIt(c Collection, e Collection.Element) {...}

ومع ذلك ، لا يزال هذا يواجه مشاكل لأنه لا يوجد شيء يقيد المجموعتين بأن تكونا من نفس النوع ... سينتهي بك الأمر في حاجة إلى شيء مثل:

func[A] useIt(c A, e A.Element) requires A:Collection

لمحاولة شرح الاختلاف ، تحتوي الواجهات متعددة المعلمات على أنواع _input_ إضافية تشارك في تحديد المثيل (ومن ثم يكون الاتصال مع إرسال متعدد) ، في حين أن الأنواع المرتبطة هي أنواع _output_ ، فقط نوع المستقبل يشارك في تحديد المثيل ، ثم تعتمد الأنواع المرتبطة على نوع جهاز الاستقبال.

@ dc0d a و b معلمات نوع للواجهة ، تمامًا كما هو الحال في فئة نوع Haskell. لكي يتم اعتبار شيء ما على أنه Collection ، يجب أن يحدد الطرق التي تتطابق مع الأنواع الموجودة في الواجهة حيث يمكن أن يكون a و b من أي نوع. ومع ذلك ، كما أشار Merovius ، فإن واجهات Go تعتمد على الأسلوب ، ولا تدعم الإرسال المتعدد ، لذا قد لا تكون الواجهات متعددة المعلمات مناسبة. مع نموذج طريقة الإرسال الفردي Go ، ثم وجود أنواع مرتبطة في الواجهات ، بدلاً من المعلمات المتعددة يبدو أنه مناسب بشكل أفضل. ومع ذلك ، فإن عدم وجود إرسال متعدد يجعل تنفيذ وظائف مثل unify(x, y) صعبًا ، ويجب عليك استخدام نمط الإرسال المزدوج الذي ليس جيدًا جدًا.

لشرح الشيء متعدد المعلمات بشكل أكبر قليلاً:

type Cloneable[A] interface {
   clone(x A) A
}

هنا يرمز a لأي نوع ، نحن لا نهتم بما هو عليه ، طالما تم تحديد الوظائف الصحيحة فنحن نعتبره Cloneable . سننظر إلى الواجهات على أنها قيود على الأنواع بدلاً من الأنواع نفسها.

func clone(x int) int {...}

لذلك في حالة "clone" نقوم باستبدال a بـ int في تعريف الواجهة ، ويمكننا استدعاء clone إذا نجح الاستبدال. هذا يتناسب بشكل جيد مع هذا الترميز:

func[A] test(x A) A requires Cloneable[A] {...}

هذا يعادل:

type Cloneable interface {
   clone() Cloneable
}

لكنه يعلن عن وظيفة وليست طريقة ، ويمكن تمديده بمعلمات متعددة. إذا كانت لديك لغة ذات إرسال متعدد ، فلا يوجد شيء مميز في الوسيطة الأولى للدالة / الطريقة ، فلماذا تكتبها في مكان مختلف.

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

@ Merovius شكرا لإلقاء نظرة على الاقتراح. اسمحوا لي أن أحاول معالجة مخاوفك. أنا حزين لأنك رفضت الاقتراح قبل أن نناقشه أكثر ، وآمل أن أتمكن من تغيير رأيك - أو ربما يمكنك تغيير رأيي :)

نظرة غير محدودة:
لذا كما ذكرت في الاقتراح ، يبدو حاليًا أن Go grammar لديها طريقة جيدة لاكتشاف "نهاية" كل شيء تقريبًا بشكل نحوي. وما زلنا بسبب الحجج العامة الضمنية. حرف واحد صغير هو البناء النحوي الذي ينشئ تلك الوسيطة العامة - أو أيًا كان ما قررناه لجعل هذا الرمز المميز المضمّن ، ربما نتراجع إلى شيء رمزي مثل @a في الاقتراح إذا أحببنا بناء الجملة بشكل كافٍ ولكنه ليس كذلك ممكن نظرًا لصعوبة المترجم بدون الرموز ، على الرغم من أن الاقتراح يفقد الكثير من السحر بمجرد القيام بذلك.

بغض النظر عن المشكلة مع type LinkedList a بموجب هذا الاقتراح ليس بهذه الصعوبة لأننا نعلم أن a هي وسيطة من النوع العام ، وبالتالي فإن هذا سيفشل مع وجود خطأ في المترجم مثل type LinkedList فشل prog.go:3:16: expected type, found newline (and 1 more errors) . لم يتم نشر المنشور الأصلي حقًا وقله ولكن لا يُسمح لك بتسمية نوع ملموس [a-z]{1} بعد الآن والذي أعتقد أنه يحل هذه المشكلة وهو تضحية أعتقد أننا جميعًا سنكون بخير صنع (يمكنني فقط رؤية العيوب في إنشاء أنواع حقيقية بأسماء من حرف واحد في كود Go اليوم).

إنه مجرد تعدد أشكال غير مقيد
السبب في أنني حذفت أي نوع من السمات أو قيود الحجة العامة هو أنني أشعر أن هذا هو دور الواجهات في Go ، إذا كنت ترغب في القيام بشيء ذي قيمة ، فيجب أن تكون هذه القيمة نوع واجهة وليس نوعًا عامًا بالكامل. أعتقد أن هذا الاقتراح يلعب بشكل جيد مع الواجهات أيضًا.

بموجب هذا الاقتراح ، لا يزال لدينا نفس المشكلة التي نواجهها الآن مع عوامل التشغيل مثل + لذلك لا يمكنك إنشاء وظيفة إضافة عامة لجميع الأنواع الرقمية ، ولكن يمكنك قبول وظيفة إضافة عامة كوسيطة. ضع في اعتبارك ما يلي:

func Sort(slice []a, compare func (a, a) bool) { ... }

أسئلة حول تحديد النطاق

لقد أعطيت مثالا هنا:

type Collection e interface {
    Element(e) book
    Insert(e)
}

func (l *(LinkedList e)) Element(el e) book {
    // ...
}

func (l* (LinkedList e)) Insert(el e) {
    // ...
}

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

keean هذا مثير للاهتمام للغاية على الرغم من أنه كما أوضح الآخرون ، يجب عليك تغيير ما أظهرته هناك حتى تتمكن بالفعل من تنفيذ الواجهات (حاليًا في المثال الخاص بك لا توجد طرق مع أجهزة الاستقبال ، فقط الوظائف). أحاول التفكير أكثر في كيفية تأثير ذلك على اقتراحي الأصلي.

حرف واحد صغير هو البناء النحوي الذي يخلق تلك الوسيطة العامة

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

بموجب هذا الاقتراح ، لا يزال لدينا نفس المشكلة التي نواجهها الآن مع مشغلين مثل +

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

إذا كنت ترغب في القيام بشيء ذي قيمة ، فيجب أن تكون هذه القيمة نوع واجهة وليست نوعًا عامًا بالكامل.

السببان الرئيسيان وراء رغبة الناس في استخدام الأدوية الجنيسة ، هما الأداء (تجنب التفاف الواجهات) وسلامة النوع (التأكد من استخدام نفس النوع في أماكن مختلفة ، مع عدم الاهتمام بأي منها). يبدو أن هذا يتجاهل تلك الأسباب.

يمكنك قبول دالة إضافة عامة كوسيطة.

صحيح. لكن غير مريح إلى حد ما. ضع في اعتبارك عدد الشكاوى هناك حول sort API. بالنسبة للكثير من الحاويات العامة ، يبدو أن مقدار الوظائف التي يجب على المتصل تنفيذها وتمريرها أمر باهظ. ضع في اعتبارك ، كيف سيبدو تنفيذ container/heap في ظل هذا الاقتراح وكيف سيكون أفضل من التنفيذ الحالي ، من حيث بيئة العمل؟ يبدو أن المكاسب لا تذكر هنا ، في أحسن الأحوال. سيتعين عليك تنفيذ المزيد من الوظائف التافهة (وتكرارها / الرجوع إليها في كل موقع استخدام) ، وليس أقل.

تضمين التغريدة

التفكير في هذه النقطة من aarondl

يمكنك قبول دالة إضافة عامة كوسيطة.

سيكون من الأفضل أن يكون لديك واجهة قابلة للإضافة للسماح بالحمل الزائد على الإضافة ، مع الأخذ في الاعتبار بعض القواعد النحوية لتحديد مشغلي infix:

type Addable interface {
   + (x Addable, y Addable) Addable
}

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

type Addable[A] interface {
   + (x A, y A) A
}

بعد ذلك ، ستحتاج أيضًا إلى Go to do Multiple إيفاد مما يعني أن جميع الوسائط في وظيفة ما يتم التعامل معها مثل جهاز استقبال لمطابقة الواجهة. لذلك في المثال أعلاه ، أي نوع هو Addable إذا كانت هناك دالة + مُعرَّفة عليها والتي تلبي تعريفات الوظيفة في تعريف الواجهة.

لكن بالنظر إلى هذه التغييرات ، يمكنك الآن كتابة:

type S struct {
   value: int
}

func (+) (x S, y S) S {
   return S {
      value: x.value + y.value
   }
}

func main() {
    println(S {value: 27} + S {value: 5})
}

بالطبع قد يكون التحميل الزائد للوظيفة والإرسال المتعدد شيئًا لا يريده الأشخاص أبدًا في Go ، ولكن بعد ذلك ستكون أشياء مثل تحديد الحساب الأساسي على أنواع محددة من قبل المستخدم مثل المتجهات والمصفوفات والأرقام المعقدة وما إلى ذلك ، مستحيلة دائمًا. كما قلت أعلاه ، فإن "الأنواع المرتبطة" على الواجهات ستسمح ببعض الزيادة في قدرة البرمجة العامة ، ولكن ليس التعميم الكامل. هل الإرسال المتعدد (ومن المفترض أن يؤدي التحميل الزائد للوظيفة) إلى شيء يمكن أن يحدث في Go؟

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

قد يعتبر البعض أن الميزة :) AFAIR هناك بعض الاقتراح أو الخيط العائم في مكان ما لمناقشة ما إذا كان ينبغي. FWIW ، أعتقد أن هذا - مرة أخرى - شرود خارج الموضوع. إن التحميل الزائد على المشغل (أو "كيفية جعل أفكار Go more Haskell" العامة) ليس في الحقيقة هو الهدف من هذه المشكلة :)

هل الإرسال المتعدد (ومن المفترض أن يؤدي التحميل الزائد للوظيفة) إلى شيء يمكن أن يحدث في Go؟

لا تستصعب شئ أبدا. لا أتوقع ذلك على الرغم من ذلك ، شخصيًا.

تضمين التغريدة

قد يعتبر البعض أن هذه ميزة :)

بالتأكيد ، وإذا لم يفعل Go ذلك ، فهناك لغات أخرى :-) ليس من الضروري أن يكون Go هو كل شيء للجميع. كنت أحاول فقط إنشاء مجال للأدوية الجنيسة في Go. ينصب تركيزي على إنشاء لغات عامة تمامًا ، حيث إنني نفور من تكرار نفسي والنمط المعياري (وأنا لا أحب وحدات الماكرو). إذا كان لدي فلس مقابل كل مرة اضطررت فيها إلى كتابة قائمة مرتبطة أو شجرة في "C" لبعض أنواع البيانات المحددة. إنه في الواقع يجعل بعض المشاريع مستحيلة بالنسبة لفريق صغير بسبب حجم التعليمات البرمجية التي يجب الاحتفاظ بها في رأسك لفهمها ، ثم الحفاظ عليها من خلال التغييرات. أعتقد أحيانًا أن الأشخاص الذين لا يحتاجون إلى الأدوية الجنيسة لم يكتبوا برنامجًا كبيرًا بما يكفي بعد. بالطبع يمكنك بدلاً من ذلك أن يكون لديك فريق كبير من المطورين يعملون على شيء ما ويكون كل مطور مسؤولاً فقط عن جزء صغير من إجمالي الكود ، لكنني مهتم بجعل مطور واحد (أو فريق صغير) فعالاً قدر الإمكان.

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

يبدو أن شيئًا كهذا هو النوع الصحيح من الأشياء:

type Collection interface {
   type Element
   Member(e Element) Bool
   Insert(e Element) Collection
}

type IntSlice struct {
    value []Int,
}

type IntSlice.Element = Int

func (IntSlice) Member(e Int) Bool {...}
func (IntSlice) Insert(e Int) IntSlice {...}

func useIt<T>(c T, e T.Element) requires T:Collection {...}

ثم سيكون هناك قرار في التنفيذ ما إذا كان سيتم استخدام الأنواع البارامترية أو الأنواع الكمية عالميًا. مع الأنواع البارامترية (مثل Java) ، فإن الوظيفة "العامة" ليست في الواقع وظيفة ولكن نوعًا ما من قالب وظيفة النوع الآمن ، وعلى هذا النحو لا يمكن تمريرها كوسيطة إلا إذا كانت معلمة النوع الخاصة بها متوفرة على النحو التالي:

f(useIt) // not okay with parametric types
f(useIt<List>) // okay with parametric types

باستخدام الأنواع المُقاسة كميًا عالميًا ، يمكنك تمرير useIt كوسيطة ، ويمكن بعد ذلك تزويدها بمعامل نوع داخل f . سبب تفضيل الأنواع البارامترية هو أنه يمكنك تعديل تعدد الأشكال في وقت التجميع مما يعني عدم توضيح الوظائف متعددة الأشكال في وقت التشغيل. لست متأكدًا من أن هذا مصدر قلق لـ Go ، لأن Go يقوم بالفعل بإرسال وقت التشغيل على الواجهات ، وطالما أن معلمة النوع لـ useIt تنفذ المجموعة ، يمكنك الإرسال إلى جهاز الاستقبال الصحيح في وقت التشغيل ، لذلك عالمي ربما يكون القياس الكمي هو الطريقة الصحيحة لـ Go.

أتساءل ، ذكرت SFINAE فقط بواسطةbcmills. لم يذكر حتى في الاقتراح (على الرغم من أن Sort موجود كمثال).
كيف سيبدو Sort للشريحة والقائمة المرتبطة بعد ذلك؟

تضمين التغريدة
لا يمكنني معرفة كيف يمكن تعريف مجموعة "شريحة" عامة باقتراحك. يبدو أنك تقوم بتعريف "IntSlice" الذي قد ينفذ "Collection" (على الرغم من أن "الإدخال" يُرجع نوعًا مختلفًا عن النوع الذي تريده الواجهة) ، ولكن هذه ليست "شريحة" عامة ، كما يبدو أنها مخصصة فقط لـ ints ، وطريقة التنفيذ هي فقط ل ints. هل نحتاج إلى تحديد تنفيذ محدد لكل نوع؟

أعتقد أحيانًا أن الأشخاص الذين لا يحتاجون إلى الأدوية الجنيسة لم يكتبوا برنامجًا كبيرًا بما يكفي بعد.

يمكنني أن أؤكد لكم أن الانطباع خاطئ. و FWIW ، ISTM أن "الجانب الآخر" يضع "عدم رؤية الحاجة" في نفس المجموعة مثل "عدم رؤية الاستخدام". أرى الاستخدام ولا أدحضه. أنا لا أرى الحاجة حقًا ، رغم ذلك. أنا أبلي بلاءً حسنًا بدون ، حتى في قواعد الرموز الكبيرة.

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

نظرًا أيضًا لمشاكل التحليل مع اقتراح aarondl .

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

urandom

لا يمكنني معرفة كيف يمكن تعريف مجموعة "شريحة" عامة باقتراحك.

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

type<T> []T.Element = Int

func<T> ([]T) Member(e T) Bool {...}
func<T> ([]T) Insert(e T) Collection {...}

ومع ذلك ، يتعين عليك الآن أيضًا التعامل مع التخصص ، لأنه يمكن لشخص ما تحديد النوع والأساليب المرتبطة بـ []int الأكثر تخصصًا وسيتعين عليك التعامل مع أي منها تستخدم. عادةً ما تستخدم المثال الأكثر تحديدًا ، لكنه يضيف طبقة أخرى من التعقيد.

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

تضمين التغريدة

أنا لا أرى الحاجة حقًا ، رغم ذلك. أنا أبلي بلاءً حسنًا بدون ، حتى في قواعد الرموز الكبيرة.

إذا كان بإمكانك التعامل مع برنامج 100،000 خط ، فستتمكن من القيام بأكثر من 100،000 سطر عام مما يمكنك القيام به مع 100،000 سطر غير عام (بسبب التكرار). لذلك قد تكون مطورًا فائقًا قادرًا على التعامل مع قواعد أكواد كبيرة جدًا ، ولكن لا يزال بإمكانك تحقيق المزيد باستخدام قاعدة بيانات عامة كبيرة جدًا حيث ستلغي التكرار. سوف يتوسع هذا البرنامج العام إلى برنامج غير عام أكبر. يبدو لي أنك لم تصل إلى حد التعقيد بعد.

ومع ذلك ، أعتقد أنك على حق ، فإن `` الحاجة '' قوية للغاية ، وأنا سعيد بكتابة رمز go ، مع إحباط عرضي فقط بسبب نقص الأدوية الجنيسة ، ويمكنني التغلب على هذا ببساطة عن طريق كتابة المزيد من التعليمات البرمجية ، وفي Go يكون هذا الرمز مباشرًا بشكل واضح والحرفية.

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

أنا أتفق مع هذا.

ستكون قادرًا على القيام بأكثر من 100000 سطر عام أكثر مما يمكنك القيام به باستخدام 100000 سطر غير عام (بسبب التكرار)

أشعر بالفضول ، من مثالك الافتراضي ، ما هي النسبة المئوية لهذه الخطوط التي ستكون دالة عامة؟
من واقع خبرتي ، هذا أقل من 2٪ (من مصدر كود بـ 115k LOC) ، لذلك لا أعتقد أن هذه حجة جيدة ما لم تكتب مكتبة لـ "المجموعات"

أتمنى أن نحصل في النهاية على الأدوية الجنيسة

تضمين التغريدة

فيما يتعلق بادعائك بأنه لا يمكنك فعل هذا المثال في هاسكل ، فإليك الكود:

هذا الرمز لا يعادل أخلاقيًا الرمز الذي كتبته. يقدم نوع غلاف جديد Cloneable بالإضافة إلى واجهة ICloneable. لم يكن كود Go بحاجة إلى غلاف ؛ ولا اللغات الأخرى التي تدعم التصنيف الفرعي.

تضمين التغريدة

هذا الرمز لا يعادل أخلاقيًا الرمز الذي كتبته. يقدم نوع غلاف جديد Cloneable بالإضافة إلى واجهة ICloneable.

أليس هذا ما يفعله هذا الرمز:

type Cloneable interface {...}

إنه يقدم نوع بيانات "قابل للاستنساخ" مشتق من الواجهة. لا ترى "ICloneable" لأنه ليس لديك تصاريح مثيلات للواجهات ، فأنت تقوم فقط بتعريف الطرق.

هل يمكنك اعتباره تصنيفًا فرعيًا عندما لا يلزم أن تكون الأنواع التي تنفذ الواجهة متوافقة هيكليًا؟

keean سأعتبر أن Cloneable مجرد نوع ، وليس "نوع بيانات" حقًا. في لغة مثل Java ، لن يكون هناك أساسًا أي تكلفة إضافية للتجريد Cloneable ، لأنه لن يكون هناك غلاف ، على عكس التعليمات البرمجية الخاصة بك.

يبدو لي أن طلب التشابه البنيوي بين الأنواع التي تنفذ الواجهة أمر مقيد وغير مرغوب فيه ، لذلك أنا في حيرة من أمري بشأن ما تفكر فيه هنا.

تضمين التغريدة
أنا أستخدم النوع ونوع البيانات بالتبادل. أي نوع يمكن أن يحتوي على بيانات هو نوع بيانات.

لأنه لن يكون هناك غلاف ، على عكس التعليمات البرمجية الخاصة بك.

يوجد دائمًا غلاف لأن أنواع Go دائمًا ما تكون محاصرًا ، لذلك يوجد الغلاف حول كل شيء. يحتاج Haskell إلى أن يكون الغلاف واضحًا لأنه يحتوي على أنواع غير معبأة.

التشابه البنيوي بين الأنواع التي تنفذ الواجهة ، لذلك أنا في حيرة من أمري بشأن ما تفكر فيه هنا.

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

أرى ما تقصده على الرغم من ذلك ، والذي قد أصفه على أنه اعتبار الواجهة فئة أساسية مجردة ، وليست واجهة ، مع نوع من علاقة النوع الاسمي الضمني مع أي نوع يقوم بتنفيذ الأساليب المطلوبة.

أعتقد في الواقع أن Go يناسب كلا النموذجين الآن ، ويمكن أن يذهب في أي من الاتجاهين من هنا ، لكنني أقترح أن وصفه بواجهة وليس فئة يوحي بطريقة تفكير غير فرعية.

keean أنا لا أفهم تعليقك. أولاً ، تخبرني أنك غير موافق وأنني "لم أستوفِ حد التعقيد الخاص بي بعد" ثم تخبرني أنك موافق (في أن كلمة "حاجة" كلمة قوية جدًا). أعتقد أيضًا أن حجتك خاطئة (أنت تفترض أن LOC هو المقياس الأساسي للتعقيد وأن كل سطر من التعليمات البرمجية متساوٍ). لكن الأهم من ذلك كله ، لا أعتقد أن "من يكتب برامج أكثر تعقيدًا" هو في الحقيقة خط مثمر للمناقشة. كنت أحاول فقط التوضيح ، أن الحجة "إذا كنت لا تتفق معي ، فهذا يعني أنك لا تعمل على حل مشاكل صعبة أو مثيرة للاهتمام" ليست مقنعة ولا تؤتي ثمارها بحسن نية. آمل أن تثق فقط في أن الناس يمكن أن يختلفوا معك حول أهمية هذه الميزة مع كونهم مؤهلين بنفس القدر ويقومون بأشياء مثيرة للاهتمام.

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

أستطيع أن أعتقد أن الأدوية الجنيسة تساعدني في كتابة برامج أكثر تعقيدًا بقدراتي العقلية المحدودة ، وأقر أيضًا أنني لست "بحاجة" إلى الأدوية الجنيسة. إنها مسألة درجة. لا يزال بإمكاني البرمجة بدون الأدوية الجنيسة ، لكن لا يمكنني بالضرورة كتابة برامج بنفس التعقيد.

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

@ Merovius شكرا على الحوار المستمر.

| السببان الرئيسيان وراء رغبة الناس في استخدام الأدوية الجنيسة ، هما الأداء (تجنب التفاف الواجهات) وسلامة النوع (التأكد من استخدام نفس النوع في أماكن مختلفة ، مع عدم الاهتمام بأي منها). يبدو أن هذا يتجاهل تلك الأسباب.

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

| صحيح. لكن غير مريح إلى حد ما. ضع في اعتبارك عدد الشكاوى التي تتعلق بواجهة برمجة التطبيقات (API). بالنسبة للكثير من الحاويات العامة ، يبدو أن مقدار الوظائف التي يجب على المتصل تنفيذها وتمريرها أمر باهظ. ضع في اعتبارك ، كيف سيبدو تنفيذ الحاوية / الكومة في ظل هذا الاقتراح وكيف سيكون أفضل من التنفيذ الحالي ، من حيث بيئة العمل؟ يبدو أن المكاسب لا تذكر هنا ، في أحسن الأحوال. سيتعين عليك تنفيذ المزيد من الوظائف التافهة (وتكرارها / الرجوع إليها في كل موقع استخدام) ، وليس أقل.

أنا في الواقع لست مهتمًا بهذا على الإطلاق. لا أعتقد أن مقدار الوظائف سيكون باهظًا ، لكنني بالتأكيد منفتح على رؤية بعض الأمثلة المضادة. تذكر أن واجهة برمجة التطبيقات (API) التي اشتكى الأشخاص منها لم تكن واجهة يجب عليك توفير وظيفة لها ولكنها الأصلية هنا: https://golang.org/pkg/sort/#Interface حيث تحتاج إلى إنشاء نوع جديد كان ببساطة شريحتك + اكتب ، ثم نفذ 3 طرق عليها. في ضوء الشكاوى والألم المرتبط بهذه الواجهة ، تم إنشاء ما يلي: https://golang.org/pkg/sort/#Slice ، ليس لدي أي مشكلة مع واجهة برمجة التطبيقات هذه وسنستعيد عقوبات الأداء لهذا بموجب الاقتراح الذي نناقشه ببساطة عن طريق تغيير التعريف إلى func Slice(slice []a, less func(a, a) bool) .

فيما يتعلق بهيكل البيانات container/heap بغض النظر عن الاقتراح العام الذي تقبله والذي يحتاج إلى إعادة كتابة كاملة. container/heap تمامًا مثل حزمة sort توفر فقط خوارزميات أعلى بنية البيانات الخاصة بك ، ولكن لا تمتلك أي من الحزمة بنية البيانات أبدًا لأنه بخلاف ذلك سيكون لدينا []interface{} و التكاليف المرتبطة بذلك. من المفترض أننا سنغيرها لأنك ستتمكن من الحصول على Heap تمتلك شريحة من نوع ملموس بفضل الأدوية الجنيسة ، وهذا صحيح في أي من العروض التي رأيتها هنا (بما في ذلك المقترحات الخاصة بي) .

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

ضع في اعتبارك تعريف جدول التجزئة التالي:

// Hasher turns a key into a hash
type Hasher interface {
  func Hash() []byte
}

type HashTable v struct {
   Keys   []Hasher
   Values []v
}

// Note that the generic arguments must be repeated here and immediately
// understood without reading another line of code, which to me
// is a readability win over the sudden appearance of the K and V which are
// defined elsewhere in the code in the example below. This is of course because
// the tokenized type declarations with constraints are fairly painful in general
// and repeating them everywhere is simply too much.
func (h (*HashTable v)) Insert(key Hasher, value v) { ... }

هل نقول أن []Hasher ليس بداية بسبب مخاوف متعلقة بالأداء / التخزين وأنه من أجل الحصول على تطبيق Generics ناجح في Go ، يجب أن يكون لدينا شيء مثل التالي؟

// Without selecting another proposal I have no idea how the constraint might be defined or implemented so let's just pretend
type [K: Hasher, V] HashTable a struct {
   Keys   []K
   Values []V
}

func (h *HashTable) Insert(key K, value V) { ... }

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

ربما ننظر إلى ما اقترحته بشكل مختلف تمامًا ، لأنه من وجهة نظري يفعل كلا الأمرين بقدر ما أستطيع أن أقول؟

كلمة "هذا" في القسم الذي تقتبس منه تشير إلى استخدام الواجهات. المشكلة ليست ، أن اقتراحك لا يعمل أيضًا ، إنه أن اقتراحك لا يسمح بتعدد الأشكال المقيّد ، والذي يستبعد معظم الاستخدامات بالنسبة لهم. والبديل الذي اقترحته لذلك هو الواجهات ، التي لا تعالج حقًا حالة الاستخدام الأساسية للأدوية أيضًا (بسبب الشيئين اللذين ذكرتهما).

على سبيل المثال ، اقتراحك (كما هو مكتوب في الأصل) لم يسمح فعليًا بكتابة خريطة عامة من أي نوع ، حيث سيتطلب ذلك أن تكون قادرًا على الأقل على مقارنة المفاتيح باستخدام == (وهو قيد ، لذا فإن تنفيذ تتطلب الخريطة تعدد الأشكال المقيد).

في ضوء الشكاوى والألم المرتبط بهذه الواجهة ، تم إنشاء ما يلي: https://golang.org/pkg/sort/#Slice

لاحظ أن هذه الواجهة لا تزال غير ممكنة في اقتراحك للأدوية ، لأنها تعتمد على انعكاس الطول والمبادلة (لذا ، مرة أخرى ، لديك قيود على عمليات الشرائح). حتى إذا قبلنا واجهة برمجة التطبيقات هذه باعتبارها الحد الأدنى لما يجب أن تتمكن الأدوية الجنيسة من تحقيقه (لن يتمكن الكثير من الأشخاص. لا يزال هناك الكثير من الشكاوى حول عدم وجود أمان من النوع في واجهة برمجة التطبيقات تلك) ، فلن يمر اقتراحك هذا الشريط.

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

سوف نسترد غرامات الأداء لهذا بموجب الاقتراح الذي نناقشه ببساطة عن طريق تغيير التعريف إلى func Slice (slice [] a، less func (a، a) bool).

هذا هو API القديم بالرغم من ذلك. أنت تقول "اقتراحي لا يسمح بتعدد الأشكال المقيّد ، لكن هذه ليست مشكلة ، لأننا لا نستطيع استخدام الأدوية الجنيسة وبدلاً من ذلك استخدام الحلول الحالية (انعكاس / واجهات) بدلاً من ذلك". حسنًا ، لا يبدو أن الرد على "اقتراحك لا يسمح بأبسط حالات الاستخدام التي يريد الأشخاص الأدوية الجنيسة لها" مع "يمكننا فقط القيام بالأشياء التي يفعلها الأشخاص بالفعل دون استخدام الأدوية الجنيسة لحالات الاستخدام الأساسية" في أي مكان ، TBH. اقتراح الأدوية الجنيسة الذي لا يساعدك في كتابة حتى أنواع الحاويات الأساسية ، الفرز ، الحد الأقصى ... فقط لا يبدو أنه يستحق ذلك.

هذا صحيح في ظل أي من المقترحات التي رأيتها هنا (بما في ذلك مقترحاتي).

تتضمن معظم مقترحات الأدوية الجنيسة طريقة ما لتقييد معلمات النوع. أي للتعبير عن "يجب أن تحتوي معلمة النوع على طريقة أقل" ، أو "يجب أن تكون معلمة النوع قابلة للمقارنة". تفضلوا بقبول فائق الاحترام - AFAICT - لا.

ضع في اعتبارك تعريف جدول التجزئة التالي:

تعريفك غير مكتمل. أ) يحتاج نوع المفتاح أيضًا إلى المساواة و ب) لا تمنع استخدام أنواع مفاتيح مختلفة. أي أن هذا سيكون قانونيًا:

type hasherA uint64

func (a hasherA) Hash() []byte {
    b := make([]byte, 8)
    binary.BigEndian.PutUint64(b, uint64(a))
    return b
}

type hasherB string

func (b hasherB) Hash() []byte {
    return []byte(b)
}

h := new(HashTable int)
h.Insert(hasherA(42), 1)
h.Insert(hasherB("Hello world"), 2)

لا ينبغي أن يكون قانونيًا ، لأنك تستخدم أنواعًا مختلفة من المفاتيح. على سبيل المثال ، لم يتم فحص نوع الحاوية إلى الدرجة التي يريدها الناس. تحتاج إلى تحديد معلمات hashtable على كل من نوع المفتاح والقيمة

type HashTable k v struct {
    Keys []k
    Values []v
}

func (h *(HashTable k v)) Insert(key k, value v) {
    // You can't actually do anything with k, as it's unconstrained. i.e. you can't hash it, compare it…
    // Implementing this is impossible in your proposal.
}

// If it weren't impossible, you'd get this:
h := new(HashTable hasherA int)
h[hasherA(42)] = 1
h[hasherB("Hello world")] = 2 // compile error - can't use hasherB as hasherA

أو ، إذا كان ذلك مفيدًا ، تخيل أنك تحاول تنفيذ مجموعة تجزئة. ستواجه نفس المشكلة ولكن الآن الحاوية الناتجة لا تحتوي على أي فحص إضافي للنوع يزيد عن interface{} .

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

أنه من أجل الحصول على تطبيق Generics ناجح في Go ، يجب أن يكون لدينا شيء مشابه لما يلي؟

إنه على الأقل ما أشعر به حيال ذلك ، نعم ، إلى حد كبير. إذا كان الاقتراح لا يسمح بكتابة حاويات آمنة من النوع أو الفرز أو ... فإنه لا يضيف حقًا أي شيء إلى اللغة الحالية يكون مهمًا بما يكفي لتبرير التكلفة.

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

لاحظ أن هذه الواجهة لا تزال غير ممكنة في اقتراحك للأدوية ، لأنها تعتمد على انعكاس الطول والمبادلة (لذا ، مرة أخرى ، لديك قيود على عمليات الشرائح). حتى إذا قبلنا واجهة برمجة التطبيقات هذه باعتبارها الحد الأدنى لما يجب أن تتمكن الأدوية الجنيسة من تحقيقه (لن يتمكن الكثير من الأشخاص. لا يزال هناك الكثير من الشكاوى حول عدم وجود أمان من النوع في واجهة برمجة التطبيقات تلك) ، فلن يمر اقتراحك هذا الشريط.

من الواضح عند قراءة هذا أنك أساءت فهم الطريقة التي ستعمل / يجب أن تعمل بها الشرائح العامة بموجب هذا الاقتراح. من خلال سوء الفهم هذا توصلت إلى نتيجة خاطئة مفادها أن "هذه الواجهة لا تزال غير ممكنة في اقتراحك". تحت أي اقتراح يجب أن تكون شريحة عامة ممكنة ، هذا ما أعتقده . و len() في العالم كما رأيته سيتم تعريفه على النحو التالي: func len(slice []a) ، وهو عبارة عن وسيطة شريحة عامة مما يعني أنه يمكن حساب الطول بطريقة غير انعكاسية لأي شريحة. هذا هو جزء كبير من هذا الاقتراح كما قلت أعلاه (التلاعب السهل بالشريحة) وأنا آسف لأنني لم أتمكن من نقل ذلك جيدًا من خلال الأمثلة التي قدمتها والجوهر الذي قدمته. يجب أن تكون الشريحة العامة قابلة للاستخدام بسهولة مثل []int اليوم ، سأقول مرة أخرى أن أي اقتراح لا يعالج هذا (مقايضات الشرائح / المصفوفات ، التخصيص ، len ، cap ، إلخ. ) يقصر في رأيي.

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

أفضل مثال مضاد هو وظيفة الفرز التي ناقشناها سابقًا.

type Sort(slice []a:Lesser, less func(a:Lesser, a:Lesser)) { ... }

كما ترى ، لا توجد طريقة جيدة لتحقيق ذلك ، وتبدأ مناهج الرمز المميز للبريد العشوائي في Generics في الظهور بشكل أفضل مرة أخرى. من أجل تحديد القيود المفروضة على هذه ، نحتاج إلى تغيير شيئين من الاقتراح الأصلي:

  • يجب أن تكون هناك طريقة للإشارة إلى نوع وسيطة ومنحها قيودًا.
  • يجب أن تستمر القيود لفترة أطول من تعريف واحد ، ربما يكون هذا النطاق نوعًا ، وربما يكون هذا النطاق عبارة عن ملف (يبدو الملف معقولًا بالفعل).

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

// Decorator style, follows the definition of the type thorugh all
// of it's methods.
<strong i="14">@a</strong>: Lesser, Hasher, Equaler
func Sort(slice []a) { ... }
<strong i="15">@k</strong>: Equaler, Hasher
type HashTable k v struct

// Inline, follows the definition of the type through
// all of it's methods.
func [a: Hasher, Equaler] Sort(slice []a) { ... }
type [k: Hasher, Equaler] HashTable k v struct

// File-scope global style, if k appears as a generic argument
// it's constrained by this that appears at the top of the file underneath
// the imports but before any other code.
<strong i="16">@k</strong>: Equaler, Hasher

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

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

func map(slice []a, mapper func(a) b) {
  for i := range slice {
    slice[i] = mapper(slice[i])
  }
}

type Result a b struct {
  Ok  a
  Err b
}

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

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

على سبيل المثال ، ماذا لو كانت الوظيفة التي تستدعي الخريطة عامة أيضًا؟ ماذا لو كانت الوظيفة التي تستدعي عام وما إلى ذلك. كيف نحدد مصمم الخرائط إذا لم نكن نعرف حتى الآن نوع a ؟

func m(slice []a) []b {
   mapper := func(x a) b {...}
   return map(slice, mapper)
}

ما هي الوظائف التي يمكننا استدعاؤها على x عند محاولة تعريف mapper ؟

keean أفهم الغرض من القيود ووظيفتها. أنا ببساطة لا أقدرها كثيرًا مثل الأشياء البسيطة مثل هياكل الحاويات العامة (وليس الحاويات العامة إذا جاز التعبير) والشرائح العامة ، وبالتالي لم أدرجها في الاقتراح الأصلي.

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

بالنسبة لمثالك ، لا يمكنك استدعاء أي وظائف على x. ولكن لا يزال بإمكانك العمل على الشريحة مثل أي شريحة أخرى تكون مفيدة للغاية لوحدها. لست متأكدًا أيضًا من وظيفة func الموجودة داخل func ... ربما قصدت تعيين var؟

تضمين التغريدة
شكرًا ، لقد أصلحت بناء الجملة ، لكن أعتقد أن المعنى لا يزال واضحًا.

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

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

تضمين التغريدة
هل متعدد الإرسال هو كل ما هو ضروري؟ عدد قليل جدًا من اللغات يدعمها. حتى لغة C ++ لا تدعمها. يدعمها C # kinda عبر dynamic لكنني لم أستخدمها مطلقًا في الممارسة العملية والكلمة الأساسية بشكل عام نادرة جدًا في الكود الحقيقي. أمثلة أتذكر أنني تعاملت مع شيء مثل تحليل JSON ، وليس كتابة الأدوية الجنيسة.

هل متعدد الإرسال هو كل ما هو ضروري؟

IMHO ، أعتقد أن keean يتحدث عن الإرسال المتعدد الثابت الذي يوفره النوع / الواجهات.
يتم توفير هذا حتى في C ++ عن طريق التحميل الزائد (لا أعرف لـ C #)

ما تعنيه هو الإرسال المتعدد الديناميكي وهو أمر مرهق للغاية في اللغات الثابتة بدون أنواع الاتحاد. تتحايل اللغات الديناميكية على هذه المشكلة عن طريق حذف التحقق من النوع الثابت (الاستدلال الجزئي للنوع للغات الديناميكية ، وهو نفس الشيء بالنسبة للنوع "الديناميكي" لـ C #).

هل يمكن تقديم نوع كمعامل "فقط"؟

func Append(t, t2 type, arr []t, value t2) []t {
    v := t(value) // conversion
    return append(arr, v)
}

var arr []float64
v := 0

arr = Append(float64, int, arr, v)

Inuart كتب:

هل يمكن تقديم نوع كمعامل "فقط"؟

مشكوك فيه إلى أي درجة سيكون هذا ممكنًا أو مرغوبًا فيه

ما تريده يمكن تحقيقه بدلاً من ذلك إذا كانت القيود العامة مدعومة:

func Append(arr []t, value s) []t  requires Convertible<s,t>{
    v := t(value) // conversion
    return append(arr, v)
}

var arr []int64
v := 0.5

arr = Append(arr, v)

يجب أن يكون هذا ممكنًا أيضًا مع وجود قيود أيضًا:

func convert(value s) t requires Convertible<s,t>{
    return t(value);
}

f:float64:=2.0

i:int64=convert(f)

لما يستحق ، تدعم لغة الجنس لدينا الإرسال المتعدد. يمكن أن توفر نماذج القيد تطبيقات متعددة يتم إرسالها إلى.

أتفهم أن الترميز Convertible<s,t> مطلوب لضمان أمان وقت التجميع ، ولكن يمكن أن يتدهور لفحص وقت التشغيل

func Append(t, t2 type, arr []t, value t2) []t {
    v, ok := t(value) // conversion
    if !ok {
        panic(...) // or return an err
    }
    return append(arr, v)
}

var arr []float64
v := 0

arr = Append(float64, int, arr, v)

ولكن هذا يشبه إلى حد كبير السكر النحوي مقابل reflect .

Inuart the point هي أن المحول البرمجي يمكنه التحقق من النوع الذي يقوم بتنفيذ فئة الآلة في وقت الترجمة ، لذا فإن فحص وقت التشغيل غير ضروري. الفائدة هي أداء أفضل (ما يسمى بتجريد التكلفة الصفرية). إذا كان التحقق من وقت التشغيل ، فيمكنك أيضًا استخدام reflect .

تضمين التغريدة

هل متعدد الإرسال هو كل ما هو ضروري؟

أنا في عقول جدا حول هذا. من ناحية أخرى ، لا يعمل الإرسال المتعدد (مع فئات نوع المعلمات المتعددة) بشكل جيد مع الوجود ، ما يطلق عليه "Go" "قيم الواجهة".

type Equals<T> interface {eq(right T) bool}
(left I) eq(right I) bool {return left == right}
(left I) eq(right F) bool {return false}
(left F) eq(right I) bool {return false}
(left F) eq(right F) bool {return left == right}

func main() {
    x := []Equals<?>{I{2}, F{4.0}, I{2}, F{4.0}}
}

لا يمكننا تحديد شريحة Equals لأنه ليس لدينا طريقة للإشارة إلى أن المعلمة اليمنى من نفس المجموعة. لا يمكننا حتى القيام بذلك في هاسكل:

data Equals = forall a . IEquals a a => Equals a

هذا ليس جيدًا لأنه يسمح فقط لمقارنة النوع مع نفسه

data Equals = forall a b . IEquals a b => Equals a

هذا ليس جيدًا لأنه ليس لدينا طريقة لتقييد b ليكون وجوديًا آخر في نفس المجموعة مثل a (إذا كان a حتى في مجموعة).

ومع ذلك ، فإنه يجعل من السهل جدًا التوسيع بنوع جديد:

(left K) eq(right I) bool {return false}
(left K) eq(right F) bool {return false}
(left I) eq(right K) bool {return false}
(left F) eq(right K) bool {return false}
(left K) eq(right K) bool {return left == right}

وسيكون هذا أكثر إيجازًا مع الحالات الافتراضية أو التخصص.

من ناحية أخرى ، يمكننا إعادة كتابة هذا في "Go" الذي يعمل الآن:

package main

type I struct {v int}
type F struct {v float32}

type EqualsInt interface {eqInt(left I) bool}
func (right I) eqInt (left I) bool {return left == right}
func (right F) eqInt (left I) bool {return false}

type EqualsFloat interface {eqFloat(left F) bool}
func (right I) eqFloat (left F) bool {return false}
func (right F) eqFloat (left F) bool {return left == right}

type EqualsRight interface {
    EqualsInt
    EqualsFloat
}

type EqualsLeft interface {eq(right EqualsRight) bool}
func (left I) eq (right EqualsRight) bool {return right.eqInt(left)}
func (left F) eq (right EqualsRight) bool {return right.eqFloat(left)}

type Equals interface {
    EqualsLeft
    EqualsRight
}

func main() {
    x := []Equals{I{2}, F{4.0}, I{2}, F{4.0}}
    println(x[0].eq(x[1]))
    println(x[1].eq(x[0]))
    println(x[0].eq(x[2]))
    println(x[1].eq(x[3]))
}

يعمل هذا بشكل جيد مع (قيمة الواجهة) الوجودية ، ومع ذلك فهي أكثر تعقيدًا بكثير ، ويصعب معرفة ما يجري وكيف يعمل ، ولديه قيود كبيرة تتمثل في أننا نحتاج إلى واجهة واحدة لكل نوع ونحتاج إلى ترميز مقبول أنواع الجانب الأيمن مثل هذا:

type EqualsRight interface {
    EqualsInt
    EqualsFloat
}

مما يعني أنه سيتعين علينا تعديل مصدر المكتبة لإضافة نوع جديد لأن الواجهة EqualsRight غير قابلة للتوسيع.

لذلك بدون واجهات متعددة المعلمات ، لا يمكننا تحديد عوامل تشغيل عامة قابلة للتوسيع مثل المساواة. مع الواجهات متعددة المعلمات تصبح الوجودية (قيم الواجهة) إشكالية.

مشكلتي الرئيسية مع الكثير من الصيغ المقترحة (التركيبات؟) Blah[E] هي أن النوع الأساسي لا يُظهر أي معلومات حول احتواء الأدوية العامة.

على سبيل المثال:

type Comparer[C] interface {
    Compare(other C) bool
}
// or
type Comparer c interface {
    Compare(other c) bool
}
...

هذا يعني أننا نعلن عن نوع جديد يضيف المزيد من المعلومات إلى النوع الأساسي. أليس الهدف من التصريح type هو تعريف الاسم بناءً على نوع آخر؟

أود أن أقترح بناء جملة على طول خط

type Comparer interface[C] {
    Compare(other C) bool
}

هذا يعني أن Comparer هو مجرد نوع يعتمد على interface[C] { ... } ، و interface[C] { ... } هو بالطبع نوع منفصل من interface { ... } . يتيح لك هذا استخدام واجهة عامة دون تسميتها ، إذا كنت تريد ذلك (وهو مسموح به مع الواجهات العادية). أعتقد أن هذا الحل أكثر حدسية قليلاً ويعمل بشكل جيد مع نظام نوع Go ، على الرغم من أنه من فضلك صححني إذا كنت مخطئًا.

ملاحظة: التصريح عن النوع العام سيكون مسموحًا به فقط على الواجهات والبنى والوظائف باستخدام الصيغ التالية:
interface[G] { ... }
struct[G] { ... }
func[G] (vars...) { ... }

ثم "تنفيذ" الوراثة سيكون لها الصيغ التالية:
interface[G] { ... }[string]
struct[G] { ... }[string]
func[G] (vars...) { ... }[int](args...)

ومع بعض الأمثلة لتوضيح الأمر أكثر قليلاً:

واجهات

package add

type Adder interface[E] {
    // Adds the element and returns the size
    Add(elem E) int
}

// Adds the integer 5 to any implementation of Adder[int].
func AddFiveTo(a Adder[int]) int {
    return a.Add(5)
}

الهياكل

package heap

type List struct[T] {
    slice []T
}

func (l *List) Add(elem T) { // T is a type defined by the receiver
    l.slice = append(l.slice, elem)
}

المهام

func[A] AddManyTo(a Adder[A], many ...A) {
    for _, each := range a {
        a.Add(each)
    }
}

هذا ردًا على مسودة عقود Go2 وسأستخدم تركيبها ، لكنني أنشرها هنا لأنها تنطبق على أي اقتراح لتعدد الأشكال البارامترية.

لا ينبغي السماح بتضمين معلمات النوع.

يعتبر

type X(type T C) struct {
  R // A regular type with method Foo()
  T // Some type parameter
}
// X defines some methods other than Foo(),
// some of which invoke Foo.

لبعض الأنواع التعسفية R وبعض العقود التعسفية C التي لا تحتوي على Foo() .

سيحتوي T على جميع المحددات المطلوبة من قبل C لكن إنشاء مثيل محدد T قد يحتوي أيضًا على محددات أخرى عشوائية ، بما في ذلك Foo .

لنفترض أن Bar عبارة عن هيكل ، مقبول تحت C ، يحتوي على حقل باسم Foo .

قد يكون X(Bar) إنشاء مثيل غير قانوني. بدون طريقة لتحديد العقد بأن النوع ليس له محدد ، يجب أن يكون هذا خاصية مستنتجة.

يمكن أن تستمر طرق X(Bar) في تحليل المراجع إلى Foo كـ X(Bar).R.Foo . هذا يجعل كتابة النوع العام أمرًا ممكنًا ولكن قد يكون مربكًا للقارئ غير المألوف عن قواعد الدقة. خارج أساليب X ، سيظل المحدِّد غامضًا ، في حين أن interface { Foo() } لا يعتمد على معلمات X ، فإن بعض عمليات إنشاء مثيل X قد لا ترضيه.

يعتبر عدم السماح بتضمين معلمة النوع أبسط.

(إذا كان هذا مسموحًا به ، فسيكون اسم الحقل T لنفس السبب الذي جعل اسم الحقل S المضمن على أنه type S = io.Reader هو S وليس Reader ولكن أيضًا لأن النوع الذي يقوم بإنشاء مثيل T لا يحتاج بالضرورة إلى اسم على الإطلاق.)

jimmyfrasche أعتقد أن الحقول المضمنة ذات الأنواع العامة مفيدة بدرجة كافية بحيث يكون من الجيد السماح بها ، حتى لو كان هناك القليل من الإحراج في الأماكن. قد يكون اقتراحي هو الافتراض في جميع الشفرات العامة أن النوع المضمن قد حدد جميع الحقول والأساليب الممكنة على كل مستوى ممكن ، بحيث يتم محو جميع الطرق والحقول المضمنة من الأنواع غير العامة ضمن الشفرة العامة.

لذلك معطى:

type R struct(type T) {
    io.Reader
    T
}

لن تكون الطرق الموجودة على R قادرة على استدعاء Read on R بدون المباشرة من خلال Reader. فمثلا:

func (r R) Do() {
     r.Read(buf)     // Illegal
     r.Reader.Read(buf)  // ok
}

الجانب السلبي الوحيد الذي يمكنني رؤيته لهذا هو أن النوع الديناميكي قد يحتوي على أعضاء أكثر من النوع الثابت. فمثلا:

func (r R) Do() {
    var x interface{} = r
    x.(io.Reader)    // Succeeds
}

تضمين التغريدة

الجانب السلبي الوحيد الذي يمكنني رؤيته لهذا هو أن النوع الديناميكي قد يحتوي على أعضاء أكثر من النوع الثابت.

هذا هو الحال مع معلمات النوع مباشرة ، لذلك أعتقد أنه يجب أن يكون جيدًا أيضًا مع الأنواع البارامترية. أعتقد أن حل المشكلة المقدم من jimmyfrasche قد يكون وضع مجموعة الطرق المرغوبة من النوع ذي المعلمات في العقد.

contract C(t T) {
  interface { Foo() } (X(T){})
  // ...
}

type X(type T C) struct {
  R // A regular type with method Foo()
  T // Some type parameter
}
// X defines some methods other than Foo(),
// some of which invoke Foo.

سيسمح هذا باستدعاء $ Foo على X مباشرة. بالطبع ، هذا من شأنه أن يتعارض مع قاعدة "عدم وجود أسماء محلية في العقود" ...

stevenblenkinsop حسنًا ، من الممكن ، إذا كان محرجًا ، القيام بذلك دون الرجوع إلى X

contract C(t T) {
  struct{ R; T }{}.Foo
}

لا يزال C ملزمًا بتنفيذ X وإن كان ذلك غير محكم بعض الشيء.

إذا لم تفعل ذلك ، فتكتب

func (x X(T)) Fooer() interface { Foo() } {
  return x
}

هل يتم تجميعها؟ لن يخضع لقاعدةrogpeppe الذي يبدو أنه يجب اعتماده أيضًا عندما لا تقدم الضمان في العقد. ولكن بعد ذلك ، هل يتم تطبيقه فقط عندما تقوم بتضمين حجة نوع بدون عقد كافٍ أو لجميع حفلات الزفاف؟

سيكون من الأسهل فقط عدم السماح بذلك.

لقد بدأت العمل على هذا الاقتراح قبل الإعلان عن مسودة Go2.

كنت على استعداد لإلغاء ما لدي بسعادة عندما رأيت الإعلان ، لكنني ما زلت غير مستقر بشأن تعقيد المسودة ، لذلك انتهيت من عملي. إنها أقل قوة ولكنها أبسط. إذا لم يكن هناك شيء آخر ، فقد يحتوي على بعض القطع التي تستحق السرقة.

إنه يتوسع في بناء جملة مقترحاتianlancetaylor السابقة ، لأن هذا هو ما كان متاحًا عندما بدأت. هذا ليس أساسيا. يمكن استبداله ببنية (type T وما إلى ذلك أو ما يعادله. أنا فقط بحاجة إلى بعض النحو كتدوين للدلالات.

إنه موجود هنا: https://gist.github.com/jimmyfrasche/656f3f47f2496e6b49e041cd8ac716e4

يجب أن تكون القاعدة أنه لا يمكن استدعاء أي طريقة يتم الترويج لها من عمق أكبر من تلك الخاصة بمعامل نوع مضمن ما لم (1) تعرف هوية وسيطة النوع أو (2) تم التأكيد على أن الطريقة قابلة للاستدعاء على السطح الخارجي اكتب حسب العقد الذي يقيد معلمة النوع. يمكن للمجمع أيضًا تحديد الحدود العلوية والسفلية للعمق الذي يجب أن يكون للطريقة التي تمت ترقيتها داخل النوع الخارجي O ، واستخدامها لتحديد ما إذا كانت الطريقة قابلة للاستدعاء على النوع الذي يتضمن O ، أي ما إذا كان هناك احتمال للتعارض مع الأساليب المروجة الأخرى أم لا. يمكن تطبيق شيء مشابه أيضًا على أي معلمة نوع يتم التأكيد على أن لها طرقًا قابلة للاستدعاء ، حيث تكون نطاقات العمق للطرق داخل معلمة النوع هي [0 ، inf).

يبدو أن تضمين معلمات النوع مفيد للغاية بحيث لا يمكن منعه تمامًا. لسبب واحد ، أنه يسمح بتكوين شفاف ، والذي لا يسمح به نمط واجهات التضمين.

لقد وجدت أيضًا استخدامًا محتملاً في تحديد العقود. إذا كنت تريد أن تكون قادرًا على قبول قيمة من النوع T (والذي يمكن أن يكون نوع مؤشر) والذي قد يكون له طرق محددة على *T ، وتريد أن تكون قادرًا على وضع هذه القيمة في واجهة ، لا يمكنك بالضرورة وضع T في الواجهة ، نظرًا لأن الأساليب قد تكون على *T ، ولا يمكنك بالضرورة وضع *T في الواجهة لأن T قد يكون *T على مجموعة طريقة فارغة). ومع ذلك ، إذا كان لديك غلاف مثل

type Wrapper(type T) { T }

يمكنك وضع *Wrapper(T) في الواجهة في جميع الحالات إذا كان العقد ينص على أنه يفي بالواجهة.

لا يمكنك أن تفعل فقط

type Interface interface {
  SomeMethod(int) error
}

contract MightBeAPointer(t T) {
  Interface(t)
}

func Example(type T MightBeAPointer)(v T) {
  var i Interface = v
  // ...
}

أحاول التعامل مع القضية التي يتصل فيها شخص ما

type S struct{}
func (s *S) SomeMethod(int) error { ... }
...
var s S
Example(S)(s)

لن يعمل هذا لأنه لا يمكن تحويل $ # $ S إلى Interface ، فقط *S يمكن تحويلها.

من الواضح أن الإجابة قد تكون "لا تفعل ذلك". ومع ذلك ، يصف اقتراح العقود العقود مثل:

contract Contract(t T) {
    var _ error = t.SomeMethod(int(0))
}

S يفي بهذا العقد بسبب العنونة التلقائية ، كما هو الحال مع *S . ما أحاول معالجته هو فجوة القدرة بين استدعاءات الطريقة وتحويلات الواجهة في العقود.

على أي حال ، هذا نوع من الظل ، يُظهر استخدامًا محتملاً واحدًا لدمج معلمات النوع.

إعادة التضمين ، أعتقد أن عبارة "يمكن التضمين في هيكل" هي قيد آخر يجب أن تلتقطه العقود إذا سمح بذلك.

يعتبر:

contract Embeddable(type X, Y) {
    type S struct {
        X
        Y
    }
}

type Embedded(type First, Second Embeddable) struct {
        First
        Second
}

// Error: First and Second both provide method Read.
// That must be diagnosed to the Embeddable contract, not the definition of Embedded itself.
type Boom = Embedded(*bytes.Buffer, *strings.Reader)

يسمحbcmills بتضمين الأنواع ذات المحددات الغامضة ، لذا لست متأكدًا من كيفية تفسير هذا العقد.

على أي حال ، إذا كنت تقوم فقط بتضمين الأنواع المعروفة ، فلا بأس بذلك. إذا كنت تقوم فقط بتضمين معلمات النوع ، فلا بأس بذلك. الحالة الوحيدة التي تصبح غريبة هي عندما تقوم بتضمين نوع واحد أو أكثر من الأنواع المعروفة وواحد أو أكثر من معلمات النوع ثم فقط عندما لا يتم فصل محددات النوع (الأنواع) المعروفة ووسيطات النوع

يسمحbcmills بتضمين الأنواع ذات المحددات الغامضة ، لذا لست متأكدًا من كيفية تفسير هذا العقد.

حسنًا ، نقطة جيدة. أفتقد قيدًا آخر لتشغيل الخطأ

contract Embeddable(type X, Y) {
    type S struct {
        X
        Y
    }
    var _ io.Reader = S{}
}

¹ https://play.golang.org/p/3wSg5aRjcQc

يتطلب هذا واحدًا من X أو Y لكن ليس كلاهما ليكون io.Reader . من المثير للاهتمام أن نظام العقد معبر بما يكفي للسماح بذلك. أنا سعيد لأنني لست مضطرًا إلى معرفة قواعد الاستدلال النوعي لمثل هذا الوحش.

لكن هذه ليست المشكلة حقًا.

إنه عندما تفعل ذلك

type S (type T C) struct {
  io.Reader
  T
}
func (s *S(T)) X() io.Reader {
  return s
}

يجب أن يفشل ذلك في التحويل البرمجي لأن T يمكن أن يحتوي على محدد Read ما لم يكن C لديه

struct{ io.Reader; T }.Read

ولكن ما هي القواعد عندما لا يضمن C أن مجموعات المحددات منفصلة و S لا تشير إلى المحددات؟ هل يمكن لكل إنشاء مثيل S تلبية واجهة ما عدا الأنواع التي تنشئ محددًا غامضًا؟

هل يمكن لكل إنشاء مثيل S تلبية واجهة ما عدا الأنواع التي تنشئ محددًا غامضًا؟

نعم ، يبدو أن هذا هو الحال. أتساءل عما إذا كان هذا يعني أي شيء أعمق ... 🤔

لم أتمكن من بناء أي شيء سيء بشكل لا يمكن إصلاحه ، لكن عدم التماثل غير سار تمامًا ويجعلني أشعر بعدم الارتياح:

type I interface { /* ... */ }
a := G(A) // ok, A satisfies contract
var _ I = a // ok, no selector overlap
b := G(B) // ok, B satisfies contract
var _ = b // error, selector overlap

أنا قلق بشأن رسائل الخطأ عندما يستخدم G0(B) G1(B) يستخدم a. . . يستخدم Gn(B) و Gn هو الذي يسبب الخطأ. . . .

FTR ، لست بحاجة إلى مواجهة مشكلة المحددات الغامضة لتشغيل أخطاء الكتابة مع التضمين.

// Error: Duplicate field name Reader
type Boom = Embedded(*bytes.Reader, *strings.Reader)

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

تم تحديد هذا بالفعل في مسودة التصميم في القسم الخاص بالأنواع ذات المعلمات :

عندما يكون النوع ذو المعلمات عبارة عن هيكل ، ويتم تضمين معلمة النوع كحقل في البنية ، يكون اسم الحقل هو اسم معلمة النوع ، وليس اسم وسيطة النوع.

type Lockable(type T) struct {
    T
    mu sync.Mutex
}

func (l *Lockable(T)) Get() T {
    l.mu.Lock()
    defer l.mu.Unlock()
    return l.T
}

(ملاحظة: هذا يعمل بشكل سيئ إذا كتبت Lockable (X) في إعلان الطريقة: هل يجب أن تعيد الطريقة lT أو lX؟ ربما يجب علينا ببساطة حظر تضمين معلمة نوع في بنية.)

أنا فقط أجلس هنا على الهامش وأراقب. ولكن أيضا الحصول على قلق صبي.

الشيء الوحيد الذي لا أشعر بالحرج من قوله هو أن 90٪ من هذه المناقشة قد غطت رأسي.

يبدو أن 20 عامًا من كسب لقمة العيش من كتابة البرامج دون معرفة ما هي الأدوية الجنيسة ، أو تعدد الأشكال البارامترية ، لم يمنعني من إنجاز المهمة.

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

لا يمكن أن أكون أكثر خطأ.

تمكنت من تعلم ما يكفي من Go لإنشاء خدمة صغيرة أدت إلى تدمير خدمة node.js التي كنت أواجه مشكلات في الأداء معها في أقل من عطلة نهاية الأسبوع.

ومن المفارقات ، أنني كنت ألعب فقط. لم أكن جادًا بشكل خاص بشأن غزو العالم باستخدام Go.

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

وهذا هو الشيء الذي أحبه في Go. إنه سريع جدا ..... للتعلم. نعلم جميعًا هنا قدرات الأداء الخاصة به. لكن السرعة التي يمكن تعلمها بها لا مثيل لها من قبل 8 لغات أخرى تعلمتها على مر السنين.

منذ ذلك الحين ، كنت أغني مدح Go ، وحصلت على 4 Devs آخرين ليقعوا في حبه. أجلس معهم لساعتين فقط وأبني شيئًا. النتائج تتحدث عن نفسها.

البساطة والسرعة في التعلم. هذه هي السمات القاتلة الحقيقية للغة.

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

لذلك ، هناك شيئان آمل أن يأخذهما فريق Go في الاعتبار:

1) ما هي المشكلة اليومية التي نتطلع إلى حلها؟

لا يمكنني العثور على مثال من العالم الحقيقي ، مع سدادة عرض يمكن حلها عن طريق الأدوية الجنيسة ، أو أيًا كان ما سيتم تسميتها.

أمثلة على أسلوب كتاب الطبخ للمهام اليومية التي تنطوي على مشاكل ، مع مثال على كيفية تحسينها باستخدام مقترحات تغيير اللغة هذه.

2) حافظ على البساطة ، مثل جميع الميزات الرائعة الأخرى في Go

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

ربما حجة مترجم لتمكين هذه الميزات المتقدمة؟ "- المتشددين"

سأكون حزينًا حقًا إذا أثرنا سلبًا على أداء المترجم. فقط قل

وهذا هو الشيء الذي أحبه في Go. إنه سريع جدا ..... للتعلم. نعلم جميعًا هنا قدرات الأداء الخاصة به. لكن السرعة التي يمكن تعلمها بها لا مثيل لها من قبل 8 لغات أخرى تعلمتها على مر السنين.

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

لا يمكنني العثور على مثال من العالم الحقيقي ، مع سدادة عرض يمكن حلها عن طريق الأدوية الجنيسة ، أو أيًا كان ما سيتم تسميتها.

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

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

تضمين التغريدة

لا يمكنني العثور على مثال من العالم الحقيقي ، مع سدادة عرض يمكن حلها عن طريق الأدوية الجنيسة ، أو أيًا كان ما سيتم تسميتها.

علم الوراثة هو لذلك ليس عليك كتابة الكود بنفسك. لذلك لن تحتاج أبدًا إلى تنفيذ قائمة أخرى مرتبطة ، أو شجرة ثنائية ، أو deque ، أو قائمة انتظار ذات أولوية أخرى بنفسك مرة أخرى. لن تحتاج أبدًا إلى تنفيذ خوارزمية الفرز ، أو خوارزمية التقسيم ، أو خوارزمية التدوير ، إلخ. تصبح هياكل البيانات تشكل مجموعات قياسية (خريطة القوائم على سبيل المثال) ، وتصبح المعالجة مؤلفة من خوارزميات قياسية (أحتاج إلى فرز البيانات ، والتقسيم ، وتدوير). إذا كان بإمكانك إعادة استخدام هذه المكونات ، سينخفض ​​معدل الخطأ ، لأنه في كل مرة تعيد فيها تنفيذ قائمة انتظار الأولوية ، أو خوارزمية التقسيم ، هناك احتمال أن تخطئ وتحدث خطأ.

تعني العوامل العامة أنك تكتب رمزًا أقل ، وتعيد استخدام المزيد. إنها تعني أنه يمكن استخدام وظائف المكتبة القياسية التي يتم صيانتها جيدًا وأنواع البيانات المجردة في المزيد من المواقف ، لذلك لا يتعين عليك كتابة ما يخصك.

والأفضل من ذلك ، يمكن القيام بكل ذلك تقنيًا في Go الآن ، ولكن فقط مع خسارة شبه كاملة لسلامة وقت التجميع _و_ مع بعض النفقات الزائدة لوقت التشغيل ، والتي يُحتمل أن تكون كبيرة. تتيح لك علم الوراثة القيام بذلك دون أي من هذه الجوانب السلبية.

تنفيذ الوظيفة العامة:

/*

* "generic" is a KIND of types, just like "struct", "map", "interface", etc...
* "T" is a generic type (a type of kind generic).
* var t = T{int} is a value of type T, values of generic types looks like a "normal" type

*/

type T generic {
    int
    float64
    string
}

func Sum(a, b T{}) T{} {
    return a + b
}

وظيفة المتصل:

Sum(1, 1) // 2
// same as:
Sum(T{int}(1), T{int}(1)) // 2

تنفيذ الهيكل العام:

type ItemT generic {
    interface{}
}

type List struct {
    l []ItemT{}
}

func NewList(t ItemT) *List {
    l := make([]t)
    return &List{l}
}

func (p *List) Push(item ItemT{}) {
    p.l = append(p.l, item)
}

المتصل:

list := NewList(ItemT{int})
list.Push(42)

كشخص يتعلم Swift فقط ولا يعجبه ، ولكن لديه الكثير من الخبرة في لغات أخرى مثل Go و C و Java وما إلى ذلك ؛ أعتقد حقًا أن الأدوية الجنيسة (أو القوالب ، أو أي شيء تريد تسميته) ليس بالأمر الجيد لإضافته إلى لغة Go.

ربما أكون أكثر خبرة في الإصدار الحالي من Go ولكن بالنسبة لي يبدو هذا وكأنه تراجع إلى C ++ من حيث أنه من الصعب فهم الكود الذي كتبه الآخرون. يجعل العنصر النائب الكلاسيكي T للأنواع من الصعب جدًا فهم ما تحاول الوظيفة القيام به.

أعلم أن هذا طلب ميزة شائع لذا يمكنني التعامل معه إذا هبط ، لكنني أردت إضافة 2 سنت (رأي).

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

تضمين التغريدة

أو القوالب ، أو أيًا كان ما تريد تسميته

كل شيء يعتمد على التنفيذ. إذا كنا نتحدث عن قوالب C ++ ، فعندئذ نعم ، يصعب فهمها بشكل عام. حتى كتابتها صعبة. من ناحية أخرى ، إذا أخذنا أدوية C # ، فهذا شيء مختلف تمامًا. المفهوم نفسه ليس مشكلة هنا.

إذا لم تكن تعلم ، فقد أعلن فريق Go عن مسودة Go 2.0:
https://golang.org/s/go2designs

هناك مسودة لتصميم Generics في Go 2.0 (عقد). قد ترغب في إلقاء نظرة وتقديم ملاحظات حول الويكي الخاص بهم.

هذا القسم ذو الصلة:

علم الوراثة

بعد قراءة المسودة ، أسأل:

لماذا

T: قابل للإضافة

يعني "نوع T تنفيذ العقد المضافة"؟ لماذا تضيف ملف
مفهوم عندما يكون لدينا بالفعل واجهات لذلك؟ مهمة الواجهات هي
تم التحقق من وقت الإنشاء ، لذلك لدينا بالفعل الوسائل لعدم الحاجة إلى أي منها
مفهوم إضافي هنا. يمكننا استخدام هذا المصطلح لقول شيء مثل: أي
اكتب T بتنفيذ الواجهة Addable. بالإضافة إلى ذلك ، T: _ أو T: أي
(كونه أي كلمة رئيسية خاصة أو اسم مستعار مضمن للواجهة {}) سيفي بالغرض
الخدعة.

فقط لا أعرف لماذا أعيد تنفيذ معظم الأشياء من هذا القبيل. لا يجعل
المعنى وسوف تكون زائدة عن الحاجة (كما زائدة عن الحاجة هو معالجة جديدة للأخطاء wrt
التعامل مع حالات الذعر).

2018-09-14 6:15 GMT-05: 00 كوالا يونج [email protected] :

إذا لم تكن تعلم ، فقد أعلن فريق Go عن مسودة Go 2.0:
https://golang.org/s/go2designs

هناك مسودة لتصميم Generics في Go 2.0 (عقد). قد ترغب
لإلقاء نظرة وإبداء الرأي
https://github.com/golang/go/wiki/Go2Generics ردود الفعل على ويكي الخاصة بهم
https://github.com/golang/go/wiki/Go2Generics

هذا القسم ذو الصلة:

علم الوراثة

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-421326634 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AlhWhS8xmN5Y85_aUKT5VnutoOKUAaLLks5ua4_agaJpZM4IG-xv
.

-
هذا اختبار لتوقيعات البريد لاستخدامها في TripleMint

تحرير: "[...] سيفي بالغرض إذا لم تكن بحاجة إلى متطلبات معينة في
حجة النوع ".

2018-09-17 11:10 GMT-05: 00 لويس ماسويلي [email protected] :

بعد قراءة المسودة ، أسأل:

لماذا

T: قابل للإضافة

يعني "نوع T تنفيذ العقد المضافة"؟ لماذا تضيف ملف
مفهوم عندما يكون لدينا بالفعل واجهات لذلك؟ مهمة الواجهات هي
تم التحقق من وقت الإنشاء ، لذلك لدينا بالفعل الوسائل لعدم الحاجة إلى أي منها
مفهوم إضافي هنا. يمكننا استخدام هذا المصطلح لقول شيء مثل: أي
اكتب T بتنفيذ الواجهة Addable. بالإضافة إلى ذلك ، T: _ أو T: أي
(كونه أي كلمة رئيسية خاصة أو اسم مستعار مضمن للواجهة {}) سيفي بالغرض
الخدعة.

فقط لا أعرف لماذا أعيد تنفيذ معظم الأشياء من هذا القبيل. لا يجعل
المعنى وسوف تكون زائدة عن الحاجة (كما زائدة عن الحاجة هو معالجة جديدة للأخطاء wrt
التعامل مع حالات الذعر).

2018-09-14 6:15 GMT-05: 00 كوالا يونج [email protected] :

إذا لم تكن تعلم ، فقد أعلن فريق Go عن مسودة Go 2.0:
https://golang.org/s/go2designs

هناك مسودة لتصميم Generics في Go 2.0 (عقد). يمكنك
تريد إلقاء نظرة وإبداء الرأي
https://github.com/golang/go/wiki/Go2Generics ردود الفعل على ويكي الخاصة بهم
https://github.com/golang/go/wiki/Go2Generics

هذا القسم ذو الصلة:

علم الوراثة

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-421326634 ، أو كتم الصوت
الخيط
https://github.com/notifications/unsubscribe-auth/AlhWhS8xmN5Y85_aUKT5VnutoOKUAaLLks5ua4_agaJpZM4IG-xv
.

-
هذا اختبار لتوقيعات البريد لاستخدامها في TripleMint

-
هذا اختبار لتوقيعات البريد لاستخدامها في TripleMint

@ luismasuelli-jobsity إذا قرأت تاريخ عمليات التنفيذ العامة في Go بشكل صحيح ، فيبدو أن السبب في تقديم العقود هو أنهم لا يريدون زيادة التحميل على المشغل في Interfaces.

تم رفض اقتراح سابق تم رفضه في النهاية باستخدام واجهات لتقييد تعدد الأشكال البارامترية ، ولكن يبدو أنه تم رفضه لأنه لا يمكنك استخدام عوامل تشغيل شائعة مثل "+" في مثل هذه الوظائف لأنه غير قابل للتحديد في واجهة. تسمح لك العقود بكتابة t == t أو t + t حتى تتمكن من الإشارة إلى النوع الذي يجب أن يدعم المساواة أو الإضافة وما إلى ذلك.

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

لماذا يعني T:Addable "نوع T ينفذ العقد القابل للإضافة"؟

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

func Example(type K, V someContract)(k K, v V) V

يمكنك فعل شيء مثل

contract someContract(k K, v V) {
  k.someMethod(v)
}

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

type someMethoder(V) interface {
  someMethod(V)
}

func Example(type K: someMethoder(V), V)(k K, v V) V

هذا نوع من الحرج. يسمح لك بناء جملة العقد بالقيام بذلك إذا احتجت إلى ذلك ، لأن "الحجج" الخاصة بالعقد يتم ملؤها تلقائيًا بواسطة المترجم إذا كان العقد يحتوي على نفس العدد كما تفعل الوظيفة مع معلمات الكتابة. يمكنك تحديدها يدويًا إذا كنت ترغب في ذلك ، مما يعني أنك تستطيع أن تفعل func Example(type K, V someContract(K, V))(k K, v V) V إذا كنت تريد ذلك حقًا ، على الرغم من أنها ليست مفيدة بشكل خاص في هذا الموقف.

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

contract Example(k K, v V) {
  k.someMethod(v)
}

func Example(type K, V)(k K, v V) V

سيكون نفس ما ورد أعلاه. ومع ذلك ، فإن الجانب السلبي هو أن العقود لن تكون قابلة لإعادة الاستخدام وتفقد هذه القدرة على تحديد حجج العقد يدويًا.

تحرير: لتوضيح سبب رغبتهم في حل نمط التكرار المثير للفضول ، ضع في اعتبارك مشكلة أقصر طريق ظلوا يشيرون إليها. مع الواجهات ذات المعلمات ، ينتهي التعريف بالشكل

type E(Node) interface {
  Nodes() []Node
}

type N(Edge) interface {
  Edges() (from, to Edge)
}

type Graph(type Node: N(Edge), Edge: E(Node)) struct { ... }
func New(type Node: N(Edge), Edge: E(Node))(nodes []Node) *Graph(Node, Edge) { ... }
func (*Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { ... }

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

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

أجد أن إصدار "CRTP" أكثر وضوحًا ، وأكثر وضوحًا ، وأسهل في التعامل معه (لا حاجة لإنشاء عقود موجودة فقط لتحديد العلاقة بين العقود الموجودة مسبقًا عبر مجموعة من المتغيرات). من المسلم به أن هذا قد يكون مجرد سنوات عديدة من الإلمام بالفكرة.

توضيحات. من خلال مسودة التصميم ، يمكن تطبيق العقد على كل من الوظائف والأنواع .

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

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

لا يمكنك القيام بما يلي؟

contract Example(t T, v V) {
  t.(interface{
    SomeMethod() V
  })
}

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

DeedleFake لا ، لأنه يمكن تأكيد نوع أي واجهة (ومن ثم من المحتمل أن تصاب بالذعر في وقت التشغيل ، ولكن لا يتم تنفيذ العقود). ولكن يمكنك استخدام مهمة بدلاً من ذلك.

يعني t.(someInterface) أيضًا أنه يجب أن يكون واجهة

نقطة جيدة. Woops.

كلما رأيت المزيد من الأمثلة على هذا ، يبدو أن "اكتشافه من هيئة وظيفية" أكثر عرضة للخطأ.

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

سألاحظ ذلك أيضًا

contract I(t T) {
  var i interface { Foo() }
  i = t
  t.(interface{})
}

تعبر عن أن T يجب أن تكون واجهة بها Foo() على الأقل ولكن يمكن أن تحتوي أيضًا على أي عدد من الطرق الإضافية.

يجب أن يكون T واجهة بها Foo() على الأقل ولكن يمكن أن تحتوي أيضًا على أي عدد آخر من الطرق الإضافية

هل هذه مشكلة رغم ذلك؟ ألا تريد عادةً تقييد الأشياء بحيث تسمح بوظائف محددة ولكنك لا تهتم بالوظائف الأخرى؟ خلاف ذلك عقد مثل

contract Example(t T) {
  t + t
}

لن تسمح بالطرح ، على سبيل المثال. لكن من وجهة نظر أيا كان ما أقوم بتطبيقه ، لا يهمني ما إذا كان النوع يسمح بالطرح أم لا. إذا قمت بتقييده من القدرة على إجراء الطرح ، فلن يتمكن الأشخاص بشكل تعسفي ، على سبيل المثال ، من تمرير أي شيء يؤدي إلى وظيفة Sum() أو شيء من هذا القبيل. يبدو أن ذلك مقيد بشكل تعسفي.

لا ، ليست مشكلة على الإطلاق. لقد كانت مجرد خاصية غير بديهية (بالنسبة لي) ، ولكن ربما كان ذلك بسبب عدم كفاية القهوة.

من العدل أن نقول إن إعلان العقد الحالي يحتاج إلى رسائل مجمعة أفضل للعمل معها. ويجب أن تكون قواعد العقد ساري المفعول صارمة.

مرحبا
لقد قدمت اقتراحًا بشأن قيود الأدوية الجنيسة التي نشرتها في هذا الموضوع منذ حوالي عام.
لقد صنعت الآن الإصدار 2 . التغييرات الرئيسية هي:

  • تم تكييف البنية مع تلك التي اقترحها فريق go.
  • تم حذف التقييد بواسطة الحقول ، مما يسمح بقدر كبير من التبسيط.
  • تم حذف الفقرات التي تعتبر غير ضرورية.

فكرت في سؤال مثير للاهتمام (ولكن ربما يكون أكثر تفصيلاً من المناسب في هذه المرحلة من التصميم؟) بخصوص هوية النوع مؤخرًا:

func Foo() interface{} {
    type S struct {}
    return S{}
}

func Bar(type T)() interface{} {
    type S struct {}
    return S{}
}

func Baz(type T)() interface{} {
    type S struct{t T}
    return S{}
}

func main() {
    fmt.Println(Foo() == Foo()) // 1
    fmt.Println(Bar(int)() == Bar(string)()) // 2
    fmt.Println(Baz(int)() == Baz(string)()) // 3
}
  1. يطبع true ، لأن أنواع القيم التي تم إرجاعها تنشأ في نفس النوع من التصريح.
  2. مطبوعات…؟
  3. يطبع false ، أفترض.

أي السؤال هو ، عندما يتم الإعلان عن نوعين في وظيفة عامة متطابقين وعندما لا يكونان كذلك. لا أعتقد أن هذا موصوف في ~ المواصفات ~ التصميم؟ على الأقل لا يمكنني العثور عليه الآن :)

merovius أفترض أن الحالة الوسطى كان من المفترض أن تكون:

fmt.Println(Bar(int)() == Bar(int)()) // 2

هذه حالة مثيرة للاهتمام ، وتعتمد على ما إذا كانت الأنواع "إنتاجية" أو "تطبيقية". هناك بالفعل متغيرات من ML التي تتخذ أساليب مختلفة. الأنواع التطبيقية ترى العام كوظيفة نوع ، وبالتالي f (int) == f (int). تعرض الأنواع التوليدية العام كقالب نوع يقوم بإنشاء نوع "مثيل" فريد جديد في كل مرة يتم استخدامه لذلك t <int>! = t <int>. يجب التعامل مع هذا على مستوى نظام النوع بالكامل حيث أن له آثارًا دقيقة على التوحيد والاستدلال والصحة. لمزيد من التفاصيل والأمثلة عن نوع المشاكل آنذاك ، أوصي بقراءة ورقة Andreas Rossberg "F-ing modules": https://people.mpi-sws.org/~rossberg/f-ing/ على الرغم من أن الورقة تتحدث عن ML " المفاعلات "هذا لأن ML يفصل نظام النوع الخاص به إلى مستويين ، والموانع هي MLs مكافئة للغة العامة ولا تتوفر إلا على مستوى الوحدة.

keean أنت تفترض خطأ.

merovius نعم ، خطأي ، أرى أن السؤال هو أنه لم يتم استخدام معلمة النوع (نوع وهمي).

مع الأنواع التوليدية ، قد ينتج عن كل مثيل نوع فريد مختلف لـ 'S' ، لذلك على الرغم من عدم استخدام المعلمة ، فلن تكون متساوية.

مع الأنواع التطبيقية ، سيكون 'S' من كل مثيل من نفس النوع ، وبالتالي سيكونان متساويين.

سيكون غريبًا إذا تغيرت النتيجة في الحالة 2 بناءً على تحسينات المترجم. يبدو مثل UB.

إنه عام 2018 ، لا أصدق أنني مضطر بالفعل لكتابة هذا كما في عام 1982:

func min (x، y int) int {
إذا س <ص {
العودة x
}
العودة ذ
}

func max (x، y int) int {
إذا س> ص {
العودة x
}
العودة ذ
}

أعني ، بجدية ، الرجال MIN (INT ، INT) INT ، كيف لا يكون ذلك في اللغة؟
انا غاضب.

@ dataf3l إذا كنت تريد أن يعمل هؤلاء كما هو متوقع مع الطلبات المسبقة ، فعليك:

func min(x, y int) int {
   if x <= y {
      return x
   }
   return y
}

هذا هو السبب في أن الزوج (min (x ، y) ، max (x ، y)) دائمًا ما يكون متميزًا ويكون إما (x ، y) أو (y ، x) ، وهذا بالتالي نوع ثابت من عنصرين.

لذلك ، هناك سبب آخر يجب أن تكون في اللغة أو المكتبة وهو أن الناس يخطئون في الغالب :-)

فكرت في <vs <= ، بالنسبة للأعداد الصحيحة ، لست متأكدًا من أنني أرى الفرق تمامًا.
ربما أنا مجرد غبية ...

لست متأكدًا من أنني أرى الفرق تمامًا.

لا يوجد شيء في هذه الحالة.

cznic true في هذه الحالة لأنها أعداد صحيحة ، ولكن نظرًا لأن الخيط كان حول الأدوية العامة ، فقد افترضت أن تعليق المكتبة كان حول وجود تعريفات عامة لـ min و max حتى لا يضطر المستخدمون إلى التصريح عنهم بأنفسهم. إعادة قراءة OP أستطيع أن أرى أنهم يريدون فقط حد أدنى وحد أقصى بسيط للأعداد الصحيحة ، لذا فإنني سيئ ، لكنهم كانوا خارج الموضوع يطلبون وظائف تكامل بسيطة في سلسلة رسائل حول الأدوية العامة :-)

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

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

لست متأكدًا من كيفية المساهمة بخلاف نشر ملاحظتي هنا كـ "مستخدم". أنا لست مبرمجًا ذا خبرة كافية للمساهمة في التصميم أو التنفيذ ، لذلك لا يمكنني إلا أن أقول إن الأدوية الجنيسة ستعزز بشكل كبير الإنتاجية في اللغة (طالما ظل وقت الإنشاء والأدوات رائعين كما هما الآن).

webern شكرا. راجع https://go.googlesource.com/proposal/+/master/design/go2draft.md .

ianlancetaylor ، بعد النشر ، برزت في رأسي فكرة جذرية / فريدة إلى حد ما أعتقد أنها ستكون "خفيفة الوزن" فيما يتعلق باللغة والأدوات. لم أقرأ الرابط الخاص بك بالكامل بعد ، سأفعل. ولكن إذا كنت أرغب في تقديم فكرة / اقتراح للبرمجة العامة بتنسيق MD ، فكيف أفعل ذلك؟

شكرا.

webern اكتبه (معظم الناس يستخدمون المعنى لتنسيق تخفيض السعر) وقم بتحديث الويكي هنا https://github.com/golang/go/wiki/Go2GenericsFeedback

كثير من الآخرين فعلوا ذلك بالفعل.

لقد دمجت (مقابل أحدث نصيحة) وقمت بتحميل CL لتنفيذ النموذج الأولي الخاص بنا قبل Gophercon للمحلل اللغوي (والطابعة) لتنفيذ تصميم مسودة العقود. إذا كنت مهتمًا بتجربة بناء الجملة ، فيرجى إلقاء نظرة: https://golang.org/cl/149638 .

للعب بها:

1) اختر Cherry-Pick the CL في إعادة شراء حديثة:
git fetch https://go.googlesource.com/go refs / Changes / 38/149638/2 && git cherry-pick FETCH_HEAD

2) إعادة بناء وتثبيت المترجم:
انتقل إلى تثبيت cmd / compile

3) استخدم المترجم:
اذهب أداة تجميع foo.go

راجع وصف CL للحصول على التفاصيل. يتمتع!

contract Addable(t T) {
    t + t
}

func Sum(type T Addable)(x []T) T {
    var total T
    for _, v := range x {
        total += v
    }
    return total
}

هذا التصميم للأدوية ، func Sum(type T Addable)(x []T) T ، قبيح جدا جدا !!!

للمقارنة بـ func Sum(type T Addable)(x []T) T ، أعتقد أن func Sum<T: Addable> (x []T) T أكثر وضوحًا ، وليس له عبء على المبرمج القادم من لغات البرمجة الأخرى.

تقصد أن بناء الجملة أكثر إسهابًا؟
يجب أن يكون هناك سبب لكونه ليس func Sum(T Addable)(x []T) T .

بدون الكلمة الأساسية type لن تكون هناك طريقة للتمييز بين دالة عامة وأخرى تقوم بإرجاع دالة أخرى ، والتي يتم استدعاؤها هي نفسها.

urandom هذه مشكلة فقط في وقت إنشاء مثيل ونحن لا نطلب الكلمة الأساسية type ، ولكن فقط نعيش مع الغموض AIUI.

المشكلة هي أنه بدون الكلمة الرئيسية $ # type func Foo(x T) (y T) إما إعلان دالة عامة تأخذ T ولا تُرجع شيئًا أو دالة غير عامة تأخذ T وإرجاع T .

func سوم(x [] T) T.

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

أعتقد أيضًا أن هذا سيجعل الكود أكثر سهولة (اقرأ: أقل Lisp-y) لتحليله للقراء البشريين ، بالإضافة إلى تقليل فرص الوصول إلى بعض الغموض الغامض في التحليل أسفل الخط (راجع تحليل C ++ "Most Vexing Parse" ، للمساعدة في تحفيز الحذر الشديد).

إنه عام 2018 ، لا أصدق أنني مضطر بالفعل لكتابة هذا كما في عام 1982:

func min (x، y int) int {
إذا س <ص {
العودة x
}
العودة ذ
}

func max (x، y int) int {
إذا س> ص {
العودة x
}
العودة ذ
}

أعني ، بجدية ، الرجال MIN (INT ، INT) INT ، كيف لا يكون ذلك في اللغة؟
انا غاضب.

هناك سبب لذلك.
إذا كنت لا تفهم ، يمكنك أن تتعلم أو تذهب بعيدًا.
اختيارك.

آمل بصدق أن يجعلوها أفضل.
لكن موقفك "يمكنك أن تتعلم أو تذهب بعيدًا" ، لا يقدم مثالًا جيدًا للآخرين ليتبعوه. يقرأ كاشطة دون داع. لا أعتقد أن هذا ما يدور حوله هذا المجتمع @ petar-dambovaliev. ومع ذلك ، فليس لي أن أخبرك بما يجب القيام به ، أو كيفية التصرف عبر الإنترنت ، فهذا ليس مكاني.

أعلم أن هناك الكثير من المشاعر القوية حول الأدوية الجنيسة ، لكن من فضلك ضع في اعتبارك قيم غوفر لدينا. يرجى إبقاء المحادثة محترمة ومرحبة من جميع الجوانب.

bcmills شكرًا لك ، أنت تجعل المجتمع مكانًا أفضل.

موافقkatzdm ، اللغة بها العديد من الأقواس بالفعل ، هذه الأشياء الجديدة تبدو غامضة حقًا بالنسبة لي

تحديد generics يبدو أنه لا مفر من تقديم أشياء مثل type's type ، مما يجعل Go معقدًا إلى حد ما.

آمل ألا يكون هذا خارج الموضوع ، ولكن ميزة function overload تبدو كثيرة بالنسبة لي.

راجع للشغل ، أعلم أنه كان هناك بعض النقاش حول التحميل الزائد .

xgfone أوافق ، أن اللغة بها العديد من الأقواس بالفعل ، مما يجعل الشفرة غير واضحة.
func Sum<T: Addable> (x []T) T أو func Sum<type T Addable> (x []T) T أفضل وأوضح.

من أجل الاتساق (مع الأدوية المضمنة) ، يكون func Sum[T: Addable] (x []T) T أفضل من func Sum<T: Addable> (x []T) T .

قد أتأثر بالعمل السابق بلغات أخرى ، ولكن يبدو أن Sum<T: Addable> (x []T) T أكثر تميزًا وقابلية للقراءة للوهلة الأولى.

أتفق أيضًا مع katzdm في أنه من الأفضل لفت الانتباه إلى شيء جديد في اللغة. إنه أيضًا مألوف تمامًا للمطورين من غير Go الذين يقفزون إلى Go.

FWIW ، هناك احتمال بنسبة 0٪ تقريبًا أن يستخدم Go أقواس زاوية للأدوية. قواعد لغة C ++ غير قابلة للتحليل لأنك لا تستطيع تمييز <b> c (سلسلة مقارنات قانونية ولكن لا معنى لها) من استدعاء عام دون فهم أنواع a و b و c. تتجنب اللغات الأخرى استخدام الأقواس المعقوفة للأدوية لهذا السبب.

func a < b Addable> (...
أعتقد أنه يمكنك إذا أدركت أنه بعد func يمكنك فقط الحصول على اسم الوظيفة ، أو ( أو < .

carlmjohnson آمل أن تكون على حق

f := sum<int>(10)

لكن هنا تعلم أن sum هو عقد ..

قواعد لغة C ++ غير قابلة للتحليل لأنك لا تستطيع تمييز <b> c (سلسلة مقارنات قانونية ولكن لا معنى لها) من استدعاء عام دون فهم أنواع a و b و c.

أعتقد أنه من الجدير الإشارة إلى أنه بينما Go ، على عكس C ++ ، لا يسمح بذلك في نظام النوع ، نظرًا لأن المشغلين < و > يعيدون bool s في Go و < لا يمكن استخدام > مع bool s ، فهو _ قانونيًا من الناحية التركيبية ، لذلك لا تزال هذه مشكلة.

هناك مشكلة أخرى تتعلق بأقواس الزاوية وهي List<List<int>> ، حيث يتم ترميز >> كعامل تحويل صحيح.

ما هي مشكلات استخدام [] ؟ يبدو لي أن معظم ما سبق يتم حله باستخدامها:

  • من الناحية النحوية ، لا لبس في استخدام المثال أعلاه ، f := sum[int](10) لأنه يحتوي على نفس بناء الجملة مثل المصفوفة أو الوصول إلى الخريطة ، ومن ثم يمكن لنظام الكتابة اكتشافها لاحقًا ، كما يجب أن تفعله بالفعل من أجل الفرق بين الوصول إلى المصفوفة والخريطة ، على سبيل المثال. هذا يختلف عن حالة <> لأن < واحد قانوني ، مما يؤدي إلى الغموض ، لكن لا يُعتبر الأمر الوحيد [ .
  • func Example[T](v T) T أيضًا لا لبس فيه.
  • ]] ليس رمزًا خاصًا به ، لذلك يتم تجنب هذه المشكلة أيضًا.

تشير مسودة التصميم إلى وجود غموض في إقرارات النوع ، كما هو الحال في type A [T] int ، لكنني أعتقد أن هذا يمكن حله بسهولة نسبيًا بطريقتين مختلفتين. على سبيل المثال ، يمكن نقل التعريف العام إلى الكلمة الأساسية نفسها ، بدلاً من اسم النوع ، على سبيل المثال:

  • func[T] Example(v T) T
  • type[T] A int

يمكن أن يأتي التعقيد هنا من استخدام كتل إعلان النوع ، مثل

type (
  A int
)

لكنني أعتقد أن هذا نادر بما فيه الكفاية بحيث لا بأس من القول بشكل أساسي أنه إذا كنت بحاجة إلى أدوية جنيسة ، فلا يمكنك استخدام واحدة من هذه الكتل.

أعتقد أنه سيكون من المؤسف أن أكتب

type[T] A []T
var s A[int]

لأن الأقواس المربعة تتحرك من جانب A إلى الجانب الآخر. بالطبع يمكن القيام بذلك ، لكن يجب أن نهدف إلى الأفضل.

ومع ذلك ، فإن استخدام الكلمة الرئيسية type في الصيغة الحالية يعني أنه يمكننا استبدال الأقواس بأقواس مربعة.

لا يبدو هذا مختلفًا عن نوع المصفوفة مقابل صيغة التعبير التي تكون [N]T مقابل arr[i] ، من حيث كيف يتم التصريح عن شيء لا يتطابق مع كيفية استخدامه. نعم ، في var arr [N]T ، ينتهي الأمر بالأقواس المربعة على نفس الجانب من arr كما هو الحال عند استخدام arr ، لكننا عادة ما نفكر في بناء الجملة من حيث النوع مقابل بناء جملة التعبير كونه عكس ذلك.

لقد قمت بتوسيع وتحسين بعض أفكاري القديمة غير الناضجة لمحاولة توحيد الأدوية المخصصة والمدمجة. لا يزال

لست متأكدًا مما إذا كانت مناقشة ( مقابل < مقابل [ واستخدام type هو تربية دراجات أم أن هناك مشكلة حقيقية في بناء الجملة

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

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

Juts لإضافة بعض تقرير الخبرة العملية:
لقد قمت بتطبيق الأدوية الجنيسة كامتداد للغة في مترجم Go الخاص بي https://github.com/cosmos72/gomacro. ومن المثير للاهتمام أن كلا اللغتين

type[T] Pair struct { First T; Second T }
type Pair[T] struct { First T; Second T }

تبين أنها تقدم الكثير من الالتباسات في المحلل اللغوي: يمكن تحليل الثانية كإعلان أن الزوج عبارة عن مصفوفة من T هيكلة ، حيث T هو عدد صحيح ثابت. عندما يتم استخدام Pair ، فهناك بعض الغموض أيضًا: يمكن أيضًا تحليل Pair[int] كتعبير بدلاً من نوع: يمكن أن يتم فهرسة مصفوفة / شريحة / خريطة باسم Pair باستخدام تعبير الفهرس int (ملاحظة: int والأنواع الأساسية الأخرى ليست كلمات رئيسية محجوزة في Go) ، لذلك اضطررت إلى اللجوء إلى بناء جملة جديد - من المسلم به أنه قبيح ، ولكنه يؤدي المهمة:

template[T] type Pair struct { First T; Second T }
type pairOfInt = Pair#[int]
var p Pair#[int]

وبالمثل للوظائف:

template[T] func Sum(args ...T) T { /*...*/ }
Sum#[int] (1,2,3)

لذا ، بينما أوافق نظريًا على أن بناء الجملة أمر سطحي ، يجب أن أشير إلى ما يلي:
1) من جانب واحد ، التركيب اللغوي هو ما سيتعرض له مبرمجو Go - لذلك يجب أن يكون معبرًا وبسيطًا وربما مستساغًا
2) من الجانب الآخر ، سيؤدي الاختيار السيئ لبناء الجملة إلى تعقيد المحلل اللغوي ومدقق الحروف والمترجم من أجل حل الغموض المقدم

يمكن أيضًا تحليل Pair[int] كتعبير بدلاً من نوع: يمكن فهرسة مصفوفة / شريحة / خريطة باسم Pair بتعبير الفهرس int

هذا ليس غموضًا تحليليًا ، بل مجرد غموض دلالي (حتى بعد تحليل الاسم) ؛ البنية النحوية هي نفسها في كلتا الحالتين. لاحظ أن Sum#[int] يمكن أن يكون نوعًا أو تعبيرًا اعتمادًا على ما هو Sum . نفس الشيء ينطبق على (*T) في الكود الحالي. طالما أن تحليل الاسم لا يؤثر على بنية ما يتم تحليله ، فأنت بخير.

قارن هذا بمشاكل <> :

f ( a < b , c < d >> (e) )

لا يمكنك حتى ترميز هذا ، نظرًا لأن >> يمكن أن يكون رمزًا واحدًا أو اثنين. بعد ذلك ، لا يمكنك معرفة ما إذا كانت هناك وسيطة واحدة أو اثنتين لـ f ... يتغير هيكل التعبير بشكل كبير اعتمادًا على ما يُشار إليه بـ a .

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

ههههههههههه

ربما أكون مخطئًا ، ولكن بجانب ما قاله stevenblenkinsop ، هل من الممكن أن يكون المصطلح:

a b

قد يشير أيضًا إلى أن b ليس نوعًا إذا كان من المعروف أن b هو رقم أبجدي رقمي (لا يوجد عامل تشغيل / لا يوجد فاصل) مع ملحق اختياري [identifier] وأن a ليس كلمة رئيسية خاصة / أبجدية رقمية خاصة (على سبيل المثال ، لا يوجد استيراد / الحزمة / النوع / func) ؟.

لا أعرف قواعد الذهاب أكثر من اللازم.

بطريقة ما ، سيتم التعامل مع أنواع مثل int و Sum [int] على أية حال كتعبيرات:

type (
    nodeList = []*Node  // nodeList and []*Node are identical types
    Polar    = polar    // Polar and polar denote identical types
)

إذا كان go يسمح بوظائف infix ، فسيكون a type tag غامضًا لأن type يمكن أن يكون دالة infix أو نوعًا.

لقد لاحظت اليوم أن نظرة عامة على مشكلة هذا الاقتراح مطالبات Swift:

إعلان أن T يفي ببروتوكول Equatable يجعل استخدام == في نص الوظيفة صالحًا. يبدو أن Equatable مضمّن في Swift ، ولا يمكن تحديده بطريقة أخرى.

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

protocol Equatable2 {
    static func == (lhs: Self, rhs: Self) -> Bool
}

class uniq: Equatable2 {
    static func == (lhs: uniq, rhs: uniq) -> Bool {
        return false
    }
}

let narf = uniq(), poit = uniq()

func !=<T: Equatable2> (lhs: T, rhs: T) -> Bool {
    return !(lhs == rhs)
}

print(narf != poit)

تضمين التغريدة
كنت أتحدث عن الغموض في بناء الجملة a[b] المقترح للأدوية ، حيث أنه مستخدم بالفعل لفهرسة الشرائح والخرائط - ليس حول a b .

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

لسوء الحظ ، يحتوي على مخطط تسمية غريب تمامًا ، لذلك ليس من السهل دائمًا فهمه للوهلة الأولى. على سبيل المثال ، يعد class قيدًا لأنواع (عامة أم لا). فئة Eq هي قيد الأنواع التي يمكن مقارنة قيمها بـ '==' و '/ =':

class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

يعني أن نوعًا a يفي بالقيد Eq في حالة وجود "تخصص" (في الواقع "مثيل" في لغة Haskell) لوظائف infix == و /= التي تقبل وسيطتين ، كل منهما من النوع a وتعيد النتيجة Bool .

أحاول حاليًا تكييف بعض الأفكار الموجودة في Haskell Genics مع اقتراح Go genics ، ومعرفة مدى ملاءمتها. يسعدني حقًا أن أرى أن التحقيق يجري مع لغات أخرى بخلاف C ++ و Java:

يوضح مثال Swift أعلاه ، ومثال Haskell الخاص بي ، أن القيود المفروضة على الأنواع العامة مستخدمة بالفعل في الممارسة من قبل العديد من لغات البرمجة ، وأن قدرًا غير ضئيل من الخبرة في الأساليب المختلفة للأدوية والقيود موجود ومتاح بين مبرمجي هذه (وغيرها) اللغات.

في رأيي ، من الجدير بالتأكيد دراسة هذه التجربة قبل وضع اللمسات الأخيرة على اقتراح Go genics.

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

type Comparer interface {
  Compare(v interface{}) (*int, error)
}
type PriorityQueue<T.(Comparer)> struct {
  things []T
}

الاعتذار إذا تمت مناقشة هذا الأمر باستفاضة في مكان آخر ؛ لم أره ، لكن ما زلت مشغولاً بالأدب. لقد تجاهلت ذلك قليلاً لأنني ، حسنًا ، لا أريد الأدوية الجنيسة في أي إصدار من Go. لكن يبدو أن الفكرة تكتسب الزخم والشعور بالحتمية في المجتمع ككل.

@ jesse-amano من المثير للاهتمام أنك لا تريد الأدوية الجنيسة في أي إصدار من Go. أجد صعوبة في فهم ذلك لأنني كمبرمج لا أحب تكرار نفسي. عندما أقوم بالبرمجة في "C" أجد نفسي مضطرًا إلى تنفيذ نفس الأشياء الأساسية مثل قائمة أو شجرة على نوع بيانات جديد ، ومن المؤكد أن تطبيقاتي مليئة بالأخطاء. باستخدام الأدوية الجنيسة ، يمكننا الحصول على إصدار واحد فقط من أي خوارزمية ، ويمكن للمجتمع بأكمله المساهمة في جعل هذا الإصدار هو الأفضل. ما هو الحل لعدم تكرار نفسك؟

فيما يتعلق بالنقطة الأخرى ، يبدو أن Go يقدم صياغة جديدة للقيود العامة لأن الواجهات لا تسمح بعوامل التحميل الزائد (مثل '==' و '+'). هناك طريقتان للمضي قدمًا من هذا ، تحديد آلية جديدة للقيود العامة ، وهي الطريقة التي يبدو أن Go يسير بها ، أو السماح للواجهات بزيادة تحميل المشغلين وهي الطريقة التي أفضلها.

أفضل الخيار الثاني لأنه يحافظ على بناء جملة اللغة أصغر وأبسط ، ويسمح بالتصريح عن أنواع رقمية جديدة يمكنها استخدام عوامل التشغيل المعتادة ، على سبيل المثال الأرقام المعقدة التي يمكنك إضافتها باستخدام "+". يبدو أن الحجة ضد هذا الأمر هي أن الأشخاص قد يسيئون استخدام عامل التشغيل الزائد لجعل "+" يفعل أشياء غريبة ، ولكن هذا يبدو لي غير جدال لأنني أستطيع بالفعل إساءة استخدام أي اسم وظيفة ، على سبيل المثال يمكنني كتابة وظيفة تسمى "طباعة" يؤدي إلى محو جميع البيانات الموجودة على محرك الأقراص الثابتة وإنهاء البرنامج. أرغب في القدرة على تقييد الأحمال الزائدة لكل من المشغلين والوظائف لتتوافق مع بعض الخصائص البديهية مثل التبديل أو الترابط ، ولكن إذا لم تنطبق على كل من المشغلين والوظائف ، فأنا لا أرى فائدة كبيرة. عامل التشغيل هو مجرد دالة infix ، والوظيفة هي مجرد عامل تشغيل بادئة بعد كل شيء.

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

لذا بطريقة أتفق معك ، لم يتم تصميم Go كلغة عامة ، وأي محاولة لتفعيل الأدوية الجنسية ستكون دون المستوى الأمثل. ربما يكون من الأفضل الاحتفاظ بـ Go بدون أدوية عامة ، وتصميم لغة جديدة حول الأدوية الجنيسة من الألف إلى الياء للحفاظ على اللغة صغيرة مع بناء جملة بسيط.

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

في عدد قليل من الحالات على مدار السنوات الأربع الماضية ، وجدت نفسي في مواقف يلزم فيها تطبيق خوارزمية معقدة ولكن قابلة للتعميم على أكثر من بنيتين معقدتين لكن متسقتين ذاتيًا للبيانات ، وفي جميع الحالات - وأقول هذا مع بكل جدية - وجدت أن إنشاء الكود عبر go: إنشاء أكثر من كافٍ.

أثناء قراءتي لتقارير التجربة ، أعتقد في كثير من الحالات أن go: إنشاء أو أداة مماثلة كان من الممكن أن تحل المشكلة ، وفي بعض الحالات الأخرى أشعر أن Go1 لم تكن اللغة الصحيحة فحسب ، وربما كان هناك شيء آخر تستخدم بدلاً من ذلك (ربما مع غلاف مكون إضافي إذا احتاج بعض كود Go للاستفادة منه). لكنني أدرك أنه من السهل بالنسبة لي التكهن بما قد أفعله ، والذي قد يكون قد نجح ؛ لم يكن لدي حتى الآن أي تجارب عملية جعلتني أتمنى أن يكون لدى Go1 طرق أكثر للتعبير عن الأنواع العامة ، ولكن قد يكون لدي طريقة غريبة في التفكير في الأشياء ، أو قد يكون من حسن حظي للغاية أن أعمل فقط في المشاريع التي لا تحتاج حقًا إلى الأدوية الجنيسة.

آمل أنه إذا انتهى الأمر بـ Go2 بدعم بناء جملة عام ، فسيكون لها تخطيط مباشر إلى حد ما للمنطق الذي سيتم إنشاؤه ، مع عدم وجود حالات حافة غريبة ربما تنشأ عن الملاكمة / unboxing ، "reification" ، سلاسل الوراثة ، إلخ. التي يجب أن تقلق بشأن اللغات الأخرى.

@ jesse-amano من واقع خبرتي ، لم يكن الأمر كذلك مرات قليلة ، كل برنامج عبارة عن تكوين لخوارزميات معروفة جيدًا. لا أستطيع تذكر آخر مرة كتبت فيها خوارزمية أصلية ، ربما مشكلة تحسين معقدة تحتاج إلى معرفة بالمجال.

عند كتابة برنامج ، فإن أول شيء أفعله هو محاولة تقسيم المشكلة إلى أجزاء معروفة جيدًا يمكنني تكوينها ، ومحلل حجة ، وبعض تدفق الملفات ، وتخطيط واجهة المستخدم المعتمد على القيود. لا يقتصر الأمر على الخوارزميات المعقدة التي يرتكبها الأشخاص أخطاءً ، فبالكاد يمكن لأي شخص كتابة تنفيذ صحيح لـ "min" و "max" في المرة الأولى (راجع: http://componentsprogramming.com/writing-min-function-part5/).

تكمن مشكلة go: create في أنه في الأساس مجرد معالج ماكرو ، وليس لديه نوع أمان ، وعليك بطريقة ما كتابة التحقق والتحقق من الخطأ في الكود الذي تم إنشاؤه ، وهو ما لا يمكنك القيام به حتى تقوم بتشغيل الجيل. يصعب تصحيح هذا النوع من البرمجة الوصفية. لا أريد كتابة برنامج لكتابة البرنامج ، أريد فقط كتابة البرنامج :-)

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

مثال بسيط حقًا هو "المبادلة" ، أريد فقط مبادلة قيمتين ، لا يهمني ما هما:

swap<A>(x: *A, y: *A) {
   let tmp = *x
   *x = *y
   *y = tmp
}

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

يمكنني بسهولة ارتكاب خطأ مثل:

let tmp = *x
*y = *x
*x = tmp

كتابة هذا يدويًا في كل مرة أردت فيها تبديل محتويات مؤشرين.

أفهم أن الطريقة الاصطلاحية للقيام بهذا النوع من الأشياء في Go هي استخدام واجهة فارغة ، لكن هذا ليس نوعًا آمنًا وهو بطيء. ومع ذلك ، يبدو لي أن Go ليس لديه الميزات الصحيحة لدعم هذا النوع من البرمجة العامة بأناقة ، وتوفر الواجهات الفارغة فتحة هروب للتغلب على المشكلات. بدلاً من تغيير أسلوب go بالكامل ، يبدو أنه من الأفضل تطوير لغة مناسبة لهذا النوع من الأدوية من البداية. من المثير للاهتمام أن "Rust" تحصل على الكثير من الأشياء العامة بشكل صحيح ، ولكن نظرًا لأنها تستخدم إدارة الذاكرة الثابتة بدلاً من جمع البيانات المهملة ، فإنها تضيف قدرًا كبيرًا من التعقيد الذي ليس ضروريًا حقًا لمعظم البرمجة. أعتقد أنه بين Haskell و Go و Rust ، من المحتمل أن يكون هناك كل الأجزاء الضرورية لإنشاء لغة عامة لائقة ، وكلها مختلطة.

للحصول على معلومات: أكتب حاليًا قائمة أمنيات على Go Genics ،

بهدف تنفيذه فعليًا في مترجم Go الخاص بي gomacro ، والذي يحتوي بالفعل على تطبيق مختلف لـ Go Genics (على غرار قوالب C ++).

لم تكتمل بعد ، التعليقات مرحب بها :)

تضمين التغريدة

قرأت منشور المدونة الذي قمت بربطه حول وظيفة min ، والمنشورات الأربع التي سبقت ذلك. لم ألاحظ حتى محاولة لتقديم الحجة القائلة "بالكاد يمكن لأي شخص كتابة تنفيذ صحيح لـ 'min' ...". يبدو أن الكاتب في الواقع يعترف بأن أول تطبيق له _ هو _ صحيح ... طالما أن المجال يقتصر على الأرقام. إنها مقدمة للكائنات والفئات ، وشرط أن تتم مقارنتها على طول بعد واحد فقط ، ما لم تكن القيم في هذا البعد هي نفسها ، باستثناء متى - وما إلى ذلك ، مما يخلق تعقيدًا إضافيًا. المتطلبات الخفية الدقيقة التي تنطوي عليها الحاجة إلى تحديد وظائف المقارنة والفرز بعناية على كائن معقد هي بالضبط سبب عدم إعجابي بالأدوية العامة كمفهوم (على الأقل في Go ؛ يبدو أن Java مع Spring هي بالفعل بيئة جيدة بما يكفي للتأليف معًا مجموعة من المكتبات الناضجة في تطبيق).

أنا شخصيا لا أجد حاجة لسلامة النوع في المولدات الكلية ؛ إذا كانوا يقومون بإنشاء رمز مقروء (يساعد gofmt في تعيين شريط لهذا المستوى المنخفض نسبيًا) ، فيجب أن يكون فحص أخطاء وقت الترجمة كافيًا. لا يهم مستخدم المولد (أو الكود الذي يستدعيه) للإنتاج ، على أي حال ؛ في مجموعة المرات الصغيرة المعترف بها التي دعيت لكتابة خوارزمية عامة كماكرو ، حفنة من اختبارات الوحدة (عادةً ما تكون عائمة وسلسلة ومؤشرًا إلى هيكل - إذا كان هناك أي أنواع مشفرة يجب أن ستكون مشفرة بشكل ثابت ، سيكون أحد هؤلاء الثلاثة غير متوافق معها ؛ إذا تعذر استخدام أي من هؤلاء الثلاثة في الخوارزمية العامة ، فهي ليست خوارزمية عامة) كان كافياً لضمان عمل الماكرو بشكل مناسب.

swap مثال سيء. آسف ، لكنه كذلك. إنها بالفعل سطر واحد في Go ، ولا حاجة لوظيفة عامة لتغليفها ولا مجال للمبرمج لارتكاب خطأ غير واضح.

*y, *x = *x, *y

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

type myslice []mytype
func (s myslice) Len() int { return len(s) }
func (s myslice) Less(i, j int) bool { return s[i].whatWouldAlsoBeNeededInAGenericImpl(s[j]) }
func (s myslice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

من المسلم به أن كتابة عدد من البايتات يزيد عن SortableList<mytype>(myThings).Sort() ، لكن القراءة _ أقل كثافة_لا يمكن أن "تتلعثم" في بقية التطبيق ، وإذا ظهرت أخطاء ، فمن غير المحتمل بحاجة إلى شيء ثقيل مثل تتبع المكدس للعثور على السبب. النهج الحالي له العديد من المزايا ، وأنا قلق من أننا سنفقدها إذا اتجهنا كثيرًا إلى الأدوية الجنيسة.

@ جيسي أمانو
تنطبق مشاكل "min / max" حتى إذا كنت لا تفهم الحاجة إلى نوع مستقر. على سبيل المثال ، يقوم أحد المطورين بتنفيذ min / max لبعض أنواع البيانات في وحدة واحدة ، ثم يتم استخدامه في نوع أو خوارزمية أخرى من قبل عضو آخر في الفريق دون التحقق المناسب من الافتراضات ، ويؤدي إلى أخطاء غريبة لأنها غير مستقرة.

أعتقد أن البرمجة تتكون في الغالب من خوارزميات قياسية ، ونادرًا ما يقوم المبرمجون بإنشاء خوارزميات مبتكرة جديدة ، لذا فإن min / max و Sort مجرد أمثلة. إن التقاط الثغرات في الأمثلة المحددة التي اخترتها يوضح فقط أنني لم أختر أمثلة جيدة جدًا ، ولا يعالج النقطة الفعلية. اخترت "مبادلة" لأنه بسيط للغاية وسريع بالنسبة لي في الكتابة. كان بإمكاني اختيار العديد من الخوارزميات الأخرى ، الفرز ، التدوير ، التقسيم ، وهي خوارزميات عامة جدًا. لا يستغرق الأمر وقتًا طويلاً عندما تكتب برنامجًا يستخدم مجموعة مثل شجرة حمراء / سوداء لتضجر من الاضطرار إلى إعادة الشجرة لكل نوع بيانات مختلف تريد مجموعة منه ، لأنك تريد أمان الكتابة ، و الواجهة الفارغة أفضل قليلاً من "void *" في "C". ثم ستفعل الشيء نفسه مرة أخرى لكل خوارزمية تستخدم كل من هذه الأشجار ، مثل الترتيب المسبق ، والترتيب ، والتكرار اللاحق ، والبحث ، وذلك قبل أن نصل إلى أي أشياء معقدة مثل خوارزميات شبكة تارجان (مفككة) المجموعات والأكوام والحد الأدنى من الأشجار الممتدة وأقصر المسارات والتدفقات وما إلى ذلك)

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

على أي حال ، أنا أتفق معك حول "Go" ، ولا أعتقد أن "Go" قد تم تصميمه من البداية ليكون لغة عامة جيدة ، وإضافة الأدوية الجنيسة الآن ربما لن ينتج عنه لغة عامة جيدة ، و ستفقد بعض البساطة والصراحة التي تمتلكها بالفعل. شخصيًا إذا كنت مضطرًا للوصول إلى منشئ رمز (بما يتجاوز أشياء مثل إنشاء المدققين من مخطط json أو المحللون من ملف قواعد اللغة) ، فمن المحتمل أنك تستخدم لغة خاطئة على أي حال.

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

من المثير للاهتمام ، بقدر ما أستطيع أن أقول ، أن السبب الوحيد وراء إدخال القيود الجديدة هو أن Go لا يسمح بتحديد المشغلين في الواجهات. سمحت مقترحات الأدوية السابقة السابقة بتقييد الأنواع بالواجهات ، ولكن تم التخلي عنها لأنها لم تتعامل مع عوامل تشغيل مثل "+".

تضمين التغريدة
ربما هناك مكان أفضل لمناقشة مطولة. (ربما لا ، لقد نظرت حولي ويبدو أن هذا هو المكان المناسب لمناقشة الأدوية الجنيسة في Go2.)

أنا بالتأكيد أفهم الحاجة إلى نوع مستقر! أظن أن مؤلفي مكتبة Go1 القياسية الأصلية قد فهموها أيضًا ، نظرًا لأن sort.Stable موجود هناك منذ الإصدار العام.

أعتقد أن الشيء العظيم في حزمة sort للمكتبة القياسية هو أنها _لا تعمل على الشرائح فقط. إنه بالتأكيد في أبسط حالاته عندما يكون جهاز الاستقبال عبارة عن شريحة ، ولكن كل ما تحتاجه حقًا هو طريقة لمعرفة عدد القيم الموجودة في الحاوية (طريقة Len() int ) ، وكيفية مقارنتها ( Less(int, int) bool ) ، وكيفية مبادلتها (طريقة Swap(int, int) بالطبع). يمكنك تنفيذ sort.Interface باستخدام القنوات! إنه بطيء ، بالطبع ، لأن القنوات ليست مصممة للفهرسة الفعالة ، ولكن يمكن إثبات صحتها نظرًا لميزانية وقت التنفيذ السخية.

لا أقصد أن ألتهم ، لكن المشكلة مع المثال السيئ هي أنه ... سيء. أشياء مثل sort و min ليست سوى _لا_ نقاط لصالح ميزة لغة عالية التأثير مثل الأدوية الجنيسة. أشعر بقوة أن إحداث ثغرات في هذه الأمثلة _هل _ يعالج النقطة الفعلية ؛ _my_ النقطة هي أنه لا توجد حاجة للأدوية الجنسية عندما يكون هناك حل أفضل موجودًا بالفعل في اللغة.

@ جيسي أمانو

حل أفضل موجود بالفعل في اللغة

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

تعد sort العامة الأخرى أمثلة رائعة لأنها تُظهر الفائدة الرئيسية للأدوية العامة min القابلية للتركيب. أنها تسمح ببناء مكتبة واسعة من إجراءات التحويل العامة التي يمكن ربطها ببعضها البعض. والأهم من ذلك ، أنه سيكون سهل الاستخدام وآمنًا وسريعًا (على الأقل يكون ممكنًا مع الأدوية الجنيسة) ، ولا حاجة إلى لغة معيارية ومولدات وواجهة {} وانعكاس وميزات لغوية غامضة أخرى مستخدمة فقط لأنه لا توجد طريقة أخرى.

تضمين التغريدة

أي واحد؟

لفرز الأشياء ، الحزمة sort . يمكن فرز أي شيء يقوم بتنفيذ sort.Interface (باستخدام خوارزمية مستقرة أو غير مستقرة من اختيارك ؛ يتم توفير بعض الإصدارات الموضعية عبر حزمة sort ، لكن لك مطلق الحرية في كتابة خوارزمية خاصة بك باستخدام API مماثلة أو مختلفة). نظرًا لأن المكتبة القياسية sort.Sort و sort.Stable كلاهما يعملان على القيمة التي تم تمريرها عبر قائمة الوسائط ، فإن القيمة التي تحصل عليها هي نفس القيمة التي بدأت بها - وبالتالي ، بالضرورة ، النوع الذي استعادته هو نفس النوع الذي بدأت به. إنه آمن تمامًا من النوع ، ويقوم المترجم بكل عمل لاستنتاج ما إذا كان النوع الخاص بك ينفذ الواجهة المطلوبة وقادرًا على - على الأقل - على أكبر عدد ممكن من تحسينات وقت الترجمة باستخدام وظيفة النمط العام sort<T> .

لمبادلة الأشياء ، سطر واحد x, y = y, x . مرة أخرى ، لا يلزم وجود تأكيدات على النوع أو قوالب واجهة أو انعكاس. إنها مجرد مبادلة قيمتين. يمكن للمترجم أن يتأكد بسهولة من أن عملياتك آمنة من النوع.

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

لا أحب حزمة xerrors أيضًا ، لكن xerrors.As لا يبدو لي أنها غير اصطلاحية ؛ إنها واجهة برمجة تطبيقات مشابهة جدًا لـ json.Unmarshal ، بعد كل شيء. قد يحتاج إلى وثائق أفضل و / أو مثال على التعليمات البرمجية ، ولكن لا بأس بذلك.

لكن لا ، sort و min هما ، بمفردهما ، أمثلة رهيبة جدًا. الأول موجود بالفعل في Go وهو قابل للتكوين تمامًا ، كل ذلك دون الحاجة إلى الأدوية الجنيسة. هذا الأخير بمعناه الأوسع هو أحد مخرجات sort (التي قمنا بحلها بالفعل) ، وفي الحالات التي قد تكون هناك حاجة إلى حل أكثر تخصصًا أو محسنًا ، يمكنك كتابة الحل المتخصص على أي حال بدلاً من الاعتماد عليه الأدوية. مرة أخرى ، لا توجد مولدات أو واجهة {} أو انعكاس أو ميزات لغة "غامضة" مستخدمة في حزمة sort للمكتبة القياسية. توجد واجهات غير فارغة (تم تعريفها جيدًا في واجهة برمجة التطبيقات بحيث تحصل على أخطاء وقت الترجمة إذا كنت تستخدمها بشكل غير صحيح ، ويتم استنتاجها بحيث لا تحتاج إلى عمليات إرسال ، والتحقق منها في وقت الترجمة حتى لا تحتاج التأكيدات). قد يكون هناك بعض المتغيرات _if_ المجموعة التي تقوم بفرزها عبارة عن شريحة ، ولكن إذا كانت بنية (مثل تلك التي تمثل العقدة الجذرية لشجرة البحث الثنائي؟) ، فيمكنك جعل ذلك يرضي sort.Interface أيضًا ، لذا فهي في الواقع _ أكثر_ مرونة من المجموعة العامة.

@ جيسي أمانو

نقطتي هي أنه لا توجد حاجة للأدوية الجنسية عندما يكون هناك حل أفضل بالفعل في اللغة

أعتقد نوعًا ما أن الحل الأفضل يعتمد نسبيًا على كيفية رؤيته. إذا كانت لدينا لغة أفضل ، فقد يكون لدينا حل أفضل ، ولهذا السبب نريد تحسين هذه اللغة. على سبيل المثال ، في حالة وجود عام أفضل ، يمكن أن يكون لدينا sort أفضل في stdlib الخاص بنا ، على الأقل الطريقة الحالية لتنفيذ واجهة الفرز ليست تجربة مستخدم جيدة بالنسبة لي ، لا يزال يتعين علي كتابة الكثير من التعليمات البرمجية المماثلة الذي أشعر بشدة أنه يمكننا تجريده بعيدًا.

@ جيسي أمانو

أعتقد أن الشيء العظيم في حزمة فرز المكتبة القياسية هو أنها لا تعمل فقط على الشرائح.

أوافق ، أنا أحب النوع القياسي.

الأول موجود بالفعل في Go وهو قابل للتكوين تمامًا ، كل ذلك دون الحاجة إلى الأدوية الجنيسة.

هذا انقسام خاطئ. تعد الواجهات في Go بالفعل شكلاً من أشكال الأدوية الجنيسة. الآلية ليست الشيء نفسه. انظر إلى ما وراء بناء الجملة ، وانظر إلى الهدف ، وهو القدرة على التعبير عن أي خوارزمية بطريقة عامة دون قيود. يعد تجريد الواجهة لـ "Sort" عامًا ، فهو يسمح بفرز أي نوع بيانات يمكنه تنفيذ الطرق المطلوبة. الترميز مختلف ببساطة. يمكننا أن نكتب:

f<T>(x: T) requires Sortable(T)

مما يعني أن النوع "T" يجب أن يقوم بتنفيذ واجهة "Sortable". في "Go" ، قد تتم كتابة هذا func f(x Sortable) . لذلك على الأقل يمكن التعامل مع تطبيق الوظيفة في Go بشكل عام ، ولكن هناك عمليات لا تحب الحساب أو إلغاء الإسناد. يعمل Go بشكل جيد ، حيث يمكن اعتبار الواجهات مسندات من النوع ، لكن Go ليس لديه إجابة للعلاقات على الأنواع.

من السهل رؤية القيود باستخدام Go ، ضع في اعتبارك:

func merge(x, y Sortable)

حيث سنقوم بدمج شيئين قابلين للفرز ، لكن Go لا يسمح لنا بفرض أن هذين الأمرين يجب أن يكونا متماثلين. قارن هذا مع:

merge<T>(x: T, y: T) requires Sortable(T)

نحن هنا واضحون أننا ندمج نوعين قابلين للفرز متماثلان. "Go" يرمي معلومات النوع الأساسي بعيدًا ويعامل أي شيء "قابل للفرز" على أنه نفس الشيء.

لنحاول الحصول على مثال أفضل: لنفترض أنني أريد كتابة شجرة حمراء / سوداء يمكن أن تحتوي على أي نوع بيانات ، كمكتبة ، حتى يتمكن الآخرون من استخدامها.

تعد الواجهات في Go بالفعل شكلاً من أشكال الأدوية الجنيسة.

إذا كان الأمر كذلك ، فقد يتم إغلاق هذه المشكلة كما تم حلها بالفعل ، لأن البيان الأصلي كان:

تقترح هذه المشكلة أن Go يجب أن تدعم شكلاً من أشكال البرمجة العامة.

المراوغة تلحق الضرر بجميع الأطراف. تعد الواجهات بالفعل _a_ شكلًا من أشكال البرمجة العامة ، وهي في الواقع ليست بالضرورة ، من تلقاء نفسها ، تحل كل مشكلة أخيرة يمكن أن تحلها الأشكال الأخرى من البرمجة العامة. لذا ، دعونا ، من أجل التبسيط ، نسمح لأي مشكلة يمكن حلها بأدوات خارج نطاق هذا الاقتراح / القضية ليتم اعتبارها "محلولة بدون أدوية". (أعتقد أن الغالبية العظمى من المشكلات القابلة للحل التي نواجهها في العالم الحقيقي ، إن لم تكن كلها ، موجودة في هذه المجموعة ، ولكن هذا فقط للتأكد من أننا جميعًا نتحدث نفس اللغة.)

ضع في اعتبارك: func merge(x, y Sortable)

ليس من الواضح بالنسبة لي سبب اختلاف دمج شيئين قابلين للفرز (أو الأشياء التي تنفذ sort.Interface ) بأي شكل من الأشكال عن دمج مجموعتين _ بشكل عام_. بالنسبة للشرائح ، هذا هو append ؛ للخرائط هذا for k, v := range m { n[k] = v } ؛ وبالنسبة لهياكل البيانات الأكثر تعقيدًا ، هناك بالضرورة استراتيجيات دمج أكثر تعقيدًا اعتمادًا على الهيكل (قد تكون محتوياتها مطلوبة لتنفيذ بعض الأساليب التي يحتاجها الهيكل). بافتراض أنك تتحدث عن خوارزمية فرز أكثر تعقيدًا تقسم وتختار خوارزميات فرعية للأقسام قبل دمجها معًا مرة أخرى ، فإن ما تحتاجه ليس أن تكون الأقسام "قابلة للفرز" بل نوعًا من الضمان أن الأقسام الخاصة بك بالفعل _sorted_ قبل الدمج. هذه مشكلة من نوع مختلف تمامًا ، وليست مشكلة تساعد بنية القالب في حلها بأي طريقة واضحة ؛ بطبيعة الحال ، قد ترغب في بعض الاختبارات الصارمة للوحدة لضمان موثوقية خوارزمية (خوارزمية) فرز الدمج ، ولكن بالتأكيد لن ترغب في كشف واجهة برمجة تطبيقات (API) تم تصديرها تثقل كاهل المطور بهذا النوع من الأشياء.

لقد طرحت نقطة مثيرة للاهتمام حول عدم وجود طريقة جيدة للتحقق مما إذا كانت قيمتان من نفس النوع بدون انعكاس ، ومفاتيح الكتابة ، وما إلى ذلك. أشعر أن استخدام interface{} هو حل مقبول تمامًا في حالة حاويات الأغراض العامة (على سبيل المثال ، قائمة مرتبطة دائرية) حيث أن النموذج المعياري المتضمن في تغليف API من أجل أمان النوع هو أمر تافه تمامًا:

type MyStack struct { stack Stack }
func (s *MyStack) Push(v MyType) error { return s.stack.Push(v) }
func (s *MyStack) Pop() (MyType, error) {
  v, err := s.stack.Pop()
  var m MyType
  if v != nil {
    if m, ok := v.(MyType); ok { return m, err; }
    panic("this code should be unreachable from the exported API")
  }
  return nil, err
}

أجد صعوبة في تخيل سبب كون هذا النموذج المعياري مشكلة ، ولكن إذا كان الأمر كذلك ، فقد يكون البديل المعقول عبارة عن نموذج (نص /). يمكنك إضافة تعليق توضيحي للأنواع التي تريد تعريف المكدسات لها بتعليق //go:generate stackify MyType github.com/me/myproject/mytype ، والسماح لـ go generate بإنتاج النموذج المعياري نيابة عنك. طالما أن cmd/stackify/stackify_test.go يجربها بهيكل واحد على الأقل ونوع مدمج واحد على الأقل ، ويقوم بالتجميع والمرور ، لا أفهم سبب كون هذه مشكلة - وربما تكون قريبة جدًا لما كان أي مترجم سينتهي به الأمر "تحت الغطاء" إذا حددت نموذجًا. الاختلاف الوحيد هو أن الأخطاء مفيدة أكثر لأنها أقل كثافة.

(قد تكون هناك أيضًا حالات نريد فيها _شيء ما_ عام يهتم بكون شيئين من نفس النوع أكثر مما يهتم بسلوكهما ، والذي لا يقع ضمن فئة "حاويات الأشياء". سيكون ذلك ممتعًا للغاية ، ولكن قد لا تكون إضافة بناء جملة بناء قالب عام إلى اللغة هي الحل الوحيد المتاح المتاح.)

لنفترض أن الصيغة المعيارية ليست مشكلة ، فأنا مهتم بمعالجة مشكلة إنشاء شجرة حمراء / سوداء يسهل على المتصلين استخدامها كحزم مثل sort أو encoding/json . سأفشل بالتأكيد لأن ... حسنًا ، أنا لست ذكيًا إلى هذا الحد. لكنني متحمس لمعرفة مدى قربي.

تحرير: يمكن رؤية بدايات أحد الأمثلة هنا ، على الرغم من أنه بعيد عن الاكتمال (أفضل ما يمكنني تجميعه في غضون ساعتين). بالطبع ، توجد أيضًا محاولات أخرى لهياكل بيانات مماثلة.

@ جيسي أمانو

إذا كان الأمر كذلك ، فقد يتم إغلاق هذه المشكلة كما تم حلها بالفعل> لأن البيان الأصلي كان:

لا يقتصر الأمر على أن الواجهات _ هي _ شكل من أشكال الأدوية الجنيسة ، ولكن تحسين نهج الواجهات يمكن أن يقودنا جميعًا في مجال الأدوية الجنيسة. على سبيل المثال ، تسمح الواجهات متعددة المعلمات (حيث يمكنك الحصول على أكثر من "جهاز استقبال") بعلاقات على الأنواع. إن السماح للواجهات بتجاوز عوامل التشغيل مثل الإضافة وإلغاء الإسناد سيزيل الحاجة إلى أي شكل آخر من أشكال القيود على الأنواع. واجهات _يمكن_ أن تكون جميع قيود النوع التي تحتاجها ، إذا تم تصميمها مع فهم نقطة النهاية للأدوية العامة بالكامل.

تتشابه الواجهات من الناحية اللغوية مع فئات النوع الخاصة بـ Haskell ، وخصائص Rust التي _فعل_ تحل هذه المشكلات العامة. تعمل فئات النوع والسمات على حل جميع المشكلات العامة نفسها التي تحلها قوالب C ++ ، ولكن بطريقة آمنة من النوع (ولكن ربما ليس كل استخدامات البرمجة الوصفية ، والتي أعتقد أنها شيء جيد).

أجد صعوبة في تخيل سبب كون هذا النموذج المعياري مشكلة ، ولكن إذا كان الأمر كذلك ، فقد يكون البديل المعقول عبارة عن نموذج (نص /).

أنا شخصياً ليس لدي مشكلة مع هذا القدر الكبير من النماذج ، لكنني أفهم الرغبة في عدم وجود نموذج معياري على الإطلاق ، بصفتي مبرمجًا ، هذا ممل ومتكرر ، وهو بالضبط نوع المهمة التي نكتب البرامج لتجنبها. لذا مرة أخرى ، شخصيًا ، أعتقد أن كتابة تنفيذ لواجهة "مكدس" / فئة نوع هي بالضبط الطريقة _ الصحيحة لجعل نوع البيانات الخاص بك "قابلًا للتكديس".

هناك نوعان من القيود مع Go والتي تحبط المزيد من البرمجة العامة. مشكلة التكافؤ "النوع" ، على سبيل المثال تحديد وظائف الرياضيات بحيث يجب أن تكون النتيجة وجميع الوسائط متطابقة. يمكننا تخيل:

mul<T>(x, y T) T requires Addable(T) {
    r := 0
    for i := 0; i < y; ++i  {
        r = r + x
    }
    return r
}

للوفاء بالقيود المفروضة على علامة "+" ، نحتاج إلى التأكد من أن x و y رقميان ، ولكنهما أيضًا من النوع الأساسي نفسه.

والآخر هو تحديد الواجهات لنوع واحد فقط من "المستقبلات". يعني هذا القيد أنه ليس عليك فقط كتابة النموذج أعلاه مرة واحدة (وهو ما أعتقد أنه معقول) ولكن لكل نوع مختلف تريد وضعه في MyStack. ما نريده هو الإعلان عن النوع الموجود كجزء من الواجهة:

type Stack<T> interface {...}

سيسمح هذا ، ضمن أشياء أخرى ، بالإعلان عن تطبيق حدودي في T حتى نتمكن من وضع أي T في MyStack باستخدام واجهة Stack ، طالما أن جميع استخدامات Push و يعمل Pop على نفس مثيل MyStack على نفس نوع "القيمة".

مع هذين التغييرين يجب أن نكون قادرين على إنشاء شجرة عامة حمراء / سوداء. يجب أن يكون ذلك ممكنًا بدونها ، ولكن مثل Stack ، سيتعين عليك الإعلان عن مثيل جديد للواجهة لكل نوع ترغب في وضعه في الشجرة الحمراء / السوداء.

من وجهة نظري ، فإن الامتدادين أعلاه للواجهات هما كل ما هو مطلوب لـ Go لدعم "الأدوية الجنيسة" بشكل كامل.

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

type Map<Key, Value> interface {
   put(x Key, y Value) 
   get(x Key) Value
}

ثم يمكن تقديم الشجرة الحمراء / السوداء كتنفيذ. من الناحية المثالية ، نريد كتابة رمز لا يعتمد على التنفيذ ، لذلك يمكنك توفير جدول تجزئة أو شجرة حمراء سوداء أو BTree. ثم نكتب الكود الخاص بنا:

f<K, V, T>(index T) T requires Map<K, V> {
   ...
}

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

أثناء التنقل كما هي الآن ، سنحتاج إلى تحديد خريطة معينة مثل هذه:

type MapIntString interface {
   put(x Int, y String)
   get(x Int) String
}

هذا ليس سيئًا للغاية ، ولكن هذا يعني أنه يجب كتابة وظيفة "المكتبة" f لكل مجموعة ممكنة من أنواع المفاتيح والقيم إذا كنا سنكون قادرين على استخدامها في تطبيق حيث لا " ر معرفة أنواع المفاتيح والقيم عند كتابة المكتبة.

بينما أتفق مع keean التعليق الأخير ، فإن الصعوبة تكمن في كتابة شجرة حمراء / سوداء في Go والتي تنفذ واجهة معروفة ، على سبيل المثال التي اقترحت للتو.

بدون الأدوية الجنيسة ، من المعروف أنه من أجل تنفيذ حاويات حيادية النوع ، يتعين على المرء استخدام interface{} و / أو الانعكاس - لسوء الحظ ، كلا النهجين بطيئان وعرضة للخطأ.

تضمين التغريدة

لا يقتصر الأمر على أن الواجهات هي شكل من أشكال الأدوية الجنيسة ، ولكن تحسين نهج الواجهات يمكن أن يقودنا إلى الأمام في الأدوية الجنيسة.

لا أرى أيًا من المقترحات المرتبطة بهذه المسألة ، حتى الآن ، على أنها تحسين. يبدو من غير الخلاف إلى حد ما أن نقول إنها كلها معيبة بطريقة ما. أعتقد أن هذه العيوب تفوق بشدة أي فائدة ، والعديد من المزايا التي تمت المطالبة بها مدعومة بالفعل من خلال الميزات الحالية. إيماني قائم على الخبرة العملية ، وليس التخمين ، لكنه لا يزال قصصياً.

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

أنا لا أتفق مع هذا أيضا. بصفتي محترفًا مدفوع الأجر ، فإن هدفي هو تقليل تكاليف الوقت / الجهد - بالنسبة لي وللآخرين - مع زيادة مكاسب صاحب العمل ، ولكن يمكن قياسها. تكون المهمة "مملة" سيئة فقط إذا كانت تستغرق وقتًا طويلاً ؛ لا يمكن أن يكون صعبًا ، أو لن يكون مملاً. إذا كان الأمر يستغرق وقتًا طويلاً فقط في البداية ، ولكنه يلغي الأنشطة التي تستغرق وقتًا طويلاً في المستقبل و / أو يُطلق المنتج في وقت أقرب ، فلا يزال الأمر يستحق ذلك تمامًا.

ثم يمكن تقديم الشجرة الحمراء / السوداء كتنفيذ.

أعتقد أنني قد أحرزت تقدمًا لائقًا في اليومين الماضيين في تنفيذ شجرة حمراء / سوداء ، (لم تنته ؛ تفتقر حتى إلى نسخة تمهيدية) لكنني قلق من أنني فشلت بالفعل في توضيح وجهة نظري إذا لم تكن وفيرة. واضح أن هدفي ليس العمل نحو واجهة بل العمل نحو التنفيذ. أنا أكتب شجرة حمراء / سوداء ، وبالطبع أريدها أن تكون مفيدة ، لكني لا أهتم بالأشياء المحددة التي قد يرغب المطورون الآخرون في استخدامها من أجلها.

أعلم أن الحد الأدنى من الواجهة التي تتطلبها مكتبة الشجرة باللون الأحمر / الأسود هي تلك التي يوجد فيها ترتيب "ضعيف" على عناصرها ، لذلك أحتاج إلى شيء _ مثل _ وظيفة تسمى Less(v interface{}) bool ، ولكن إذا كان لدى المتصل طريقة يفعل شيئًا مشابهًا ولكن لم يتم تسميته Less(v interface{}) bool ، الأمر متروك لهم لكتابة الأغلفة / الحشوات المعيارية لجعلها تعمل.

عند الوصول إلى العناصر الموجودة في الشجرة الحمراء / السوداء ، تحصل على interface{} ، ولكن إذا كنت على استعداد للوثوق في ضماني بأن المكتبة قدمت _is_ شجرة حمراء / سوداء ، فأنا لا أفهم سبب عدم رغبتك في ذلك. لا تثق في أن أنواع العناصر التي ستدخلها ستكون بالضبط أنواع العناصر التي تحصل عليها. إذا كنت تثق في كلا الضمانتين ، فلن تكون المكتبة عرضة للخطأ على الإطلاق. ما عليك سوى كتابة (أو لصق) عشرات الأسطر أو نحو ذلك من التعليمات البرمجية لتغطية تأكيدات الكتابة.

الآن لديك مكتبة آمنة تمامًا (مرة أخرى ، بافتراض عدم وجود أكثر من مستوى الثقة الذي يجب أن تكون على استعداد لتقديمه لتنزيل المكتبة في المقام الأول) التي تحتوي حتى على أسماء الوظائف التي تريدها بالضبط. هذا مهم. في نظام إيكولوجي على غرار جافا حيث ينحني مؤلفو المكتبات إلى الخلف للتشفير مقابل تعريف واجهة _exact_ (لقد _لقد فعلوا ذلك تقريبًا ، لأن اللغة تفرضه عن طريق بناء جملة class MyClassImpl extends AbstractMyClass implements IMyClass ) وهناك مجموعة من البيروقراطية الإضافية ، عليك أن تبذل قصارى جهدك لإنشاء واجهة لمكتبة الطرف الثالث لتلائم معايير الترميز الخاصة بمؤسستك (وهي نفس القدر من النموذج المعياري ، إن لم يكن أكثر) ، أو السماح لهذا أن يكون "استثناء" لـ معايير الترميز الخاصة بمؤسستك (وفي النهاية تمتلك مؤسستك العديد من الاستثناءات في معاييرها كما في قواعدها البرمجية) ، أو تتخلى عن استخدام مكتبة جيدة تمامًا (على افتراض ، من أجل الجدل ، أن المكتبة جيدة بالفعل).

من الناحية المثالية ، نريد كتابة رمز لا يعتمد على التنفيذ ، لذلك يمكنك توفير جدول تجزئة أو شجرة حمراء سوداء أو BTree.

أنا أتفق مع هذا المثال ، لكنني أعتقد أن Go يرضيها بالفعل. بواجهة مثل:

type MyStorage interface {
  Get(KeyType) (ValueType, error)
  Put(KeyType, ValueType) error
}

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

بصفتي مشرفًا (افتراضيًا) لمكتبة شجرة حمراء / سوداء ، لا يهمني ما هي أنواعك. سأستخدم فقط interface{} لجميع وظائفي الأساسية التي تتعامل مع "بعض البيانات" ، و _ ربما _ أقدم بعض نماذج funcs التي تم تصديرها والتي تتيح لك استخدامها بسهولة أكبر مع الأنواع الشائعة مثل string و int . لكن الأمر متروك للمتصل لتوفير طبقة رقيقة للغاية حول واجهة برمجة التطبيقات هذه لجعلها آمنة لأي أنواع مخصصة قد ينتهي بهم الأمر إلى تحديدها. لكن الشيء الوحيد المهم في واجهة برمجة التطبيقات التي أقدمها هو أنها تسمح للمتصل بفعل كل الأشياء التي قد يتوقع أن تكون الشجرة الحمراء / السوداء قادرة على القيام بها.

بصفتي متصلًا (افتراضيًا) بمكتبة شجرة حمراء / سوداء ، ربما أريدها فقط للتخزين السريع ووقت البحث. لا يهمني أنها شجرة حمراء / سوداء. يهمني أنه يمكنني Get أشياء منه و Put أشياء فيه ، والأهم من ذلك - أنا أهتم بما هي هذه الأشياء. إذا كانت المكتبة لا تقدم وظائف تسمى Get و Put ، أو لا يمكنها التفاعل بشكل مثالي مع الأنواع التي حددتها ، فهذا لا يهمني طالما أنه سهل بالنسبة لي لكتابة الطرق Get و Put بنفسي ، وجعل النوع الخاص بي يلبي الواجهة التي تحتاجها المكتبة أثناء تواجدي فيها. إذا لم يكن الأمر سهلاً ، فأنا عادة ما أجد أنه خطأ مؤلف المكتبة ، وليس خطأ اللغة ، ولكن مرة أخرى من الممكن أن تكون هناك أمثلة مضادة لست على دراية بها.

بالمناسبة ، يمكن أن تصبح الشفرة أكثر تشابكًا إذا لم تكن هكذا. كما تقول ، هناك العديد من التطبيقات الممكنة لمتجر المفتاح / القيمة. يؤدي تمرير "مفهوم" تخزين القيمة / المفتاح المجرد إلى إخفاء مدى تعقيد كيفية إنجاز تخزين المفتاح / القيمة ، وقد يختار مطور في فريقي الخيار الخطأ لمهمته (بما في ذلك الإصدار المستقبلي من نفسي الذي لديه معرفة بالمفتاح / تم ترحيل تنفيذ تخزين القيمة من الذاكرة!). قد يحتوي التطبيق أو اختبارات الوحدة الخاصة به ، على الرغم من بذلنا قصارى جهدنا في مراجعة التعليمات البرمجية ، على رمز دقيق يعتمد على التنفيذ يتوقف عن العمل بشكل موثوق عندما تعتمد بعض مخازن المفاتيح / القيمة على اتصال بقاعدة بيانات والبعض الآخر لا. إنه أمر مؤلم عندما يأتي تقرير الخطأ مع تتبع مكدس كبير ، والسطر الوحيد في تتبع المكدس يشير إلى شيء ما في _real_ يشير إلى نقطة في سطر يستخدم قيمة واجهة ، كل ذلك لأن تنفيذ تلك الواجهة يتم إنشاؤه رمز (والذي يمكنك رؤيتها فقط في وقت التشغيل) بدلاً من البنية العادية ، حيث تقوم الطرق بإرجاع قيم خطأ يمكن قراءتها.

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

ما زلت أرغب في رؤية الأشياء التالية بلغة عامة:

  • أنواع المعلمات ، مثل: RBTree<Int, String> لأن هذا من شأنه أن يفرض أمان مجموعات المستخدمين.
  • متغيرات النوع ، مثل: f<T>(x, y T) T ، لأن هذا ضروري لتحديد عائلات الوظائف ذات الصلة مثل الجمع والطرح وما إلى ذلك حيث تكون الوظيفة متعددة الأشكال ، لكننا نطلب أن تكون جميع الوسائط من نفس النوع الأساسي.
  • قيود النوع ، مثل: f<T: Addable>(x, y T) T ، والتي تقوم بتطبيق واجهات على متغيرات النوع ، لأنه بمجرد أن نقدم متغيرات النوع ، نحتاج إلى طريقة لتقييد متغيرات النوع هذه بدلاً من التعامل مع Addable كنوع. إذا نظرنا Addable كنوع وكتبنا f(x, y Addable) Addable ، فليس لدينا طريقة لمعرفة ما إذا كانت الأنواع الأساسية الأصلية x و y هي نفسها بعضها البعض أو النوع الذي تم إرجاعه.
  • واجهات متعددة المعلمات ، مثل: type<K, V> Map<K, V> interface {...} ، والتي يمكن استخدامها مثل merge<K, V, T: Map<K, V>>(x, y T) T والتي تسمح لنا بالتصريح عن الواجهات التي يتم تحديدها ليس فقط من خلال نوع الحاوية ، ولكن في هذه الحالة أيضًا المفتاح والقيمة أنواع الخريطة.

أعتقد أن كلًا من هؤلاء سيزيد من القوة التجريدية للغة.

أي تقدم أو جدول زمني في هذا?

leaxoy هناك حديث مجدول على "Generics in Go" بقلم ianlancetaylor في GopherCon . أتوقع أن أسمع المزيد عن الوضع الحالي في هذا الحديث.

griesemer شكرا لهذا الارتباط.

keean أود أيضًا أن أرى جملة Where من Rust هنا ، والتي قد تكون تحسينًا لمقترحك type constraints . يسمح باستخدام نظام النوع لتقييد سلوك مثل "بدء معاملة قبل الاستعلام" ليتم التحقق من النوع دون انعكاس وقت التشغيل. تحقق من هذا الفيديو عليها: https://www.youtube.com/watch؟v=jSpio0x7024

jadbox آسف إذا لم يكن توضيحي واضحًا ، لكن عبارة "أين" هي بالضبط ما كنت أقترحه بالضبط. الأشياء بعد "أين" في الصدأ هي قيود على الكتابة ، لكنني أعتقد أنني استخدمت الكلمة الأساسية "يتطلب" في منشور سابق بدلاً من ذلك. تم تنفيذ كل هذه الأشياء في Haskell قبل عقد من الزمان على الأقل ، باستثناء أن Haskell تستخدم عامل التشغيل '=>' في توقيعات النوع للإشارة إلى قيود النوع ، لكنها الآلية الأساسية نفسها.

لقد تركت هذا من منشوري الملخص أعلاه لأنني أردت أن أبقي الأمور بسيطة ، لكني أرغب في شيء مثل هذا:

merge<K, V, T>(x, y T) T requires T: Map<K, V>

لكنها لا تضيف أي شيء إلى ما يمكنك فعله بصرف النظر عن بناء الجملة الذي يمكن أن يكون أكثر قابلية للقراءة لمجموعات القيود الطويلة. يمكنك تمثيل أي شيء يمكنك باستخدام عبارة "where" بوضع القيد بعد كتابة المتغير في الإعلان الأولي مثل هذا:

merge<K, V, T: Map<K, V>>(x, y T) T

بشرط أنه يمكنك الرجوع إلى متغيرات النوع قبل الإعلان عنها ، يمكنك وضع أي قيود هناك ، ويمكنك استخدام قائمة مفصولة بفواصل لتطبيق قيود متعددة على متغير النوع نفسه.

وبقدر ما أعلم ، فإن الميزة الوحيدة لعبارة "where" / "تتطلب" هي أن جميع متغيرات النوع قد تم الإعلان عنها مسبقًا ، مما قد يسهل على المحلل اللغوي والاستدلال اللطيف.

هل لا يزال هذا هو الخيط الصحيح للتعليقات / المناقشة حول اقتراح Go 2 Generics الحالي / الأخير الذي تم الإعلان عنه مؤخرًا؟

باختصار ، أنا حقًا أحب الاتجاه الذي يسير فيه الاقتراح بشكل عام وآلية العقود بشكل خاص. لكنني مهتم بما يبدو أنه افتراض أحادي التفكير في جميع أنحاء أن المعلمات العامة لوقت الترجمة يجب أن تكون (دائمًا) معلمات نوع. لقد كتبت بعض التعليقات حول هذه المشكلة هنا:

هل تكفي معلمات النوع العامة فقط لـ Go 2 Generics؟

بالتأكيد التعليقات هنا جيدة ، لكن بشكل عام لا أعتقد أن مشكلات GitHub هي تنسيق جيد للمناقشة ، لأنها لا توفر أي نوع من الترابط. أعتقد أن القوائم البريدية أفضل.

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

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

Go فريد تمامًا بسبب الطريقة التي هو عليها. إذا لم يتم كسرها ، فالرجاء عدم محاولة إصلاحها!

يجب على الأشخاص غير الراضين عن الطريقة التي تم تصميم Go بها الذهاب واستخدام واحدة من العديد من اللغات الأخرى التي تمتلك بالفعل هذا التعقيد الإضافي والمزعج.

Go فريد تمامًا بسبب الطريقة التي هو عليها. إذا لم يتم كسرها ، فالرجاء عدم محاولة إصلاحها!

إنه مكسور ، وبالتالي يجب إصلاحه.

إنه مكسور ، وبالتالي يجب إصلاحه.

قد لا تعمل بالطريقة التي تعتقد أنها يجب أن تعمل - ولكن عندئذٍ لا تستطيع اللغة ذلك أبدًا. إنه بالتأكيد لم ينكسر بأي حال من الأحوال. إن النظر في المعلومات المتاحة والنقاش ثم أخذ الوقت لاتخاذ قرار مستنير ومعقول هو دائمًا الخيار الأفضل. عانت العديد من اللغات الأخرى ، في رأيي ، بسبب إضافة المزيد والمزيد من الميزات لحل المزيد والمزيد من المشكلات المحتملة. تذكر أن "لا" مؤقتة ، و "نعم" إلى الأبد.

بعد أن شاركت في القضايا الضخمة السابقة ، هل لي أن أقترح فتح قناة على Gopher Slack لأولئك الذين يرغبون في مناقشة هذا الأمر ، ويتم قفل المشكلة مؤقتًا ، ثم يتم نشر الأوقات عندما يتم إلغاء تجميد المشكلة لأي شخص يريد دمج مناقشة من سلاك؟ لم تعد إصدارات Github تعمل كمنتدى بمجرد ظهور الرابط "478 عنصرًا مخفيًا تحميل المزيد ...".

اسمح لي أن أقترح فتح قناة على Gopher Slack لأولئك الذين يريدون مناقشة هذا الأمر
تعتبر القوائم البريدية أفضل لأنها توفر أرشيفًا قابلاً للبحث. لا يزال من الممكن نشر ملخص حول هذه المسألة.

بعد أن شاركت في قضايا ضخمة سابقة ، هل لي أن أقترح فتح قناة على Gopher Slack لأولئك الذين يرغبون في مناقشة هذا الأمر.

من فضلك لا تنقل المناقشة بالكامل إلى منصات مغلقة. إذا كان هناك أي مكان ، فإن golang-nuts متاح للجميع (ish؟ لا أعرف ما إذا كان ذلك يعمل بدون حساب Google أم لا ، ولكن على الأقل هو وسيلة اتصال قياسية يمتلكها الجميع أو يمكنهم الحصول عليها) ويجب نقلها إلى هناك . GitHub سيء بما فيه الكفاية ، لكنني أتقبل على مضض أننا عالقون معه للتواصل ، ولا يمكن للجميع الحصول على حساب Slack أو استخدام عملائهم الرهيبين.

لا يمكن للجميع الحصول على حساب Slack أو استخدام عملائهم السيئين

ماذا تعني "يمكن" هنا؟ هل هناك قيود حقيقية على Slack لا أعرف عنها أو لا يحبها الناس فقط؟ أعتقد أن هذا الأخير جيد ، لكن بعض الأشخاص يقاطعون Github أيضًا لأنهم لا يحبون Microsoft ، لذلك تخسر بعض الأشخاص وتكسب آخرين.

لا يمكن للجميع الحصول على حساب Slack أو استخدام عملائهم السيئين

ماذا تعني "يمكن" هنا؟ هل هناك قيود حقيقية على Slack لا أعرف عنها أو لا يحبها الناس فقط؟ أعتقد أن هذا الأخير جيد ، لكن بعض الأشخاص يقاطعون Github أيضًا لأنهم لا يحبون Microsoft ، لذلك تخسر بعض الأشخاص وتكسب آخرين.

Slack هي شركة أمريكية وعلى هذا النحو ستتبع أي سياسات خارجية تفرضها الولايات المتحدة.

جيثب لديه نفس المشكلة وكان فقط في الأخبار لطرد الإيرانيين دون سابق إنذار. إنه أمر مؤسف ، ولكن ما لم نستخدم Tor أو IPFS أو شيء من هذا القبيل ، فسيتعين علينا احترام القانون الأمريكي / الأوروبي في أي منتدى مناقشة عملي.

جيثب لديه نفس المشكلة وكان فقط في الأخبار لطرد الإيرانيين دون سابق إنذار. إنه أمر مؤسف ، ولكن ما لم نستخدم Tor أو IPFS أو شيء من هذا القبيل ، فسيتعين علينا احترام القانون الأمريكي / الأوروبي في أي منتدى مناقشة عملي.

نعم ، نحن عالقون في GitHub ومجموعات Google. دعونا لا نضيف المزيد من الخدمات ذات المشاكل إلى القائمة. كما أن الدردشة ليست أرشيفًا جيدًا ؛ من الصعب البحث في هذه المناقشات عندما تكون مترابطة بشكل جيد وعلى golang-nuts (حيث تأتي مباشرة إلى صندوق الوارد الخاص بك). Slack يعني أنه إذا لم تكن في نفس المنطقة الزمنية مثل أي شخص آخر ، فيجب عليك الخوض في مجموعات كبيرة من أرشيفات الدردشة ، مرة واحدة غير متسلسلة ، وما إلى ذلك ، تعني القوائم البريدية أن لديك على الأقل منظمًا إلى حد ما في سلاسل الرسائل ، ويميل الأشخاص إلى أخذها مزيد من الوقت في ردودهم حتى لا تحصل على عدد كبير من التعليقات العشوائية الفردية التي يتم تركها بشكل عرضي. أيضًا ليس لدي حساب على Slack ولن يعمل عملاؤهم الأغبياء على أي من الأجهزة التي أستخدمها. من ناحية أخرى ، يعمل Mutt (أو عميل البريد الإلكتروني الذي تختاره ، معايير yay) في كل مكان.

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

فيما يتعلق بتفرد Go: يحتوي Go على بعض الميزات الرائعة ولكنها ليست فريدة كما يعتقد البعض. على سبيل المثال ، فإن CLU و Modula-3 لهما أهداف مماثلة ومكافأة مماثلة ، وكلاهما يدعم الأدوية الجنيسة في شكل ما (منذ 1975 ~ في حالة CLU!) ليس لديهم دعم صناعي في الوقت الحالي ولكن FWIW ، من الممكن الحصول على مترجم يعمل لكليهما.

استفسارا حول بناء الجملة ، هل الكلمة الأساسية type في معلمات النوع مطلوبة؟ وهل من المنطقي اعتماد <> لمعلمات النوع مثل اللغات الأخرى؟ قد يجعل هذا الأشياء أكثر قابلية للقراءة ومألوفة ...

على الرغم من أنني لست معارضًا لما هو عليه في الاقتراح ، فما عليك سوى طرح هذا الأمر للنظر فيه

بدلا من:

type Vector(type Element) []Element
var v Vector(int)
func (v *Vector(Element)) Push(x Element) { *v = append(*v, x) }
type VectorInt = Vector(int)

كان باستطاعتنا ان

type Vector<Element> []Element
var v Vector<int>
func (v *Vector<Element>) Push(x Element) { *v = append(*v, x) }
type VectorInt = Vector<int>

تم ذكر بناء الجملة <> في المسودة ، jnericks (اسم المستخدم الخاص بك مثالي لهذه المناقشة ...). الحجة الأساسية ضدها هي أنها تزيد بشكل كبير من تعقيد المحلل اللغوي. بشكل عام ، يجعل تحليل Go لغة أصعب بكثير من أجل القليل من الفائدة. يتفق معظم الناس على أنه يحسن قابلية القراءة ، ولكن هناك خلاف حول ما إذا كان يستحق المقايضة أم لا. أنا شخصياً لا أعتقد ذلك.

يعد استخدام الكلمة الرئيسية type ضروريًا لإزالة الغموض. وإلا فإنه من الصعب معرفة الفرق بين func Example(T)(arg int) {} و func Example(arg int) (int) {} .

قرأت من خلال أحدث اقتراح حول go genics. كلها تتناسب مع ذوقي ماعدا قواعد إعلان العقد.

كما نعلم ، نعلن دائمًا عن البنية أو الواجهة مثل هذا:

type MyStruct struct {
        a int
        s string
}

type MyInterface inteface {
    Method1() err
    Method2() string
}

لكن إعلان العقد في الاقتراح الأخير مثل هذا:

contract Ordered(T) {
    T int, int8
}

contract G(Node, Edge) {
    Node Edges() []Edge
    Edge Nodes() (from Node, to Node)
}

في رأيي ، قواعد العقد غير متوافقة في الشكل مع النهج التقليدي. ماذا عن القواعد كما يلي:

type Ordered(T) contract {
    T int, int8
}

if there is only one type parameter, the declaration above can be also wrote like this:

type Ordered contract {
    int , int8
}


if there are more than one type parameter, we have to use named parameter:

type G(Node, Edge) contract {
    Node Edges() []Edge
    Edge Nodes() (from Node, to Node)
}

الآن شكل العقد يتوافق مع التقليدية. يمكننا أن نعلن عن عقد في نوع كتلة مع واجهة ، هيكل:

type (
        Sequence contract {
                string, []byte
        }

    Stringer(T) contract {
        T String() string
    }

    Stringer contract { // equivalent with the above Stringer(T), single type parameter could be omitted
        String() string
    }

        MyStruct struct {
                a int
                b string
        }

    G(Node, Edge) contract {
        Node Edges() []Edge
        Edge Nodes() (from Node, to Node)
    }
)

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

bigwhite ما زلنا نناقش هذا الترميز. الحجة المؤيدة للتدوين المقترح في مسودة التصميم هي أن العقد ليس نوعًا (على سبيل المثال لا يمكن للمرء الإعلان عن متغير من نوع العقد) ، وبالتالي فإن العقد هو نوع جديد من الكيانات في نفس عبثًا مثل الثابت أو دالة أو متغير أو نوع. الحجة المؤيدة لاقتراحك هي أن العقد هو ببساطة "نوع من النوع" (أو نوع تعريف) وبالتالي يجب أن يتبع تدوينًا متسقًا. هناك حجة أخرى لصالح اقتراحك وهي أنه سيسمح باستخدام العناصر الحرفية للعقد "المجهول" بدون الحاجة إلى التصريح عنها صراحة. باختصار ، لم يتم تسوية هذا IMHO بعد. ولكن من السهل أيضًا التغيير في المستقبل.

يدعم FWIW، CL 187317 كلا الترميزين في الوقت الحالي (على الرغم من أنه يجب كتابة معلمة العقد مع العقد) ، على سبيل المثال:

type C contract(X) { ... }

و

contract C (X) { ... }

يتم قبولها وتمثيلها بنفس الطريقة داخليًا. سيكون النهج الأكثر اتساقًا هو:

type C(type X) contract { ... }

العقد ليس نوعًا. إنه ليس حتى نوعًا فرعيًا ، لأن الأنواع الوحيدة منه
تهتم بنفسها مع معاييرها. لا يوجد نوع منفصل لجهاز الاستقبال
الذي يمكن اعتباره نوعًا تعريفيًا من.

يحتوي Go أيضًا على إعلانات وظيفية:

func Name(args) { body }

التي يعكسها بناء جملة العقد المقترح بشكل مباشر.

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

تحرير: فيما يتعلق بالعقود المضمنة ، لدى Go قيم حرفية للوظيفة. لا أرى أي سبب لعدم وجود قيم حرفية للعقد. سيكون هناك عدد محدود جدًا من الأماكن التي يمكن أن تظهر فيها ، لأنها ليست أنواعًا أو قيمًا.

stevenblenkinsop لن أذهب إلى حد التصريح بشكل واقعي أن العقد ليس من النوع (أو النوع الفوقي). أعتقد أن هناك حججًا معقولة جدًا لكلا وجهتي النظر. على سبيل المثال ، عقد المعلمة الواحدة الذي يحدد الطرق فقط يخدم أساسًا كـ "الحد الأعلى" لمعلمة النوع: أي وسيطة نوع صالحة يجب أن تنفذ هذه الأساليب. وهو ما نستخدمه عادة الواجهات. قد يكون من المنطقي السماح بالواجهات في تلك الحالات بدلاً من العقد ، أ) لأن هذه الحالات قد تكون شائعة ؛ و ب) لأن الوفاء بالعقد في هذه الحالة يعني ببساطة تلبية الواجهة الموضحة كعقد. أي أن مثل هذا العقد يشبه إلى حد كبير نوعًا "يُقارن به" نوع آخر.

إن اعتبار griesemer للعقود كأنواع يمكن أن يؤدي إلى مشاكل مع مفارقة راسل (كما هو الحال في نوع جميع الأنواع التي ليست `` أعضاءًا '' في نفسها). أعتقد أنه من الأفضل اعتبارهم "قيودًا على الأنواع". إذا اعتبرنا نظام الكتابة شكلاً من أشكال "المنطق" ، فيمكننا وضع نموذج أولي لهذا في Prolog. تصبح متغيرات النوع متغيرات منطقية ، وتصبح الأنواع ذرات ، ويمكن حل العقود / القيود عن طريق برمجة المنطق القيد. كل شيء أنيق للغاية وغير متناقض. فيما يتعلق بالصياغة ، يمكننا اعتبار العقد دالة على الأنواع التي تُرجع قيمة منطقية.

keean أي واجهة تعمل بالفعل كـ "قيد على الأنواع" ، ومع ذلك فهي أنواع. ينظر الناس في نظرية النوع كثيرًا إلى قيود الأنواع على أنها أنواع ، بطريقة رسمية للغاية. كما ذكرت أعلاه ، هناك حجج معقولة يمكن تقديمها لأي من وجهتي النظر. لا توجد "مفارقات منطقية" هنا - في الواقع ، يقوم النموذج الأولي الحالي للعمل قيد التقدم بنماذج عقد كنوع داخليًا لأنه يبسط الأمور في الوقت الحالي.

واجهات griesemer في Go هي "أنواع فرعية" وليست قيودًا على الأنواع. ومع ذلك ، أجد الحاجة إلى كل من العقود والواجهات عيبًا في تصميم Go ، ومع ذلك فقد يكون الوقت قد فات لتغيير الواجهات إلى قيود النوع بدلاً من الأنواع الفرعية. لقد ذكرت أعلاه أن واجهات Go ليس بالضرورة أن تكون أنواعًا فرعية ، لكني لا أرى الكثير من الدعم لهذه الفكرة. سيسمح هذا للواجهات والعقود بأن تكون هي نفسها - إذا كان من الممكن الإعلان عن الواجهات للمشغلين أيضًا.

هناك مفارقات هنا ، لذا فإن طريقة Girard's Paradox هي "التشفير" الأكثر شيوعًا لـ Russel's Paradox في نظرية النوع. تقدم نظرية النوع مفهوم الأكوان لمنع هذه المفارقات ، ولا يُسمح لك إلا بالإشارة إلى الأنواع في الكون "U" من الكون "U + 1". داخليًا ، يتم تنفيذ هذه النظريات من النوع كمنطق أعلى رتبة (على سبيل المثال ، يستخدم Elf lambda-prolog). وهذا بدوره يقلل من حل القيود لمجموعة فرعية قابلة للفصل لمنطق الترتيب الأعلى.

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

فيما يتعلق بالنموذج الأولي الحالي الذي يعامل القيود كأنواع ، يأتي الخطر إذا كان بإمكانك استخدام "نوع القيد" هذا كنوع عادي ، ثم إنشاء "نوع قيد" آخر على هذا النوع. ستحتاج إلى إجراء فحوصات لمنع المرجع الذاتي (عادة ما يكون تافهًا) وحلقات المرجع المتبادل. يجب كتابة هذا النوع من النموذج الأولي في Prolog ، لأنه يسمح لك بالتركيز على قواعد التنفيذ. أعتقد أن مطوري Rust أدركوا ذلك أخيرًا منذ فترة (انظر الطباشير).

griesemer مثير للاهتمام ، إعادة صياغة العقود كأنواعها. من نموذجي العقلي ، كنت أفكر في القيود على أنها أنواع وصفية ، والعقود كنوع من هيكل مستوى النوع.

type A int
func (a A) Foo() int {
    return int(a)
}

type C contract(T, U) {
    T int
    U int, uint
    U Foo() int
}

var B (int, uint; Foo() int).type = A
var C1 C = C(A, B)

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

stevenblenkinsop لقد خسرتني ، فلماذا تمرر T إلى C contract عندما لا يتم استخدامه ، وما هي الخطوط var التي تحاول أن تفعلها؟

griesemer شكرا لردك. أحد مبادئ تصميم Go هو "توفير طريقة واحدة فقط للقيام بشيء ما". من الأفضل الاحتفاظ بنموذج إعلان عقد واحد فقط. النوع C (النوع X) العقد {...} أفضل.

Goodwine @ لقد قمت بإعادة تسمية الأنواع لتمييزها عن معايير العقد. ربما يساعد ذلك؟ الغرض من (int, uint; Foo() int).type هو أن يكون النوع الوصفي لأي نوع له نوع أساسي من int أو uint والذي يستخدم Foo() int . الغرض من var B هو إظهار استخدام نوع كقيمة ، وتعيينه إلى متغير يكون نوعه نوعًا وصفيًا (نظرًا لأن النوع الأساسي يشبه النوع الذي تكون قيمه أنواعًا). الغرض من var C1 هو إظهار متغير من نوعه هو عقد ، وإظهار مثال لشيء يمكن تخصيصه لمثل هذا المتغير. في الأساس ، محاولة الإجابة على السؤال "إذا كان العقد نوعًا ، كيف تبدو قيمه؟". الهدف هو إظهار أن هذه القيمة لا تبدو في حد ذاتها نوعًا.

لدي مشكلة في العقود مع أنواع متعددة.

يمكنك إضافته أو تركه لنوع عقد بارميتر ، كلاهما
type Graph (type Node, Edge) struct { ... }
و
type Graph (type Node, Edge G) struct { ... } على ما يرام.

ولكن ماذا لو أردت فقط إضافة عقد على أحد معاملي النوعين؟

contract G(Node, Edge) {
    Node Edges() []Edge
    Edge Nodes() (from Node, to Node)
}

ضد

contract G(Edge) {
    Edge Nodes() (from Node, to Node)
}

themez هذا في المسودة. يمكنك استخدام بناء الجملة (type T, U comparable(T)) لتقييد معلمة نوع واحد فقط ، على سبيل المثال.

stevenblenkinsop أرى ، شكرًا.

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

على الأقل ، هذا هو الانطباع الذي حصلت عليه ، خاصة من حقيقة أنها تسمى "عقود".

keean أعتقد أننا نفسر كلمة "قيد" بشكل مختلف ؛ أنا أستخدمه بشكل غير رسمي إلى حد ما. من خلال تعريف الواجهات ، بالنظر إلى الواجهة I ، والمتغير x من النوع I ، يمكن فقط تعيين القيم ذات الأنواع التي تنفذ I إلى x . وبالتالي يمكن النظر إلى I على أنه "قيد" على تلك الأنواع (بالطبع لا يزال هناك عدد غير محدود من الأنواع التي ترضي هذا "القيد"). وبالمثل ، يمكن استخدام I كقيد لمعامل نوع P لدالة عامة ؛ يُسمح فقط بوسائط النوع الفعلي مع مجموعات الأساليب التي تنفذ I . وبالتالي ، فإن I يحد أيضًا من مجموعة أنواع الوسائط الفعلية الممكنة.

في كلتا الحالتين ، يكون السبب في ذلك هو وصف العمليات (الطرق) المتاحة داخل الوظيفة. إذا تم استخدام I كنوع معلمة (قيمة) ، فإننا نعلم أن المعلمة توفر هذه الطرق. إذا تم استخدام I لنا كـ "قيد" (بدلاً من العقد) ، فإننا نعلم أن جميع قيم معلمة النوع المقيد توفر هذه الطرق. من الواضح أنه واضح ومباشر.

أود مثالًا ملموسًا عن سبب هذه الفكرة المحددة لاستخدام الواجهات للعقود ذات المعلمة الواحدة التي تعلن فقط عن "تعطل" الطرق دون بعض القيود كما أشرت في تعليقك .

كيف سيتم تقديم مقترح العقود؟ استخدام معلمة go modules go1.14 ؟ متغير بيئة GO114CONTRACTS ؟ على حد سواء؟ شيء آخر..؟

آسف إذا تمت معالجة هذا من قبل ، فلا تتردد في إعادة توجيهي إلى هناك.

أحد الأشياء التي أحبها بشكل خاص في مسودة تصميم الأدوية الحالية هو أنها تضع الماء الصافي بين contracts و interfaces . أشعر أن هذا مهم لأنه من السهل الخلط بين المفهومين على الرغم من وجود ثلاثة اختلافات أساسية بينهما:

  1. يصف Contracts متطلبات _set_ من الأنواع ، بينما يصف interfaces الطرق التي يجب أن يلبيها النوع الفردي.

  2. يمكن لـ Contracts التعامل مع العمليات المضمنة والتحويلات وما إلى ذلك من خلال سرد الأنواع التي تدعمها ؛ يمكن لـ interfaces التعامل فقط مع الأساليب التي لا تتوفر في الأنواع المضمنة نفسها.

  3. مهما كانت في المصطلحات النظرية ، فإن contracts ليست أنواعًا بالمعنى الذي نفكر فيه عادةً في Go ، أي لا يمكنك التصريح عن متغيرات من أنواع contract ومنحها بعض القيمة. من ناحية أخرى ، interfaces من الأنواع ، يمكنك تعريف المتغيرات من هذه الأنواع وتعيين القيم المناسبة لها.

على الرغم من أنني أستطيع أن أرى معنى contract ، والذي يتطلب معلمة نوع واحد لها طرق معينة ، ليتم تمثيلها بدلاً من ذلك بـ interface (إنه شيء حتى أنني دافعت عنه في الماضي الخاص بي مقترحات) ، أشعر الآن أنها ستكون خطوة مؤسفة لأنها ستؤدي مرة أخرى إلى تعكير صفو المياه بين contracts و interfaces .

لم يخطر ببالي ذلك حقًا قبل أن يتم التصريح عن contracts بطريقة معقولة بالطريقة التي اقترحها bigwhite باستخدام نمط "النوع" الحالي. ومع ذلك ، مرة أخرى ، لست حريصًا على الفكرة لأنني أشعر أنها ستؤدي إلى حل وسط (3) أعلاه. أيضًا ، إذا كان من الضروري (لأسباب التحليل) تكرار الكلمة الرئيسية type عند الإعلان عن بنية عامة مثل:

type List(type Element) struct {
    next *List(Element)
    val  Element
}

من المفترض أيضًا أنه سيكون من الضروري تكراره إذا تم الإعلان عن contracts بطريقة مماثلة وهي عبارة عن `` شذوذ '' قليلاً مقارنة بنهج تصميم المسودة.

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

آسف لأن أبدو شديد المقاومة للتغييرات المقترحة على مسودة الأدوية (التي تحتوي على بعض المشكلات) ولكن ، بصفتي مدافعًا متحمسًا عن الأدوية الجنيسة البسيطة لـ Go ، أشعر أن هذه النقاط تستحق الذكر.

أود أن أقترح عدم استدعاء المسندات على أنواع "العقود". هناك سببان:

  • مصطلح "العقود" مستخدم بالفعل في علوم الكمبيوتر بطريقة مختلفة. على سبيل المثال ، راجع: (https://scholar.google.com/scholar؟hl=ar&as_sdt=0٪2C33&q=contracts+languages&btnG=)
  • توجد بالفعل أسماء متعددة لهذه الفكرة في أدبيات علوم الكمبيوتر. أعرف ما لا يقل عن ثلاثة ~ أربعة: "مجموعات الطباعة" و "فئات النوع" و "المفاهيم" و "القيود". ستؤدي إضافة أخرى إلى إرباك الأمور بشكل أكبر.

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

بالنسبة لي ، فإن طبيعة وقت التشغيل للأنواع الفرعية هي الاختلاف الحاسم ، إذا كان X <: Y يمكننا اجتياز X حيث يتوقع Y ولكننا نعرف فقط النوع على أنه Y بدون عمليات وقت تشغيل غير آمنة. بهذا المعنى ، لا يقيد النوع Y ، Y دائمًا Y. يعتبر النوع الفرعي أيضًا "اتجاهيًا" وبالتالي يمكن أن يكون متغيرًا أو متباينًا اعتمادًا على ما إذا كان يتم تطبيقه على وسيطة إدخال أو إخراج.

باستخدام قيد النوع "pred (X)" ، نبدأ بـ X متعدد الأشكال تمامًا ، ثم نقوم بتقييد القيم المسموح بها. لذلك قل فقط X التي تنفذ "طباعة". هذا غير اتجاهي وبالتالي لا يحتوي على تباين مشترك أو تناقض. إنه ثابت في حقيقة الأمر من حيث أننا نعرف نوع X الأساسي في وقت الترجمة.

لذلك أعتقد أنه من الخطير التفكير في الواجهات على أنها قيود على الأنواع لأنها تتجاهل الاختلافات المهمة مثل التغاير والتناقض.

هل هذا يجيب على سؤالك ، أم أنني فاتني النقطة؟

تحرير: يجب أن أشير إلى أنني أشير إلى واجهات "Go" أعلاه تحديدًا. تنطبق النقاط المتعلقة بالتصنيف الفرعي على جميع اللغات التي تحتوي على أنواع فرعية ، ولكن Go غير معتاد في جعل الواجهات نوعًا ومن ثم وجود علاقة نوع فرعي. في لغات أخرى مثل Java ، لا تعد الواجهة بشكل صريح نوعًا (الفئة عبارة عن نوع) ، لذا فإن الواجهات _are_ قيد على الأنواع. لذلك في حين أنه من الصواب بشكل عام اعتبار الواجهات كقيود على الأنواع ، فمن الخطأ تحديدًا "Go".

Inuart من السابق لأوانه معرفة كيفية إضافة هذا إلى التنفيذ. لا يوجد اقتراح حتى الآن ، مجرد مشروع تصميم. بالتأكيد لن يكون في 1.14.

andrewcmyers تعجبني كلمة "عقد" لأنها تصف العلاقة بين كاتب الوظيفة العامة والمتصل بها.

تشير كلمات مثل "typeets" و "type class" إلى أننا نتحدث عن نوع meta-type ، وهو ما نحن عليه بالطبع ، لكن العقود تصف أيضًا العلاقة بين أنواع متعددة. أعلم أن فئات النوع في ، على سبيل المثال ، Haskell ، يمكن أن تحتوي على معلمات نوع متعددة ، ولكن يبدو لي أن الاسم غير مناسب للفكرة الموصوفة.

لم أفهم أبدًا سبب تسمية C ++ على هذا "المفهوم". ماذا يعني ذلك حتى؟

سيكون "القيد" أو "القيود" مناسبًا لي. في الوقت الحالي ، أعتقد أن العقد يحتوي على قيود متعددة. لكن يمكننا تغيير هذا التفكير.

لست قلقًا جدًا من حقيقة وجود لغة برمجة قائمة تسمى "عقد". أعتقد أن هذه الفكرة تشبه نسبيًا الفكرة التي نريد التعبير عنها ، من حيث إنها علاقة بين دالة ومستدعيها. أفهم أن الطريقة التي يتم التعبير بها عن تلك العلاقة مختلفة تمامًا ، لكني أشعر أن هناك تشابهًا أساسيًا.

لم أفهم أبدًا سبب تسمية C ++ على هذا "المفهوم". ماذا يعني ذلك حتى؟

المفهوم هو تجريد للتشكيلات التي تشترك في بعض القواسم المشتركة ، مثل التوقيعات.

يعتبر مصطلح المفهوم مناسبًا بشكل أفضل للواجهات حيث يتم استخدام الأخير أيضًا للإشارة إلى حد مشترك بين مكونين.

sighoya كنت سأذكر أيضًا أن "المفاهيم" هي مفاهيمية لأنها تتضمن "بديهيات" ضرورية لمنع إساءة استخدام المشغلين. على سبيل المثال ، يجب أن تكون الإضافة "+" ترابطية وتبادلية. لا يمكن تمثيل هذه البديهيات في C ++ ، ومن ثم فهي موجودة كأفكار مجردة ، ومن ثم "مفاهيم". لذا فإن المفهوم هو "العقد" النحوي بالإضافة إلى البديهيات الدلالية.

ianlancetaylor "القيد" هو ما أطلقناه في جنس (http://www.cs.cornell.edu/~yizhou/papers/genus-pldi2015.pdf) ، لذلك أنا متحيز لهذه المصطلحات. سيكون مصطلح "عقد" اختيارًا معقولًا تمامًا ، باستثناء أنه قيد الاستخدام النشط جدًا في مجتمع PL للإشارة إلى العلاقة بين الواجهات والتطبيقات ، والتي لها أيضًا نكهة تعاقدية.

keean بدون أن أكون خبيرًا ، لا أعتقد أن الانقسام الذي ترسمه يعكس الواقع جيدًا. على سبيل المثال ، ما إذا كان المترجم يولد إصدارات مُنشأة من الوظائف العامة هي مسألة تنفيذ بالكامل ، لذلك من المعقول تمامًا أن يكون لديك تمثيل لوقت التشغيل للقيود ، على سبيل المثال في شكل جدول مؤشرات دالة لكل عملية مطلوبة. تمامًا مثل جداول طريقة الواجهة ، في الواقع. وبالمثل ، لا تتناسب الواجهات في Go مع تعريف النوع الفرعي الخاص بك ، لأنه يمكنك إسقاطها بأمان مرة أخرى (عبر تأكيدات النوع ) ولأنك لا تملك أي مشاركة ولا تتعارض مع أي نوع من المنشئات في Go.

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

ماذا عن شيء مثل:

typeConstraint C (T) {
}

أو

typeContract C (T) {
}

إنه يختلف عن تعريفات الأنواع الأخرى للتأكيد على أن هذا ليس بناء وقت تشغيل.

لدي بعض الأسئلة حول تصميم العقد الجديد.

1.

عندما يقوم النوع العام بتضمين نوع عام آخر ب ،
أو وظيفة عامة A تستدعي وظيفة عامة أخرى B ،
هل نحتاج أيضًا إلى تحديد عقود B على A؟

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

  1. إلى جانب موانع النوع الحالي وطريقة ضبطه ، هل نحتاج إلى موانع أخرى؟
    مثل قابل للتحويل من نوع إلى آخر ، قابل للتخصيص من نوع إلى آخر ،
    قابلة للمقارنة بين نوعين ، هي قناة قابلة للإرسال ، وهي قناة قابلة للاستلام ،
    لديه مجموعة حقول محددة ، ...

3.

إذا كانت دالة عامة تستخدم خطًا مثل السطر التالي

v.Foo()

كيف يمكننا كتابة عقد يجعل Foo إما طريقة أو حقلاً من نوع الوظيفة؟

يجب أن يتم حل قيود النوع merovius في وقت الترجمة ، أو يمكن أن يكون نظام النوع غير سليم. هذا لأنه يمكن أن يكون لديك نوع يعتمد على نوع آخر غير معروف حتى وقت التشغيل. لديك بعد ذلك خياران ، يجب عليك تنفيذ نظام نوع تابع كامل (والذي يسمح بالتحقق من النوع في وقت التشغيل عندما تصبح الأنواع معروفة) أو عليك إضافة أنواع وجودية إلى نظام النوع. ترميز الوجود اختلاف الطور للأنواع المعروفة بشكل ثابت ، والأنواع المعروفة فقط في وقت التشغيل (الأنواع التي تعتمد على القراءة من الإدخال / الإخراج على سبيل المثال).

لا تُعرف الأنواع الفرعية كما هو مذكور أعلاه عادةً حتى وقت التشغيل ، على الرغم من أن العديد من اللغات لديها تحسينات في حالة معرفة النوع بشكل ثابت.

إذا افترضنا أن أحد التغييرات المذكورة أعلاه قد تم تقديم اللغة (الأنواع التابعة أو الأنواع الوجودية) فإننا لا نزال بحاجة إلى فصل مفاهيم التصنيف الفرعي وقيود النوع. بالنسبة إلى Go على وجه التحديد ، فإن محددات النوع ثابتة يمكننا تجاهل هذه الاختلافات ، ويمكننا اعتبار أن واجهات Go _ هي قيود على الأنواع (بشكل ثابت).

لذلك يمكننا اعتبار واجهة Go بمثابة عقد بمعلمة واحدة حيث تكون المعلمة هي مستقبل جميع الوظائف / الطرق. فلماذا يكون لدى Go واجهات وعقود؟ يبدو لي أن السبب في ذلك هو أن Go لا يريد السماح بالواجهات للمشغلين (مثل "+") ، ولأن Go لا يحتوي على أنواع تابعة ولا أنواع وجودية.

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

keean Cool ، لذلك نحن على الأقل متفقون مع AIUI على أن الواجهات في Go يمكن اعتبارها قيودًا :)

فيما يتعلق بالباقي: أعلاه قلت:

"القيود على الأنواع" هي شيء وقت تجميع محض ، لأن الأنواع تمحى قبل وقت التشغيل. تتسبب القيود في تطوير الكود العام في كود غير عام يمكن تنفيذه.

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

FWIW ، لقد جادلت من قبل أنني أفضل استخدام الواجهات للتعبير عن القيود - وتوصلت أيضًا إلى استنتاج مفاده أن السماح باستخدام المشغلين في رمز عام هو حجر الطريق الرئيسي للقيام بذلك ، وبالتالي السبب الرئيسي لتقديم منفصل مفهوم في شكل عقود.

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

لاحظ أنني لم أذكر الأنواع الفرعية أو التباين المشترك أو التباين المعاكس (الذي لا نسمح به في Go على أي حال ، يجب أن تتطابق التوقيعات دائمًا) ، وما إلى ذلك. نوع المعلمة ، وما إلى ذلك) لشرح ما أعنيه بكلمة "القيد" لأن هذه هي اللغة المشتركة التي يفهمها الجميع هنا وبالتالي يمكن للجميع اتباعها. (أيضًا ، على عكس ادعائك هنا ، في Java ، تبدو الواجهة كنوع بالنسبة لي وفقًا لمواصفات Java : "يحدد إعلان الواجهة نوعًا مرجعيًا جديدًا مسمىًا". إذا لم يذكر هذا أن الواجهة هي نوع إذن لدى الأشخاص في مواصفات Java بعض الأعمال التي يتعين عليهم القيام بها.)

ولكن يبدو أنك أجبت على سؤالي بشكل غير مباشر بتعليقك الأخير ، كما لاحظ Merovius بالفعل ، عندما تقول: "لذلك يمكننا اعتبار واجهة Go بمثابة عقد بمعامل واحد حيث تكون المعلمة هي المتلقي لجميع الوظائف / الطرق . ". هذه بالضبط هي النقطة التي كنت أشرحها في البداية ، لذا أشكرك على تأكيد ما قلته طوال الوقت.

تضمين التغريدة

عندما يدمج النوع العام A نوعًا عامًا آخر B ، أو تستدعي دالة عامة A دالة عامة أخرى B ، فهل نحتاج أيضًا إلى تحديد عقود B على A؟

إذا كان النوع العام A يتضمن نوعًا عامًا آخر B ، فيجب أن تفي معلمات النوع التي تم تمريرها إلى B بأي عقد يستخدمه B. للقيام بذلك ، يجب أن يتضمن العقد الذي يستخدمه A العقد المستخدم من قبل B. أي ، جميع القيود على معلمات النوع التي تم تمريرها إلى B ، يجب التعبير عنها في العقد المستخدم بواسطة A. وهذا ينطبق أيضًا عندما تستدعي دالة عامة وظيفة عامة أخرى.

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

أعتقد أن ما تقوله صحيح ، لكنه ليس مشكلة التسمم المستمر. تكمن مشكلة التسمم الثابت في أنه يجب عليك نشر const في كل مكان يتم فيه تمرير وسيطة ، ثم إذا اكتشفت مكانًا يجب تغيير الوسيطة فيه ، فعليك إزالة const في كل مكان. الحالة مع الأدوية الجنيسة تشبه إلى حد كبير "إذا قمت باستدعاء عدة وظائف ، يجب عليك تمرير قيم من النوع الصحيح إلى كل من هذه الوظائف."

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

إلى جانب موانع النوع الحالي وطريقة ضبطه ، هل نحتاج إلى موانع أخرى؟ مثل قابلة للتحويل من نوع إلى آخر ، قابلة للتخصيص من نوع إلى آخر ، قابلة للمقارنة بين نوعين ، هي قناة قابلة للإرسال ، هي قناة قابلة للاستلام ، لها مجموعة حقول محددة ، ...

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

إذا كانت دالة عامة تستخدم خطًا مثل السطر التالي

ضد فو ()
كيف نكتب عقدًا يسمح لـ Foo أن يكون طريقة أو حقلاً لنوع دالة؟

في مسودة التصميم الحالية ، لا يمكنك ذلك. هل تبدو هذه حالة استخدام مهمة؟ (أعلم أن مسودة التصميم السابقة دعمت ذلك).

griesemer ، لقد فاتتك النقطة التي قلت أنها كانت صالحة فقط إذا أدخلت الأنواع التابعة أو الأنواع الوجودية في نظام الكتابة.

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

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

بالنسبة لي ، سيكون تصميمًا أفضل لدمج الواجهات والعقود ، وجعلها تكتب قيودًا صريحة (المسندات على الأنواع).

تضمين التغريدة

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

لماذا يكون هذا غير عادي؟ إذا قمت بتحديد وظيفة من النوع "T" ، فسأريد استدعاء الوظائف على "T". على سبيل المثال ، إذا قمت بتعريف دالة "مجموع" على "أنواع قابلة للإضافة" عن طريق العقد. الآن أريد إنشاء دالة مضاعفة عامة تستدعي المجموع؟ العديد من الأشياء في البرمجة لها هيكل مجموع / منتج (أي شيء يمثل "مجموعة").

لا أحصل على ما سيكون الغرض من الواجهة بعد أن تكون العقود على اللغة ، يبدو أن العقود ستعمل للغرض نفسه ، للتأكد من أن النوع يحتوي على مجموعة من الأساليب المحددة فيه.

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

mrkaspa إن أبسط طريقة للتفكير فيها هي أن العقود تشبه وظائف قالب C ++ والواجهات مثل الأساليب الافتراضية لـ C ++. هناك استخدام والغرض لكليهما.

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

ianlancetaylor لا أعرف الكثير عن ++ C ، فما هي حالات استخدام الواجهات والعقود في golang؟ متى يجب علي استخدام الواجهة أو العقد؟

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

تشرح مسودة التصميم بأكبر قدر ممكن من الوضوح سبب اعتقادي أن استنتاج القيود هو اختيار ضعيف للغة مثل Go المصممة للبرمجة على نطاق واسع.

mrkaspa على سبيل المثال ، إذا كان لديك []io.Reader فأنت تريد قيمة واجهة ، وليس عقدًا. يتطلب العقد أن تكون جميع العناصر الموجودة في الشريحة من نفس النوع. ستسمح لهم الواجهة بأن يكونوا من أنواع مختلفة ، طالما أن جميع الأنواع تستخدم io.Reader .

ianlancetaylor بقدر ما حصلت على واجهة تخلق نوعًا جديدًا وفي الوقت نفسه تقيد العقود نوعًا ولكن لا تنشئ نوعًا جديدًا ، هل أنا على حق؟

ianlancetaylor :

ألا تستطيع أن تفعل شيئًا كالتالي؟

contract Reader(T) {
  T Read([]byte) (int, error)
}

func ReadAll(type T Reader)(readers []T) ([]byte, error) {
  // Use the readers...
}

الآن يجب أن يقبل $ # ReadAll() []io.Reader تمامًا كما يقبل []*os.File ، أليس كذلك؟ يبدو أن io.Reader يفي بالعقد ، ولا أتذكر أي شيء في المسودة حول عدم إمكانية استخدام قيم الواجهة كوسائط للنوع.

تحرير: لا تهتم. أسأت الفهم. لا يزال هذا مكانًا تستخدم فيه الواجهة ، لذا فهي إجابة لسؤالmrkaspa . أنت فقط لا تستخدم الواجهة في توقيع الوظيفة ؛ أنت تستخدمه فقط حيث يتم استدعاؤه.

mrkaspa نعم ، هذا صحيح.

ianlancetaylor إذا كانت لدي قائمة بـ [] io.Reader وهذا العقد:

contract Reader(T) {
  T Read([]byte) (int, error)
}

func ReadAll(type T Reader)(readers []T) ([]byte, error) {
  // Use the readers...
}

يمكنني الاتصال بـ ReadAll على كل واجهة لأنها تفي بالعقد؟

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

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

في الواقع ، ستظهر هذه المشكلات فقط في قاعدة بيانات كبيرة ، يتم الاحتفاظ بها بمرور الوقت. إنه ليس شيئًا يمكنك كتابة مثال صغير سريع لتوضيحه.

توجد هذه المشكلات في اللغات العامة الأخرى أيضًا ، على سبيل المثال Ada. يمكنك نقل بعض تطبيقات Ada الكبيرة التي تستخدم Go على نطاق واسع للأدوية الجنيسة ، ولكن إذا كانت المشكلة موجودة في Ada ، فلا أرى أي شيء في Go من شأنه أن يخفف من هذه المشكلة.

mrkaspa نعم.

في هذه المرحلة ، أقترح أن ينتقل موضوع المحادثة هذا إلى golang-nuts. يعد متعقب مشكلة GitHub مكانًا سيئًا لهذا النوع من المناقشة.

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

keean أنا لا أفهم مثال التسجيل الخاص بك. المشكلة التي تصفها هي شيء يمكنك حله باستخدام الواجهات في وقت التشغيل ، وليس باستخدام الأدوية الجنسية في وقت الترجمة.

تحتوي واجهات bserdar على معلمة نوع واحدة فقط ، لذلك لا يمكنك القيام بشيء يكون فيه أحد المعلمات هو الشيء المطلوب تسجيله ، ومعامل النوع الثاني هو نوع السجل.

keean IMO في هذا المثال ، ستفعل نفس الشيء الذي تفعله اليوم ، بدون أي معلمات نوع على الإطلاق: استخدم الانعكاس لفحص الشيء المراد تسجيله واستخدم context.Context لتمرير قيمة السجل. أعلم أن هذه الأفكار مثيرة للاشمئزاز لعشاق الكتابة ، لكنها اتضح أنها عملية جدًا. بالطبع هناك قيمة في معلمات النوع المقيدة ، وهذا هو سبب إجراء هذه المحادثة - لكنني أفترض أن سبب الحالات التي تتبادر إلى ذهنك هي الحالات التي تعمل بالفعل بشكل جيد في قواعد أكواد Go الحالية على نطاق واسع ، هي أن هذه ليست الحالات التي تستفيد حقًا من فحص صارم إضافي للنوع. وهو ما يعود إلى نقطة إيان - يبقى أن نرى ما إذا كانت هذه مشكلة تظهر في الممارسة.

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

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

جانباً: بالطبع يعتمد الأمر على كيفية تحقيقك للنطاق ، في الوقت الحالي أنا أفضل منهج API-First ، بدءًا من ملف OpenAPI / Swagger JSON ثم استخدام إنشاء التعليمات البرمجية لإنشاء كعب خادوم وعميل SDK. على هذا النحو ، يعمل OpenAPI بالفعل كنظام النوع الخاص بك للخدمات الصغيرة.

تضمين التغريدة

يتم التعبير عن القيود مثل قابلية التحويل وإمكانية التخصيص وقابلية المقارنة في شكل أنواع

بالنظر إلى وجود الكثير من التفاصيل في قواعد التحويل من النوع Go ، فمن الصعب حقًا كتابة عقد مخصص C لتلبية وظيفة تحويل الشرائح العامة التالية:

func ConvertSlice(type In, Out C(In, Out)) (x []In) []Out {
    o := make([]Out, len(x))
    for i := range x {
        o[i] = Out(x[i])
    }
    return o
}

يجب أن يسمح C المثالي بالتحويلات:

  • بين أي عدد صحيح ، أنواع عدد الفاصلة العائمة
  • بين أي أنواع رقمية معقدة
  • بين نوعين الأنواع الأساسية متطابقة
  • من نوع Out الذي ينفذ In
  • من نوع قناة إلى نوع قناة ثنائية الاتجاه ويكون لنوعي القناة نوع عنصر متطابق
  • هيكل العلامة ذات الصلة ، ...
  • ...

حسب فهمي ، لا يمكنني كتابة مثل هذا العقد. فهل نحتاج إلى عقد مدمج convertible ؟

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

بالنظر إلى أن تضمين النوع يتم استخدامه غالبًا في برمجة Go ، أعتقد أن الاحتياجات لن تكون نادرة.

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

merovius أنا على استعداد لقبول أن التغييرات على Go يجب أن تكون تدريجية ، وأقبل الوضع الراهن.

كنت فقط أرد على تعليقك كجزء من محادثة ، وأوافق على أنني من عشاق الكتابة. لقد ذكرت رأيًا حول انعكاس وقت التشغيل ، ولم أقترح أن يتخلى Go عن انعكاس وقت التشغيل. أنا أعمل على لغات أخرى ، وأستخدم العديد من اللغات في عملي. أنا أقوم (ببطء) بتطوير لغتي الخاصة ، لكني دائمًا ما آمل أن التطورات في اللغات الأخرى ستجعل هذا غير ضروري.

dotaheor أوافق على أنه لا يمكننا كتابة عقد عام للتحويل اليوم. سيتعين علينا أن نرى ما إذا كان هذا يبدو مشكلة في الممارسة.

الرد علىianlancetaylor

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

في حالة المصفوفات ، يبدو أن مجرد تمرير نوع المصفوفة (بالكامل) كوسيطة نوع أمر محدود للغاية ، لأن العقد لن يكون قادرًا على تفكيك أبعاد المصفوفة أو نوع العنصر وفرض قيود عليها. على سبيل المثال ، هل يمكن أن يتطلب عقد يأخذ "نوع مصفوفة كاملة" نوع عنصر نوع المصفوفة لتنفيذ طرق معينة؟

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

هل تكفي معلمات النوع العامة فقط لـ Go 2 Generics؟

القسم الجديد بعنوان "أمثلة على الطرق التي تكون فيها العوامل العامة غير الأنواع مفيدة".

كخلاصة سريعة ، يمكن للعقود الخاصة بعمليات المصفوفة والمتجه أن تفرض قيودًا مناسبة على كل من الأبعاد وأنواع عناصر المصفوفات. على سبيل المثال ، يمكن أن يؤدي ضرب المصفوفة لمصفوفة nxm بمصفوفة mxp ، يتم تمثيل كل منها كمصفوفة ثنائية الأبعاد ، إلى تقييد عدد صفوف المصفوفة الأولى بشكل صحيح بحيث يساوي عدد أعمدة المصفوفة الثانية ، إلخ.

بشكل عام ، يمكن للأدوية الجنيسة استخدام معلمات غير من النوع لتمكين تكوين وقت الترجمة وتخصص الكود والخوارزميات بطرق عديدة. على سبيل المثال ، متغير عام للرياضيات / big.Int يمكن تكوينه في وقت الترجمة إلى جزء معين مع و / أو توقيع ، مما يلبي متطلبات الأعداد الصحيحة 128 بت والأعداد الصحيحة غير الأصلية ذات العرض الثابت ذات الكفاءة المعقولة والتي من المحتمل أن تكون أفضل بكثير من الكبير الموجود حيث يكون كل شيء ديناميكيًا. قد يكون المتغير العام لـ big.Float متخصصًا بالمثل في وقت التجميع لدقة معينة و / أو معلمات وقت التجميع الأخرى ، على سبيل المثال ، لتوفير تطبيقات عامة فعالة بشكل معقول لتنسيقات binary16 و binary128 و binary256 من IEEE 754-2008 أن Go لا يدعم أصلاً. العديد من خوارزميات المكتبات التي يمكنها تحسين عملياتها بناءً على معرفة احتياجات المستخدم أو جوانب معينة من البيانات التي تتم معالجتها - على سبيل المثال ، تحسينات خوارزمية الرسم البياني التي تعمل فقط على أوزان الحواف غير السلبية أو فقط على DAGs أو الأشجار ، أو تحسينات معالجة المصفوفة التي الاعتماد على المصفوفات التي تكون على شكل مثلث علوي أو سفلي ، أو حساب عدد صحيح كبير للتشفير يحتاج أحيانًا إلى التنفيذ في وقت ثابت وأحيانًا لا - يمكن استخدام العوامل العامة لجعل نفسها قابلة للتكوين في وقت الترجمة للاعتماد على معلومات تعريفية اختيارية مثل هذا ، مع التأكد من أن جميع اختبارات خيارات وقت الترجمة هذه في التنفيذ يتم تجميعها عادةً عبر الانتشار المستمر.

كتب bford :

أي أن معلمات الأدوية الجنيسة مرتبطة بالثوابت في وقت الترجمة.

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

ولكن بالنسبة لهذا المطلب ، ستكون كلمة رئيسية مثل comp أو comptime مناسبة بشكل أفضل.
علاوة على ذلك ، إذا كانت قواعد golang تسمح فقط بمعاملتي tuple على الأكثر لوظيفة ما ، فيمكن ترك التعليق التوضيحي للكلمة الرئيسية لأن المعلمة الأولى tuple من نوع ووظيفة (في حالة وجود مجموعتين من معلمات tuples) سيتم تقييمها دائمًا في وقت الترجمة.

نقطة أخرى: ماذا لو تم تمديد const للسماح بتعبيرات وقت التشغيل (تسجيل دخول فردي حقيقي)؟

على المؤشر مقابل طرق القيمة :

إذا تم إدراج طريقة في عقد مع T عادي بدلاً من *T ، فقد تكون إما طريقة مؤشر أو طريقة قيمة T . من أجل تجنب القلق بشأن هذا التمييز ، في جسم الوظيفة العامة ، ستكون جميع استدعاءات الطريقة عبارة عن استدعاءات أسلوب المؤشر. ...

كيف هذا المربع مع تنفيذ الواجهة؟ إذا كان لدى T طريقة المؤشر (مثل MyInt في المثال) ، فهل يمكن تعيين T للواجهة بهذه الطريقة ( Stringer في مثال)؟

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

(ملاحظة: يجب أن نعيد النظر في هذا القرار إذا أدى إلى ارتباك أو رمز غير صحيح.)

أرى أن الفريق لديه بالفعل بعض التحفظات حول هذا الغموض في صيغة طريقة المؤشر. أنا فقط أضيف أن الغموض يؤثر أيضًا على تنفيذ الواجهة (وأضيف ضمنيًا تحفظاتي عليها أيضًا).

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

في الأنواع ذات المعاملات ، خاصة فيما يتعلق بمعلمات النوع المضمنة كحقل في بنية:

يعتبر

type Lockable(type T) struct {
    T
    sync.Locker
}

ماذا لو كان لدى T طرق باسم Lock أو Unlock ؟ لن يتم تجميع الهيكل. هذا الشرط الذي لا يحتوي على طريقة X غير مدعوم بالعقود ، وبالتالي لدينا رمز غير صالح لا يكسر العقد (هزيمة الغرض الكامل للعقود).

يصبح الأمر أكثر تعقيدًا إذا كان لديك العديد من المعلمات المضمنة (على سبيل المثال T1 و T2 ) نظرًا لأنه لا يجب أن يكون لهذه المعلمات طرق شائعة (مرة أخرى ، لا يتم فرضها بواسطة العقود). بالإضافة إلى ذلك ، يساهم دعم الأساليب التعسفية اعتمادًا على الأنواع المضمنة في قيود وقت التجميع المحدودة للغاية على مفاتيح التبديل لتلك الهياكل (بشكل مشابه جدًا لتأكيدات النوع والمفاتيح ).

كما أراه ، هناك بديلان جيدان:

  • عدم السماح بتضمين معلمات النوع تمامًا: بسيط ، ولكن بتكلفة قليلة (إذا كانت الطريقة مطلوبة ، يتعين على المرء كتابتها بوضوح في البنية مع الحقل).
  • قصر الطرق القابلة للاستدعاء على العقود: على نحو مشابه لتضمين الواجهة. هذا ينحرف عن الوضع العادي (ليس هدفًا) ولكن بدون تكلفة (لا يلزم كتابة الأساليب بشكل صريح في الهيكل مع الحقل).

لن يتم تجميع الهيكل.

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

لن يعمل الحل الثاني الخاص بك ، وهو تقييد الطرق القابلة للاستدعاء لتلك المذكورة في العقد: حتى إذا كان العقد على T حدد Lock و Unlock ، فلا يزال بإمكانك ' ر اتصل بهم على Lockable .

jba شكرا على الأفكار حول التجميع.

أعني بالحل الثاني معالجة معلمات النوع المضمنة كما نفعل مع الواجهات الآن ، بحيث إذا لم تكن الطريقة موجودة في العقد ، فلا يمكن الوصول إليها فورًا بعد التضمين. في هذا السيناريو ، نظرًا لعدم وجود عقد لـ T ، فإنه يتم التعامل معه على أنه interface{} ، وبالتالي لن يتعارض مع sync.Locker حتى لو تم إنشاء مثيل لـ T مع نوع بهذه الأساليب. هذا قد يساعد في شرح وجهة نظري .

في كلتا الحالتين ، أفضل حل القبضة (حظر التضمين تمامًا) ، لذلك إذا كان هذا هو ما تفضله ، فليس هناك غرض كبير من مناقشة الحل الثاني! : مبتسم:

يغطي المثال الذي قدمه JavierZunzunegui أيضًا حالة أخرى. ماذا لو كان T عبارة عن هيكل به حقل noCopy noCopy ؟ يجب أن يكون المترجم قادرًا على التعامل مع هذه الحالة أيضًا.

لست متأكدًا مما إذا كان هذا هو المكان المناسب تمامًا لذلك ، لكنني أردت التعليق باستخدام حالة استخدام واقعية ملموسة للأنواع العامة التي تسمح بـ "تحديد المعاملات على قيم غير من النوع مثل الثوابت" ، وعلى وجه التحديد لحالة المصفوفات . آمل أن يكون هذا مفيدا.

في العالم الذي لا أعيش فيه ، أكتب الكثير من التعليمات البرمجية التي تبدو كالتالي:

import "math/bits"

// SigEl is the element type used in variable length bit vectors, 
// can be any unsigned integer type
type SigEl = uint

// SigElBits is the number of bits storable in each SigEl
const SigElBits = 8 << uint((^SigEl(0)>>32&1)+(^SigEl(0)>>16&1)+(^SigEl(0)>>8&1))

// HammingDist counts the number bitwise differences between two
// bit vectors b1 and b2. I want this to be generic
// Function will panic at runtime if b1 and b2 aren't of equal length.
func HammingDist(b1, b2 []SigEl) (sum int) {
    // Give the compiler a hint so it won't need to bounds check the slices in loops
    _ = b1[len(b2)-1]  
        // This switch is optimized away because SigElBits is const
    switch SigElBits {   // Yay no golang generics!
    case 64:
        _ = b2[len(b1)-1]
        for x := range b1 {
            sum += bits.OnesCount64(uint64(b1[x] ^ b2[x]))
        }
    case 32:
        _ = b2[len(b1)-1]
        for x := range b1 {
            sum += bits.OnesCount32(uint32(b1[x] ^ b2[x]))
        }
    case 16:
        _ = b2[len(b1)-1]
        for x := range b1 {
            sum += bits.OnesCount16(uint16(b1[x] ^ b2[x]))
        }
    case 8:
        _ = b2[len(b1)-1]
        for x := range b1 {
            sum += bits.OnesCount8(uint8(b1[x] ^ b2[x]))
        }
    }
    return sum
}

هذا يعمل بشكل جيد ، مع تجعد واحد. غالبًا ما أحتاج إلى مئات الملايين من []SigEl s ، وطولها غالبًا ما بين 128 و 384 بتًا. نظرًا لأن الشرائح تفرض حملًا ثابتًا يبلغ 192 بت فوق حجم المصفوفة الأساسية ، عندما تكون المصفوفة نفسها 384 بت أو أقل ، فإن هذا يفرض عبئًا غير ضروري للذاكرة بنسبة 50-150٪ ، وهو أمر فظيع بشكل واضح.

الحل الذي أقدمه هو تخصيص شريحة من Sig _arrays_ ، ثم تقطيعها سريعًا كمعلمات إلى HammingDist أعلاه:

const SigBits = 256  // Any multiple of SigElBits is valid

// Sig is the bit vector array type
type Sig [SigBits/SigElBits]SigEl

bitVects := make([]Sig, 100000000)
// stuff happens ... 

// Note slicing below, just to make the arrays "generic" for the call 
dist := HammingDist(bitVects[x][:], bitVects[y][:])

ما أود _ أن أكون قادرًا على القيام به بدلاً من كل ذلك هو تحديد نوع التوقيع العام وإعادة كتابة كل ما سبق على أنه (شيء مثل):

contract UnsignedInteger(T) {
    T uint, uint8, uint16, uint32, uint64
}

type Signature (type Element UnsignedInteger, n int) [n]Element

// HammingDist counts the number bitwise differences between two bit vectors
func HammingDist(b1, b2 *Signature) (sum int) {
    for x := range *b1 {
        // Assuming the std lib bits.OnesCount becomes generic over 
        // all UnsignedInteger types
        sum += bits.OnesCount(*b1[x] ^ *b2[x])
    }
    return sum
}

إذن لاستخدام هذه المكتبة:

type sigEl = uint   // Any unsigned int type
const sigElBits = 8 << uint((^SigEl(0)>>32&1)+(^SigEl(0)>>16&1)+(^SigEl(0)>>8&1))
const sigBits = 256  // Any multiple of SigElBits is valid
type sig Signature(sigEl, sigBits/sigElBits)

bitVects := make([]sig, 100000000)
// stuff happens ... 

dist := HammingDist(&bitVects[x], &bitVects[y])

يمكن للمهندس أن يحلم ... 🤖

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

contract uintArrayOfFixedLength(ElemType,ArrayType)
{
    ArrayType [1]ElemType,[2]ElemType,...,[maxBit]ElemType
    ElemType uint8,uint16,uint32,uint64
}

func HammingDist(type ElemType,ArrayType uintArrayOfFixedLength)(t1,t2 ArrayType) (sum int)
{

}

vsivsi لست متأكدًا من أنني أفهم كيف تعتقد أن ذلك سيحسن الأشياء - هل ربما تفترض أن المترجم سينشئ نسخة محسّنة من هذه الوظيفة لكل طول مصفوفة ممكن؟ لأن ISTM أنه أ) ليس من المحتمل جدًا ، لذلك ب) سينتهي بك الأمر بنفس خصائص الأداء تمامًا كما تفعل الآن. التطبيق الأكثر احتمالاً ، IMO ، هو أن يقوم المترجم بتمرير الطول والمؤشر إلى العنصر الأول ، لذلك لا يزال بإمكانك تمرير شريحة في الكود المُنشأ (أعني ، لن تمرر السعة ، لكنني لا أعتقد أن كلمة إضافية في المكدس مهمة حقًا).

بصراحة ، ما تقوله IMO هو مثال جيد جدًا على الإفراط في استخدام الأدوية الجنيسة ، حيث لا تكون هناك حاجة إليها - "مجموعة من الطول غير المحدد" هي بالضبط ما هي الشرائح.

Merovius شكرًا ، أعتقد أن تعليقك يكشف عن بضع نقاط مناقشة مثيرة للاهتمام.

"مصفوفة ذات طول غير محدد" هي بالضبط ما هي الشرائح.

صحيح ، لكن في المثال الذي قدمته لا توجد مصفوفات بطول غير محدد. طول الصفيف ثابت معروف في وقت التجميع. هذا هو بالضبط الغرض من المصفوفات ، ولكن لا يتم استخدامها بشكل كافٍ في golang IMO لأنها غير مرنة للغاية.

لأكون واضحا ، أنا لا أقترح

type Signature (type Element UnsignedInteger, n int) [n]Element

يعني أن n هو متغير وقت التشغيل. يجب أن يظل ثابتًا بنفس معنى اليوم:

const n = 10
type nArray [n]uint               // works
type nSigInt Signature(uint, n)   // works 

var m = int(n)
type mArray [m]uint               // error
type mSigInt Signature(uint, m)   // error 

لذلك دعونا نلقي نظرة على "التكلفة" للشريحة على أساس وظيفة HammingDist . أوافق على أن الفرق بين تمرير مصفوفة مثل bitVects[x][:] مقابل &bitVects[x] صغير (-ish ، عامل 3 كحد أقصى). يأتي الاختلاف الحقيقي في التحقق من الكود ووقت التشغيل الذي يجب أن يحدث داخل هذه الوظيفة.

في الإصدار المستند إلى الشرائح ، يحتاج كود وقت التشغيل إلى التحقق من حدود الوصول إلى الشريحة لضمان سلامة الذاكرة. هذا يعني أن هذا الإصدار من الكود يمكن أن يصاب بالذعر (أو هناك حاجة إلى آلية واضحة للتحقق من الخطأ وإرجاعه لمنع ذلك). تُحدث تعيينات NOP ( _ = b1[len(b2)-1] ) فرقًا ذا مغزى في الأداء من خلال إعطاء مُحسِّن المترجم تلميحًا بأنه لا يحتاج إلى تحديد كل وصول للشريحة في الحلقة. لكن عمليات التحقق من الحد الأدنى هذه لا تزال ضرورية ، على الرغم من أن المصفوفات الأساسية التي تم تمريرها تكون دائمًا بنفس الطول. علاوة على ذلك ، قد يواجه المترجم صعوبة في تحسين حلقة for / range بشكل مربح (على سبيل المثال من خلال إلغاء التمرير ).

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

علاوة على ذلك ، بالنسبة لأبعاد المصفوفة الصغيرة (مهمة في حالتي) ، سيكون من السهل على المترجم أن يفتح بشكل مربح أو حتى يحسن تمامًا حلقة for / range للحصول على أداء لائق ، لأنه سيعرف في وقت التجميع ما هي تلك الأبعاد .

الميزة الكبيرة الأخرى للإصدار العام من الكود هو أنه يسمح لمستخدم الوحدة النمطية HammingDist بتحديد نوع int غير الموقع في التعليمات البرمجية الخاصة بهم. يتطلب الإصدار غير العام تعديل الوحدة نفسها لتغيير النوع المحدد SigEl ، حيث لا توجد طريقة "لتمرير" نوع إلى وحدة نمطية. نتيجة هذا الاختلاف هو أن تنفيذ وظيفة المسافة يصبح أكثر بساطة عندما لا تكون هناك حاجة لكتابة رمز منفصل لكل من حالات uint {8،16،32،64} بت.

تعتبر تكاليف إصدار الوظيفة المستند إلى الشرائح والحاجة إلى تعديل رمز المكتبة لتعيين نوع العنصر تنازلات دون المستوى المطلوب لتجنب الاضطرار إلى تنفيذ إصدارات "NxM" من هذه الوظيفة والحفاظ عليها. من شأن الدعم العام لأنواع المصفوفات ذات المعلمات (الثابت) أن يحل هذه المشكلة:

// With generics + parameterized constant array lengths:
type Signature (type Element UnsignedInteger, n int) [n]Element
func HammingDist(b1, b2 *Signature) (sum int) { ... }

// Without generics
func HammingDistL1Uint(b1, b2 [1]uint) (sum int) { ... }
func HammingDistL1Uint8(b1, b2 [1]uint8) (sum int) { ... }
func HammingDistL1Uint16(b1, b2 [1]uint16) (sum int) { ... }
func HammingDistL1Uint32(b1, b2 [1]uint32) (sum int) { ... }
func HammingDistL1Uint64(b1, b2 [1]uint64) (sum int) { ... }

func HammingDistL2Uint(b1, b2 [2]uint) (sum int) { ... }
func HammingDistL2Uint8(b1, b2 [2]uint8) (sum int) { ... }
func HammingDistL2Uint16(b1, b2 [2]uint16) (sum int) { ... }
func HammingDistL2Uint32(b1, b2 [2]uint32) (sum int) { ... }
func HammingDistL2Uint64(b1, b2 [2]uint64) (sum int) { ... }

func HammingDistL3Uint(b1, b2 [3]uint) (sum int) { ... }
func HammingDistL3Uint8(b1, b2 [3]uint8) (sum int) { ... }
func HammingDistL3Uint16(b1, b2 [3]uint16) (sum int) { ... }
func HammingDistL3Uint32(b1, b2 [3]uint32) (sum int) { ... }
func HammingDistL3Uint64(b1, b2 [3]uint64) (sum int) { ... }

// and L4, L5, L6 ... ad nauseum

إن تجنب الكابوس المذكور أعلاه ، أو التكاليف الحقيقية للبدائل الحالية يبدو وكأنه "معاكس" لـ "الإفراط العام" بالنسبة لي. أتفق مع sighoya على أن تعداد جميع أطوال المصفوفات المسموح بها في العقد يمكن أن يصلح لمجموعة محدودة جدًا من الحالات ، لكنني أعتقد أن هذا محدود جدًا حتى بالنسبة لحالتي ، حتى لو وضعت الحد الأعلى للدعم عند إجمالي 384 بت ، سيتطلب ذلك ما يقرب من 50 بندًا في بند العقد ArrayType [1]ElemType,[2]ElemType,...,[maxBit]ElemType لتغطية حالة uint8 .

صحيح ، لكن في المثال الذي قدمته لا توجد مصفوفات بطول غير محدد. طول الصفيف ثابت معروف في وقت الترجمة.

أفهم ذلك ، لكن لاحظ أنني لم أقل "في وقت التشغيل" أيضًا. تريد كتابة رمز غافل عن طول المصفوفة. يمكن للشرائح فعل ذلك بالفعل.

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

قد يكون هناك إصدار عام للدالة - لأن كل إنشاء مثيل من هذا النوع يستخدم ثابتًا مختلفًا. لهذا السبب لدي انطباع بأنك تفترض أن الكود الذي تم إنشاؤه لن يكون عامًا ، بل يتم توسيعه لكل نوع. على سبيل المثال ، يبدو أنك تفترض أنه سيكون هناك العديد من عمليات إنشاء هذه الوظيفة ، لـ [1]Element ، [2]Element ، إلخ. أنه سيكون هناك إصدار واحد يتم إنشاؤه ، وهو ما يعادل أساسًا إصدار الشرائح.

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

الميزة الكبيرة الأخرى للنسخة العامة من الكود

للتوضيح: من خلال "الإصدار العام" ، هل تشير إلى الفكرة العامة للأدوية ، كما تم تطبيقها على سبيل المثال في مسودة تصميم العقود الحالية ، أم أنك تشير بشكل أكثر تحديدًا إلى الأدوية التي لا تحتوي على معلمات من النوع؟ لأن المزايا التي تسميها في هذه الفقرة تنطبق أيضًا على مسودة تصميم العقود الحالية.

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

// With generics + parameterized constant array lengths:
// Without generics

هذه ثنائية زائفة (وواضحة جدًا لدرجة أنني محبط منك قليلاً). هناك أيضًا "مع معلمات النوع ، ولكن بدون معلمات عدد صحيح":

contract Unsigned(T) {
    T uint, uint8, uint16, uint32, uint64
}
func HammingDist(type T Unsigned) (b1, b2 []T) (sum int) {
    if len(b1) != len(b2) {
        panic("slices of different lengths passed to HammingDist")
    }
    for i := range b1 {
        sum += bits.OnesCount(b1[i]^b2[i]) // Same assumption about OnesCount being generic you made above
    }
    return sum
}

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

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

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

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

من الواضح أنه ليس لدي أي فكرة عن الكيفية التي سيقوم بها مشرفو golang في النهاية بتنفيذ أي شيء ، لذلك سأمتنع عن التكهن أكثر وسأكون سعيدًا لمن لديهم معرفة أكثر من الداخل.

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

BenchmarkHD/256-bit_unrolled_array_HD-20            2000000000           1.05 ns/op        0 B/op          0 allocs/op
BenchmarkHD/256-bit_slice_HD-20                     300000000            5.10 ns/op        0 B/op          0 allocs/op

الكود على: https://github.com/vsivsi/hdtest

هذا فرق محتمل في الأداء بمقدار 5 أضعاف لحالة 4x64 بت (نقطة جيدة في عملي) مع القليل من فتح الحلقة (ولا يوجد في الأساس رمز منبعث إضافي) في حالة الصفيف. هذه الحسابات موجودة في الحلقات الداخلية لخوارزمياتي ، والتي تم إجراؤها فعليًا عدة تريليونات من المرات ، لذا فإن اختلاف الأداء بمقدار 5 أضعاف كبير جدًا. ولكن لتحقيق مكاسب الكفاءة هذه اليوم ، أحتاج إلى كتابة كل إصدار من الوظيفة ، لكل نوع عنصر مطلوب وطول مصفوفة.

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

على أي حال ، مناقشة ممتعة. أعلم أن هذه قضايا خلافية ، لذا شكرًا لإبقائها حضارية!

vsivsi FWIW ، تتلاشى المكاسب التي تلاحظها إذا لم تقم بفك الحلقات يدويًا (أو إذا كنت تقوم أيضًا بفك الحلقة عبر شريحة) - لذلك لا يزال هذا لا يدعم في الواقع حجتك بأن المعلمات الصحيحة تساعد لأنها تسمح المترجم للقيام بالتمرير نيابة عنك. يبدو لي أنه علم سيء ، أن أجادل X على Y ، استنادًا إلى أن المترجم أصبح ذكيًا بشكل تعسفي بالنسبة لـ X وظل غبيًا بشكل تعسفي بالنسبة لـ Y. ليس من الواضح بالنسبة لي ، لماذا قد يتم تشغيل استكشافية مختلفة في حالة التكرار فوق مصفوفة ، ولكن لا يتم تشغيلها في حالة التكرار على شريحة بطول معروف في وقت الترجمة. أنت لا تُظهر فوائد نكهة معينة للأدوية على أخرى ، فأنت تُظهر فوائد ذلك الاستدلال التجريبي المختلف.

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

Merovius أعتقد أن أقوى حالة للأدوية الجنيسة في هذا النوع من الأمثلة هي توضيح وقت التجميع (لذا فإن إصدار وظيفة فريدة لكل عدد صحيح على مستوى النوع) حيث يكون الرمز المراد تخصصه في مكتبة. إذا كان مستخدم المكتبة سيستخدم عددًا محدودًا من عمليات إنشاء مثيل للوظيفة ، فسيستفيدون من الإصدار الأمثل. لذلك إذا كان الكود الخاص بي يستخدم فقط المصفوفات ذات الطول 64 ، فيمكنني استخدام التوضيحات المُحسَّنة لوظائف المكتبة للطول 64.

في هذه الحالة المحددة ، يعتمد الأمر على التوزيع التكراري لأطوال المصفوفات ، لأننا قد لا نرغب في توضيح جميع الوظائف الممكنة إذا كان هناك الآلاف منها بسبب قيود الذاكرة ، ومهملات ذاكرة التخزين المؤقت للصفحة التي يمكن أن تجعل الأمور أبطأ. على سبيل المثال ، إذا كانت الأحجام الصغيرة شائعة ، ولكن الأكبر ممكنًا (توزيع طويل الذيل في الحجم) ، فيمكننا تطوير وظائف متخصصة للأعداد الصحيحة الصغيرة ذات الحلقات غير المنتظمة (على سبيل المثال من 1 إلى 64) ثم توفير نسخة معممة واحدة مع مخفي - معلمة لبقية.

لا أحب فكرة "المترجم الذكي بشكل تعسفي" وأعتقد أن هذه حجة سيئة. إلى متى سأنتظر هذا المترجم الذكي بشكل تعسفي؟ لا أحب على وجه الخصوص فكرة قيام المترجم بتغيير الأنواع ، على سبيل المثال تحسين شريحة إلى مصفوفة مما يجعل التخصصات المخفية في لغة مع انعكاس ، كما لو كنت تفكر في تلك الشريحة ، فقد يحدث شيء غير متوقع.

فيما يتعلق بـ "المعضلة العامة" ، أنا شخصياً سأختار "جعل المترجم أبطأ / يقوم بمزيد من العمل" ، لكن حاول أن تجعله أسرع ما يمكن باستخدام تطبيق جيد وتجميع منفصل. يبدو أن Rust يعمل بشكل جيد ، وبعد إعلان Intel الأخير يبدو أنه قد يحل في النهاية محل "C" كلغة برمجة الأنظمة الرئيسية. لا يبدو أن وقت التجميع كان حتى عاملاً في قرار إنتل ، حيث يبدو أن ذاكرة وقت التشغيل وأمان التزامن مع سرعة مثل "C" هما العاملان الرئيسيان. تعتبر "سمات" Rust تطبيقًا معقولًا لفئات النوع العامة ، ولديهم بعض حالات الزاوية المزعجة التي أعتقد أنها تأتي من تصميم نظام النوع الخاص بهم.

بالرجوع إلى مناقشتنا السابقة ، يجب أن أكون حريصًا على فصل المناقشة حول الأدوية الجنيسة بشكل عام ، وكيف يمكن تطبيقها تحديدًا على Go. على هذا النحو ، لست متأكدًا من أن Go يجب أن يكون لديها حتى الأدوية الجنيسة لأنها تعقد ما هي لغة بسيطة وأنيقة ، إلى حد كبير في الطريقة التي لا تحتوي بها كلمة "C" على الأدوية الجنيسة. ما زلت أعتقد أن هناك فجوة في السوق للغة لها تطبيقات عامة كميزة أساسية ، لكنها تظل بسيطة وأنيقة.

أتساءل عما إذا كان هناك أي تقدم في هذا الشأن.

كم من الوقت يمكنني تجربة الأدوية الجنيسة. لقد كنت أنتظر لفترة طويلة

Nsgj يمكنك التحقق من هذا CL: https://go-review.googlesource.com/c/go/+/187317/

في المواصفات الحالية ، هل هذا ممكن؟

contract Point(T) {
  T struct { X, Y float64 }
}

بمعنى آخر ، يجب أن يكون النوع عبارة عن هيكل يحتوي على حقلين ، X و Y ، من النوع float64.

تحرير: مع استخدام المثال

func generate(type T Point)() T {
  return T{X: randomFloat64(), Y: randomFloat64()}
}

@ abuchanan-nr نعم ، سيسمح مشروع التصميم الحالي بذلك ، على الرغم من صعوبة معرفة كيف سيكون مفيدًا.

لست متأكدًا أيضًا من أنه مفيد ، لكنني لم أر مثالًا واضحًا لاستخدام نوع بنية مخصصة في قائمة نوع العقد. تستخدم معظم الأمثلة أنواعًا مضمنة.

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

type MyVertex struct {
  X, Y float64
  Color color.Color
  OtherAttr int
}
p := geo.RandomPolygon(MyVertex)()

for _, vert := range p.Vertices() {
  p.Color = randColor()
}

مرة أخرى ، لست متأكدًا من أن هذا كان تصميمًا جيدًا من الناحية العملية ، ولكن هذا هو المكان الذي كان فيه خيالي في ذلك الوقت :)

راجع https://godoc.org/image#Image لمعرفة كيفية القيام بذلك في معيار Go اليوم.

فيما يتعلق بالمشغلين / أنواع العقود :

ينتج عن هذا تكرار العديد من الطرق العامة ، حيث سنحتاجها في تنسيق عامل التشغيل ( + ، == ، < ، ...) وتنسيق الطريقة ( Plus(T) T ، Equal(T) bool ، LessThan(T) bool ، ...).

أقترح أن نوحد هذين النهجين في واحد ، تنسيق الطريقة. لتحقيق ذلك ، يجب تحويل الأنواع المعلنة مسبقًا ( int ، int64 ، string ، ...) إلى أنواع ذات طرق عشوائية. بالنسبة للحالة البسيطة (البسيطة) التي تكون ممكنة بالفعل ( type MyInt int; func (i MyInt) LessThan(o MyInt) bool {return int(i) < int(o)} ) ، لكن القيمة الحقيقية تكمن في الأنواع المركبة ( []int -> []MyInt ، map[int]struct{} -> map[MyInt]struct{} ، وهكذا للقناة ، المؤشر ، ...) ، وهو غير مسموح به (راجع الأسئلة الشائعة ). يُعد السماح بهذه التحويلات تغييرًا مهمًا في حد ذاته ، لذا فقد توسعت في الجوانب الفنية في اقتراح تحويل النوع المريح . سيسمح ذلك لوظائف الأدوية العامة بعدم التعامل مع المشغلين مع استمرار دعم جميع الأنواع ، بما في ذلك ما تم الإعلان عنه مسبقًا.

لاحظ أن هذا التغيير يفيد أيضًا الأنواع غير المعرفة مسبقًا. بموجب الاقتراح الحالي ، بالنظر type X struct{S string} (الذي يأتي من مكتبة خارجية ، لذلك لا يمكنك إضافة طرق إليها) ، لنفترض أن لديك []X وتريد تمريره إلى وظيفة عامة توقع []T ، مقابل T إرضاء العقد Stringer . سيتطلب ذلك type X2 X; func(x X2) String() string {return x.S} ، ونسخة عميقة من []X إلى []X2 . بموجب هذه التغييرات المقترحة على هذا الاقتراح ، تقوم بحفظ النسخة العميقة بالكامل.

ملاحظة: يتطلب اقتراح تحويل النوع المريح المذكور تحديًا.

JavierZunzunegui لا يعد توفير "تنسيق طريقة" (أو تنسيق عامل) لمشغلي التشغيل الأحادي / الثنائي الأساسي هو المشكلة. من السهل جدًا تقديم طرق مثل +(x int) int من خلال السماح برموز المشغل كأسماء طرق ، وتوسيع ذلك ليشمل الأنواع المضمنة (على الرغم من أن هذا ينهار للتحولات حيث يمكن أن يكون عامل التشغيل الأيمن نوع عدد صحيح عشوائي - ليس لدينا طريقة للتعبير عن هذا في الوقت الحالي). المشكلة هي أن هذا لا يكفي. أحد الأشياء التي يحتاج العقد إلى التعبير عنها هو ما إذا كانت القيمة x من النوع X يمكن تحويلها إلى نوع معلمة النوع T كما في T(x) والعكس صحيح. أي ، يحتاج المرء إلى ابتكار "تنسيق أسلوب" للتحويلات المسموح بها. علاوة على ذلك ، يجب أن تكون هناك طريقة للتعبير عن أنه يمكن تعيين ثابت غير من النوع c (أو تحويله إلى) متغير من نوع المعلمة من النوع T : هل من القانوني التعيين ، على سبيل المثال ، 256 إلى t من النوع T ؟ ماذا لو كان T هو byte ؟ هناك القليل من الأشياء مثل هذا. يمكن للمرء أن يخترع تدوين "تنسيق الطريقة" لهذه الأشياء ، لكنه يتعقد بسرعة ، وليس من الواضح أنه أكثر قابلية للفهم أو القراءة.

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

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

إذا لم تكن هناك تحويلات ضمنية في نظام النوع ، فيمكنني استخدام التحميل الزائد للتحكم بدقة في نطاق الأنواع المقبولة ، والتحكم في التحميل الزائد للواجهات.

أميل إلى التعبير عن التشابه بين الأنواع التي تستخدم الواجهات ، وبالتالي فإن عمليات مثل "+" سيتم التعبير عنها بشكل عام كعمليات على واجهة رقمية بدلاً من نوع. يجب أن يكون لديك متغيرات من النوع وكذلك واجهات للتعبير عن القيد الذي يجب أن تكون كل من الوسيطات له ونتيجة الإضافة من نفس النوع.

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

قد ينتهي بك الأمر بشيء مثل:

+(T Addable)(x T, y T) T

إذا سمحت بالاختيار الضمني للواجهة ، فعندئذٍ يمكن أن يكون عامل التشغيل "+" مجرد طريقة للواجهة الرقمية ، لكنني أعتقد أن ذلك قد يتسبب في مشاكل في اختيار الطريقة في Go؟

griesemer تتحدث عن وجهة نظرك حول التحويلات:

أحد الأشياء التي يحتاج العقد إلى التعبير عنها هو ما إذا كان يمكن تحويل القيمة x من النوع X إلى نوع معلمة النوع T كما في T (x) (والعكس صحيح). أي ، يحتاج المرء إلى ابتكار "تنسيق أسلوب" للتحويلات المسموح بها

أستطيع أن أرى كيف سيكون ذلك من المضاعفات ، لكنني لا أعتقد أن هناك حاجة لذلك. هم بالطريقة التي أراها ستحدث مثل هذه التحويلات خارج الشفرة العامة ، من قبل المتصل. مثال (باستخدام Stringify حسب مسودة التصميم):

Stringify(int)([]int{1,2}) // does not compile
type MyInt int
func (i MyInt) String() string {...}
Stringify(MyInt)([]MyInt([]int{1,2})) // OK. Generic type MyInt could be inferred

أعلاه ، بقدر ما يتعلق الأمر Stringify فإن الوسيطة هي كتابة []MyInt وتفي بالعقد. لا يمكن للكود العام تحويل الأنواع العامة إلى أي شيء آخر (بخلاف الواجهات التي ينفذونها ، وفقًا للعقد) ، على وجه التحديد لأن عقدهم لا ينص على شيء عن ذلك.

JavierZunzunegui لا أرى كيف يمكن للمتصل إجراء مثل هذه التحويلات بدون كشفها في الواجهة / العقد. على سبيل المثال ، قد أرغب في تنفيذ خوارزمية رقمية عامة (دالة ذات معلمات) تعمل على أنواع مختلفة من الأعداد الصحيحة أو الفاصلة العائمة. كجزء من هذه الخوارزمية ، يحتاج رمز الوظيفة إلى تعيين قيم ثابتة c1 ، c2 ، إلخ إلى قيم نوع المعلمة T . لا أرى كيف يمكن للكود القيام بذلك دون معرفة أنه لا بأس من تعيين هذه الثوابت لمتغير من النوع T . (لن يرغب المرء بالتأكيد في تمرير تلك الثوابت إلى الدالة.)

func NumericAlgorithm(type T SomeContract)(vector []T) T {
   ...
   vector[i] = 3.1415  // <<< how do we know this is valid without the contract telling us?
   ...
}

يحتاج إلى تعيين قيم ثابتة c1 ، c2 ، وما إلى ذلك لقيم نوع المعلمة T

griesemer أود (من وجهة نظري كيف تكون / يجب أن تكون الأدوية الجنيسة) أقول ما ورد أعلاه هو بيان مشكلة خاطئ. أنت تطلب من T أن يتم تعريفه على أنه float32 ، لكن العقد ينص فقط على الطرق المتاحة لـ T ، وليس ما تم تعريفه به. إذا كنت بحاجة إلى هذا ، فيمكنك إما الاحتفاظ بـ vector كـ []T وتطلب وسيطة func(float32) T ( vector[i] = f(c1) ) ، أو أفضل بكثير الاحتفاظ بـ vector كـ []float32 وتتطلب T عن طريق العقد للحصول على طريقة DoSomething(float32) أو DoSomething([]float32) ، لأنني أفترض T و يجب أن تتفاعل العوامات في مرحلة ما. هذا يعني أن T قد يُعرّف أو لا يُعرّف على أنه type T float32 ، كل ما يمكننا قوله هو أنه يحتوي على الأساليب المطلوبة منه بموجب العقد.

JavierZunzunegui أنا لا أقول على الإطلاق أن T يتم تعريفه على أنه float32 - يمكن أن يكون float32 ، أو float64 ، أو حتى واحد من أنواع معقدة. بشكل عام ، إذا كان الثابت عددًا صحيحًا ، يمكن أن يكون هناك مجموعة متنوعة من أنواع الأعداد الصحيحة التي يمكن أن تكون صالحة لتمريرها إلى هذه الدالة ، وبعضها ليس كذلك. إنه بالتأكيد ليس "بيان مشكلة خاطئ". المشكلة حقيقية - من المؤكد أنه ليس من المفترض على الإطلاق أن تكون قادرًا على كتابة مثل هذه الوظائف - والمشكلة لا تختفي عندما تعلن أنها "خاطئة".

griesemer أرى ، أعتقد أنك مهتم بالتحويل وحده ، لم أسجل العنصر الأساسي الذي يتعامل مع ثوابت غير نمطية.

يمكنك القيام بذلك وفقًا لإجابتي أعلاه ، مع وجود طريقة $ # T DoSomething(X) ، وتتخذ الوظيفة وسيطة إضافية func(float64) X ، لذلك يتم تعريف النموذج العام بنوعين ( T,X ). الطريقة التي تصف بها المشكلة هي X عادةً float32 أو float64 ووسيطة الوظيفة هي func(f float64) float32 {return float32(f)} أو func(f float64) float64 {return f} .

الأهم من ذلك ، كما توضح ، بالنسبة لحالة الأعداد الصحيحة ، هناك مشكلة تتمثل في أن تنسيقات الأعداد الصحيحة الأقل دقة قد لا تكون كافية لثابت معين. الطريقة الأكثر أمانًا هي الاحتفاظ بالوظيفة العامة من نوعين ( T,X ) خاصة وإظهار MyFunc32 / MyFunc64 / إلخ.

سوف أعترف بأن MyFunc32(int32) / MyFunc64(int64) / إلخ. أقل عملية من MyFunc(type T Numeric) واحد (العكس لا يمكن الدفاع عنه!). ولكن هذا فقط للتطبيقات العامة التي تعتمد على ثابت ، وفي المقام الأول ثابت عدد صحيح - كم عدد هؤلاء هناك؟ بالنسبة للباقي ، يمكنك الحصول على حرية إضافية تتمثل في عدم التقيد بعدد قليل من الأنواع المضمنة مقابل T .

وبالطبع ، إذا لم تكن الوظيفة باهظة الثمن ، فقد تكون على ما يرام تمامًا عند إجراء الحساب مثل int64 / float64 وفضح ذلك فقط ، مع إبقائه بسيطًا وغير مقيد على T .

لا يمكننا حقًا أن نقول للناس "يمكنك كتابة وظائف عامة على أي نوع T ولكن هذه الوظائف العامة قد لا تستخدم ثوابت غير من النوع." Go هي قبل كل شيء لغة بسيطة. اللغات ذات القيود الغريبة مثل هذه ليست بسيطة.

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

JavierZunzunegui إحدى الخصائص المثيرة للاهتمام للشفرة ذات المعلمات (العامة) هي أن المترجم يمكنه تخصيصها بناءً على النوع (الأنواع) التي يتم إنشاء مثيل لها في الكود. على سبيل المثال ، يريد أحد الترحيل استخدام نوع byte بدلاً من int لأنه يؤدي إلى توفير مساحة كبيرة (تخيل وظيفة تخصص شرائح ضخمة من النوع العام). لذا فإن مجرد تقييد الشفرة على نوع "كبير بما يكفي" يعد إجابة غير مرضية ، حتى بالنسبة للغة "المعتادة" مثل Go.

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

ianlancetaylorgriesemer شكرًا على التعليقات - أستطيع أن أرى أن هناك تعارضًا كبيرًا في التغيير المقترح مع الثوابت غير المصنفة والأعداد الصحيحة السلبية ، سأضعها ورائي.

هل يمكنني لفت انتباهك إلى النقطة الثانية في https://github.com/golang/go/issues/15292#issuecomment -546313279:

لاحظ أن هذا التغيير يفيد أيضًا الأنواع غير المعرفة مسبقًا. بموجب الاقتراح الحالي ، بالنظر إلى النوع X هيكل {S string} (والذي يأتي من مكتبة خارجية ، لذلك لا يمكنك إضافة طرق إليه) ، لنفترض أن لديك [] X وتريد تمريره إلى دالة عامة تتوقع [ ] T ، مقابل إرضاء عقد Stringer. سيتطلب ذلك نوع X2 X ؛ func (x X2) String () string {return xS} ، ونسخة عميقة من [] X في [] X2. بموجب هذه التغييرات المقترحة على هذا الاقتراح ، تقوم بحفظ النسخة العميقة بالكامل.

سيظل تخفيف قواعد التحويل (إذا كان ذلك ممكنًا من الناحية الفنية) مفيدًا.

JavierZunzunegui مناقشة التحويلات من النوع []B([]A) إذا كان B(a) (مع a من النوع A ) مسموح به في الغالب يبدو متعامدًا مع الميزات العامة. أعتقد أننا لسنا بحاجة إلى إحضار هذا هنا.

ianlancetaylor لست متأكدًا من مدى ملاءمة هذا الأمر ، لكنني لا أعتقد أن الثوابت غير مطبوعة حقًا ، يجب أن يكون لها نوع حيث يجب على المترجم أن يختار تمثيل الآلة. أعتقد أن المصطلح الأفضل هو ثوابت من نوع غير محدد ، حيث يمكن تمثيل الثابت بعدة أنواع مختلفة. يتمثل أحد الحلول في استخدام نوع الاتحاد بحيث يكون للثابت مثل 27 نوع مثل int16|int32|float16|float32 اتحاد من جميع الأنواع الممكنة. ثم T في النوع العام يمكن أن يكون هذا النوع من الاتحاد. الشرط الوحيد هو أنه يجب علينا في مرحلة ما حل الاتحاد لنوع واحد. قد تكون الحالة الأكثر إشكالية هي شيء مثل print(27) لأنه لا يوجد نوع واحد لحل المشكلة ، في مثل هذه الحالات يمكن لأي نوع في الاتحاد القيام به ، ويمكننا الاختيار بناءً على معلمة تحسين مثل المساحة / السرعة وما إلى ذلك. .

keean الاسم الدقيق والتعامل مع ما تسميه المواصفات "الثوابت غير المصنفة" خارج عن الموضوع في هذه المسألة. دعنا نأخذ هذه المناقشة في مكان آخر من فضلك. شكرا.

ianlancetaylor يسعدني ذلك ، ولكن هذا هو أحد الأسباب التي تجعلني أعتقد أن Go لا يمكن أن يكون لها تنفيذ نظيف / بسيط للأدوية ، فكل هذه المشكلات مترابطة ، ولم يتم أخذ الخيارات الأصلية التي تم إجراؤها لـ Go مع وضع البرمجة العامة في الاعتبار. أعتقد أن هناك حاجة إلى لغة أخرى ، مصممة لجعل الأدوية العامة بسيطة حسب التصميم ، بالنسبة لـ Go ، ستكون الأدوية الجنيسة دائمًا شيئًا يضاف إلى اللغة لاحقًا ، وأفضل خيار للحفاظ على اللغة نظيفة وبسيطة قد يكون عدم استخدامها على الإطلاق.

إذا كنت أرغب اليوم في تصميم لغة بسيطة بأوقات ترجمة سريعة ومرونة قابلة للمقارنة ، فسأختار طريقة التحميل الزائد والتعدد الهيكلي (التصنيف الفرعي) عبر واجهات golang وعدم وجود الأدوية الجنيسة. في الواقع ، سيسمح بالحمل الزائد على واجهات مجهولة مختلفة ذات مجالات مختلفة.

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

بتوضيح ذلك ، أنا لست ضد الأدوية الجنيسة ، لكنني أفكر فيما إذا كانت هذه هي الطريقة التي يجب اتباعها فيما يتعلق بالحفاظ على بساطة go's.

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

فيما يتعلق بالتأثير على أداء وقت التشغيل ، فإن هذا من شأنه أن يقلل من الأداء بسبب مشكلة الملاكمة / unboxing ، وإلا ، هناك مهندسين من مستوى c ++ ينصحون بأدوية الملاكمة مثل java من أجل التخفيف من أخطاء ذاكرة التخزين المؤقت.

ianlancetaylorgriesemer لقد أعدت النظر في مسألة الثوابت غير المصنفة والأدوية الجنيسة "غير المشغلة" (https://github.com/golang/go/issues/15292#issuecomment-547166519) ووجدت طريقة أفضل للتعامل معها.

أعط الأنواع nummetic ( type MyInt32 int32 ، type MyInt64 int64 ، ...) ، هذه لها العديد من الطرق التي تفي بنفس العقد ( Add(T) T ، ...) ولكن بشكل حاسم ليس غيرها قد يخاطر بتجاوز func(MyInt64) FromI64(int64) MyInt64 لكن لا يوجد ~ func(MyInt32) FromI64(int64) MyInt32 ~. يتيح ذلك استخدام ثوابت رقمية (يتم تعيينها صراحة لأدنى قيمة دقة تتطلبها) بأمان (1) لأن الأنواع الرقمية منخفضة الدقة لن تفي بالعقد المطلوب ، ولكن جميع الأنواع الأعلى منها ستفي بالعقد المطلوب. انظر إلى الملعب ، باستخدام واجهات بدلاً من الأدوية الجنيسة.

ميزة الاسترخاء في الأدوية الرقمية التي تتعدى الأنواع المضمنة (ليست خاصة بهذه المراجعة الأخيرة ، لذلك كان يجب أن أشاركها الأسبوع الماضي) هي أنها تسمح بإنشاء طرق عامة مع أنواع فحص الفائض - انظر الملعب . يعد فحص الفائض بحد ذاته طلبًا / اقتراحًا شائعًا جدًا (https://github.com/golang/go/issues/31500 والمشكلات ذات الصلة).


(1) : ضمان وقت الترجمة غير الفائض للثوابت غير المصنفة قوي داخل نفس "الفرع" ( int[8/16/32/64] و uint[8/16/32/64] ). عند عبور الفروع ، لا يتم إنشاء مثيل ثابت uint[X] بأمان إلى int[2X+] ولا يمكن إنشاء مثيل ثابت int[X] بأمان بواسطة أي uint[X] على الإطلاق. حتى الاسترخاء (السماح بدولار int[X]<->uint[X] ) سيكون أمرًا بسيطًا وآمنًا باتباع بعض المعايير الدنيا ، والأهم من ذلك أن أي تعقيد يقع على عاتق كاتب الكود العام ، وليس على مستخدم العام (الذي يهتم فقط بالعقد) ، ويمكن توقع صحة أي نوع رقمي يفي به).

طرق عامة - كان سقوط جافا!

ianlancetaylor يسعدني ذلك ، ولكن هذا هو أحد الأسباب التي تجعلني أعتقد أن Go لا يمكن أن يكون لها تنفيذ نظيف / بسيط للأدوية ، فكل هذه المشكلات مترابطة ، ولم يتم أخذ الخيارات الأصلية التي تم إجراؤها لـ Go مع وضع البرمجة العامة في الاعتبار. أعتقد أن هناك حاجة إلى لغة أخرى ، مصممة لجعل الأدوية العامة بسيطة حسب التصميم ، بالنسبة لـ Go ، ستكون الأدوية الجنيسة دائمًا شيئًا يضاف إلى اللغة لاحقًا ، وأفضل خيار للحفاظ على اللغة نظيفة وبسيطة قد يكون عدم استخدامها على الإطلاق.

أوافق 100٪. بقدر ما أحب أن أرى نوعًا من الأدوية الجنيسة مطبقة ، أعتقد أن ما تطبخونه يا رفاق حاليًا سيدمر بساطة لغة Go.

تبدو الفكرة الحالية لتوسيع الواجهات كما يلي:

type I1(type P1) interface {
        m1(x P1)
}

type I2(type P1, P2) interface {
        m2(x P1) P2
        type int, float64
}

func f(type P1 I1(P1), P2 I2(P1, P2)) (x P1, y P2) P2

آسف على الإطلاق ، ولكن من فضلك لا تفعل هذا! إنه قبيح جمال Go big time.

بعد أن كتبت ما يقرب من 100 ألف سطر من كود Go الآن ، فأنا على ما يرام مع عدم وجود أدوية عامة.

ومع ذلك ، فإن الأشياء الصغيرة مثل الدعم

// Allow mulitple types in Slices and Maps declarations
func Reverse(s []<int,string>) {
    first := 0
    last := len(s) - 1
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

//  Allow multiple types in variable declarations
func Index (s <string, []byte>, b byte) int {
    for i := 0; i < len(s); i++ {
        if s[i] == b {
            return i
        }
    }
    return -1
}

// Allow slices and maps declarations with interface values
func ToStrings (s []Stringer) []string {
    r := make([]string, len(s))
    for i, v := range s {
        r[i] = v.String()
    }
    return r
}

من شأنه أن يساعد.

اقتراح بناء الجملة للتمكن من فصل الأدوية الجنسية تمامًا عن كود Go العادي

package graph

// Example how you would define generics completely separat from Go 1 code
contract (Node, Edge)G {
    Node Edges() []Edge
    Edge Nodes() (from, to Node)
}

type (type Node, Edge G) ( Graph )
func (type Node, Edge G) ( New )
const _ = (Node, Edge) Graph

// Unmodified Go 1 code
type Graph struct { ... }
func New(nodes []Node) *Graph { ... }
func (g *Graph) ShortestPath(from, to Node) []Edge { ... }

martinrode ومع ذلك ، فإن الأشياء الصغيرة مثل الدعم
... السماح بأنواع متعددة في تعريفات الشرائح والخرائط

هذا لا يجيب عن احتياجات بعض وظائف الشرائح العامة الوظيفية ، مثل head() ، tail() ، map(slice, func) ، filter(slice, func)

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

(على المستوى الشخصي ، من المتعب أيضًا معرفة أن لدي مجموعة من الميزات التي أرغب في تنفيذها وليس لدي طريقة نظيفة للتعبير عنها دون الرد أيضًا على قيود اللغة)

ضع في اعتبارك ما يلي في الانتقال الحالي غير العام:

لدي متغير x من النوع externallib.Foo ، تم الحصول عليه من مكتبة externallib لا أتحكم فيه.
أريد أن أنقله إلى دالة SomeFunc(fmt.Stringer) ، لكن externallib.Foo ليس لديه طريقة String() string . يمكنني ببساطة أن أفعل:

type MyFoo externallib.Foo
func (mf MyFoo) String() string {...}
// ...
SomeFunc(MyFoo(x))

ضع في اعتبارك نفس الشيء مع الأدوية الجنيسة.

لدي متغير x من النوع []externallib.Foo . أريد أن أنقله إلى AnotherFunc(type T Stringer)(s []T) . لا يمكن القيام بذلك بدون نسخ عميق باهظ الثمن للشريحة إلى []MyFoo جديد. إذا كان نوعًا أكثر تعقيدًا بدلاً من شريحة (على سبيل المثال ، تشان أو خريطة) ، أو عدلت الطريقة جهاز الاستقبال ، فإنه يصبح أكثر فاعلية ومملًا ، إذا كان ذلك ممكنًا على الإطلاق.

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

اقتراحي هو تخفيف التحويل للسماح بـ []Foo([]Bar{}) لأي Foo مُعرَّف على أنه type Foo Bar ، أو العكس ، وبالمثل للخرائط والمصفوفات والقنوات والمؤشرات بشكل متكرر. لاحظ أن هذه كلها نسخ ضحلة رخيصة. مزيد من التفاصيل الفنية في "مقترح تحويل النوع المريح" .


تم طرح هذا لأول مرة كميزة ثانوية في https://github.com/golang/go/issues/15292#issuecomment -546313279.

JavierZunzunegui لا أعتقد أن هذا مرتبط حقًا بالأدوية الجنيسة على الإطلاق. نعم ، يمكنك تقديم مثال باستخدام الأدوية الجنيسة ، ولكن يمكنك تقديم مثال مشابه دون استخدام الأدوية الجنيسة. أعتقد أنه ينبغي مناقشة هذه المسألة بشكل منفصل ، وليس هنا. راجع أيضًا https://golang.org/doc/faq#convert_slice_with_same_underlying_type. شكرا.

بدون الأدوية الجنيسة ، فإن هذا التحويل لا قيمة له على الإطلاق ، لأنه بشكل عام []Foo لن يلبي أي واجهة ، أو على الأقل لا توجد واجهة تستفيد من كونها شريحة. الاستثناء هو الواجهات التي لها نمط محدد للغاية للاستفادة منه ، مثل Sort.Interface ، والتي لا تحتاج إلى تحويل الشريحة على أي حال.

الإصدار غير العام لما ورد أعلاه ( func AnotherFunc(type T Stringer)(s []T) ) هو

type SliceOfStringers interface {
  Len() int
  Get(int) fmt.Stringer
}
func AnotherFunc(s SliceOfStringers) {...}

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

غالبًا ما يظهر نوع التحويل الذي ذكرته في التعليمات البرمجية غير العامة بشكل كافٍ بحيث يكون من الأسئلة الشائعة. دعنا ننتقل من فضلك هذه المناقشة إلى مكان آخر. شكرا.

ما هي حالة هذا؟ أي مسودة محدثة؟ أنا في انتظار الأدوية منذ ذلك الحين
منذ ما يقرب من عامين. متى سيكون لدينا الأدوية الجنيسة؟

المار، 4 دى فبراير. de 2020 على طريقة 13:28 ، إيان لانس تايلور (
[email protected]) escribió:

غالبًا ما يأتي تحويل النوع الذي ذكرته بشكل كافٍ في رمز غير عام
أنها أسئلة وأجوبة. دعنا ننتقل من فضلك هذه المناقشة إلى مكان آخر. شكرا.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292؟email_source=notifications&email_token=AJMFNBN3MFHDMENAFXIKBLDRBGXUTA5CNFSM4CA35RX2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63L20
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AJMFNBO5UTKNPL3MSA3NESLRBGXUTANCNFSM4CA35RXQ
.

-
هذا اختبار لتوقيعات البريد لاستخدامها في TripleMint

نحن نعمل على ذلك. بعض الاشياء تأخذ الوقت.

هل تم العمل دون اتصال بالإنترنت؟ أود أن أراها تتطور بمرور الوقت ، بطريقة لا يستطيع "الجمهور العام" مثلي التعليق عليها لتجنب الضوضاء.

على الرغم من إغلاقه منذ ذلك الحين للاحتفاظ بمناقشة الأدوية الجنيسة في مكان واحد ، تحقق من # 36177 حيث يرتبط Griesemer بنموذج أولي يعمل عليه ويقدم بعض التعليقات المثيرة للاهتمام حول أفكاره حول هذه المسألة حتى الآن.

أعتقد أنني على صواب في القول إن النموذج الأولي يتعامل فقط مع جوانب فحص النوع لمسودة اقتراح "العقود" في الوقت الحالي ، لكن العمل يبدو بالتأكيد واعدًا بالنسبة لي.

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

يعد هذا أمرًا مثاليًا يجب السعي لتحقيقه ولكن في الواقع ، فإن تطوير البرامج في بعض الأحيان ليس بطبيعته - بسيطًا للتفسير -.

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

انظر إلى Istio و Kubernetes و المشغل sdk وإلى حد ما Terraform وحتى مكتبة protobuf. جميعهم يهربون من نظام النوع Go باستخدام الانعكاس ، أو تنفيذ نظام نوع جديد أعلى Go باستخدام الواجهات وإنشاء الكود ، أو مزيج من هذه.

omeid

انظر إلى Istio ، Kubernetes

هل حدث لك أن سبب قيامهم بهذه الأشياء السخيفة هو أن تصميمهم الأساسي ليس له أي معنى ، ونتيجة لذلك كان عليهم أن ينتجوا ألعاب reflect لتحقيق ذلك ؟

أصر على أن التصميمات الأفضل لبرامج golang (في كل من مرحلة التصميم وفي واجهة برمجة التطبيقات) لا تتطلب الأدوية الجنيسة.

من فضلك لا تضيفهم إلى golang.

البرمجة صعبة. Kubelet مكان مظلم. العوامل العامة تقسم الناس أكثر من السياسة الأمريكية. اريد ان اصدق.

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

انظر إلى Istio و Kubernetes و المشغل sdk وإلى حد ما Terraform وحتى مكتبة protobuf. جميعهم يهربون من نظام النوع Go باستخدام الانعكاس ، أو تنفيذ نظام نوع جديد أعلى Go باستخدام الواجهات وإنشاء الكود ، أو مزيج من هذه.

لا أجد ذلك حجة مقنعة. يجب أن تكون لغة Go من الناحية المثالية سهلة القراءة والكتابة والفهم ، مع استمرار إمكانية إجراء عمليات معقدة بشكل تعسفي. هذا يتوافق مع ما تقوله: الأدوات التي ذكرتها تحتاج إلى القيام بشيء معقد ، و Go يمنحهم طريقة للقيام بذلك.

يجب أن تكون لغة Go من الناحية المثالية سهلة القراءة والكتابة والفهم ، مع استمرار إمكانية إجراء عمليات معقدة بشكل تعسفي.

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

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

ولهذا السبب يظل هذا الاقتراح مفتوحًا ولماذا لدينا مسودة تصميم لتطبيق محتمل للأدوية (https://blog.golang.org/why-generics).

انظروا ... حتى مكتبة protobuf. جميعهم يهربون من نظام النوع Go باستخدام الانعكاس ، أو تنفيذ نظام نوع جديد أعلى Go باستخدام الواجهات وإنشاء الكود ، أو مزيج من هذه.

عند الحديث من تجربة مع protobufs ، هناك عدد قليل من الحالات حيث يمكن للأدوية الجنيسة تحسين قابلية الاستخدام و / أو تنفيذ API ، لكن الغالبية العظمى من المنطق لن تستفيد من الأدوية الجنيسة. تفترض الوراثة أن معلومات النوع الملموس معروفة في وقت الترجمة . بالنسبة إلى protobufs ، تتضمن معظم المواقف الحالات التي تكون فيها معلومات النوع معروفة فقط في وقت التشغيل .

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

dsnet شكرًا مثيرًا للاهتمام ، لم أفكر مطلقًا في أن protobuf لا يكون متوافقًا بشكل عام. افترضت دائمًا أن كل أداة تنشئ رمزًا أساسيًا ، مثل protoc ، استنادًا إلى مخطط محدد مسبقًا ، ستكون قادرة على إنشاء رمز عام دون انعكاس باستخدام الاقتراح العام الحالي. هل تمانع في تحديث هذا في المواصفات بمثال أو في منشور مدونة جديد حيث تصف هذه المشكلة بمزيد من التفصيل؟

الأدوات التي ذكرتها تحتاج إلى القيام بشيء معقد ، ويمنحهم Go طريقة للقيام بذلك.

إن استخدام القوالب النصية لإنشاء كود Go ليس مرفقًا حسب التصميم ، أود أن أزعم أنه أداة مساعدة شريطية مخصصة ، من الناحية المثالية ، على الأقل يجب أن تسمح حزم ast و parser القياسية بإنشاء كود Go التعسفي.

الشيء الوحيد الذي يمكنك أن تجادل بأن Go يمنح الشخص للتعامل مع المنطق المعقد هو الانعكاس ، لكن هذا يوضح بسرعة حدوده ، وليس التحدث عن الكود المهم للأداء ، حتى عند استخدامه في المكتبة القياسية ، على سبيل المثال ، معالجة Go's JSON بدائية في أحسن الأحوال.

من الصعب المجادلة بأن استخدام قوالب النص أو الانعكاس للقيام _شيء معقد بالفعل_ يناسب المثل الأعلى:

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

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

بشكل عام ، لاحظت أن الناس غالبًا ما يشيرون إلى أي استخدام للتفكير ويدعون ذلك كدليل على الحاجة إلى الأدوية الجنيسة.

ربما يكون هناك مثل هذا المفهوم الخاطئ العام ، ولكن مكتبة protobuf ، وخاصة واجهة برمجة التطبيقات الجديدة يمكن أن تكون قفزات كبيرة بحدود أكثر بساطة مع _generics_ ، أو نوع ما _sum_.

قال أحد مؤلفي واجهة برمجة التطبيقات الجديدة protobuf "لن تستفيد الغالبية العظمى من المنطق من الأدوية الجنيسة" ، لذلك لست متأكدًا من أين تحصل على ذلك "خاصة أن واجهة برمجة التطبيقات الجديدة يمكن أن تكون قفزات كبيرة بسيط مع الأدوية ". ما هذا على أساس؟ هل يمكنك تقديم أي دليل على أن الأمر سيكون أبسط بكثير؟

بالحديث باعتباري شخصًا استخدم واجهات برمجة تطبيقات protobuf في عدة لغات تتضمن لغات عامة (Java ، C ++) ، لا أستطيع أن أقول إنني لاحظت أي اختلافات كبيرة في قابلية الاستخدام مع Go API وواجهات برمجة التطبيقات الخاصة بهم. إذا كان تأكيدك صحيحًا ، كنت أتوقع أن يكون هناك بعض الاختلاف من هذا القبيل.

قال dsnet أيضًا "هناك حالات قليلة يمكن فيها للأدوية الجنيسة تحسين قابلية الاستخدام و / أو تنفيذ واجهة برمجة التطبيقات".

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

omeid تتعلق هذه المشكلة بالأدوية وليس أنواع المجموع. لذلك لست متأكدًا من مدى صلة هذا المثال.

سؤالي على وجه التحديد هو: كيف يمكن أن يؤدي استخدام الأدوية الجنيسة إلى تطبيق protobuf أو واجهة برمجة تطبيقات "أبسط كثيرًا" من واجهة برمجة التطبيقات الجديدة (أو القديمة)؟

يبدو أن هذا لا يتماشى مع قراءتي لما قالتهdsnet أعلاه ، ولا مع تجربتي مع واجهات برمجة تطبيقات Java و C ++ protobuf.

أيضًا ، فإن تعليقك على معالجة JSON البدائية في Go يبدو لي أيضًا غريبًا بنفس القدر. هل يمكنك أن تشرح كيف تعتقد أنه سيتم تحسين واجهة برمجة تطبيقات encoding/json بواسطة الأدوية الجنيسة؟

AFAIK ، تطبيقات تحليل JSON في Java تستخدم الانعكاس (وليس الأدوية العامة). صحيح أن واجهة برمجة التطبيقات ذات المستوى الأعلى في معظم مكتبات JSON ستستخدم على الأرجح طريقة عامة (مثل Gson ) ، ولكنها طريقة تأخذ معلمة عامة غير مقيدة T وتعيد قيمة من النوع T يوفر القليل جدًا من فحص النوع الإضافي عند مقارنته بـ json.Unmarshal . في الواقع ، أعتقد أن الخطأ الوحيد المتمثل في أن سيناريو الخطأ الإضافي الوحيد الذي لم يتم اكتشافه بواسطة json.Unmarshal في وقت الترجمة هو إذا قمت بتمرير قيمة غير مؤشر. (لاحظ أيضًا التحذيرات الواردة في وثائق API الخاصة بـ Gson لاستخدام وظيفة مختلفة للأنواع العامة مقابل الأنواع غير العامة. مرة أخرى ، يجادل هذا بأن الأدوية الجنيسة تعقد واجهة برمجة التطبيقات الخاصة بهم ، بدلاً من تبسيطها ؛ في هذه الحالة ، دعم التسلسل / إلغاء التسلسل العام أنواع).

(دعم JSON في C ++ هو أسوأ من AFAICT ؛ الأساليب المختلفة التي أعرفها إما تستخدم كميات كبيرة من وحدات الماكرو أو تتضمن كتابة وظائف التحليل / التسلسل يدويًا. مرة أخرى ، هذا لا)

إذا كنت تتوقع أن تضيف الأدوية الجنيسة قدرًا كبيرًا إلى دعم Go لـ JSON ، أخشى أنك ستصاب بخيبة أمل.


gertcuykens كل تطبيق protobuf في كل لغة أعرفها يستخدم توليد الكود ، بغض النظر عما إذا كان لديهم أدوية جنيسة أم لا. يتضمن ذلك Java و C ++ و Swift و Rust و JS (و TS). لا أعتقد أن امتلاك الأدوية الجنسية يؤدي تلقائيًا إلى إزالة جميع استخدامات إنشاء الكود (كدليل على الوجود ، لقد كتبت مولدات الأكواد التي تنشئ كود Java ورمز C ++) ؛ يبدو من غير المنطقي توقع أن يلبي أي حل للأدوية الجنيسة هذا المعيار.


فقط لأكون واضحًا تمامًا: أؤيد إضافة الأدوية الجنيسة إلى Go. لكني أعتقد أننا يجب أن نكون واضحين بشأن ما سنخرج منه. لا أعتقد أننا سنحصل على تحسينات كبيرة على واجهات برمجة تطبيقات protobuf أو JSON.

لا أعتقد أن البروتوبوف هو حالة جيدة بشكل خاص للأدوية الجنسية. لا تحتاج إلى أدوية عامة في اللغة الهدف حيث يمكنك ببساطة إنشاء رمز متخصص مباشرة. سينطبق هذا على أنظمة أخرى مماثلة مثل Swagger / OpenAPI أيضًا.

حيث يبدو أن الأدوية الجنيسة مفيدة بالنسبة لي ، ويمكن أن تقدم كلًا من التبسيط وأمان النوع ، فستكون كتابة مترجم protobuf نفسه.

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

حيث يبدو أن الأدوية الجنيسة مفيدة بالنسبة لي ، ويمكن أن تقدم كلًا من التبسيط وأمان النوع ، فستكون كتابة مترجم protobuf نفسه.

أنا حقا لا أرى كيف. توفر الحزمة go/ast تمثيلاً لـ Go's AST. لا يستخدمه برنامج التحويل البرمجي Go protobuf لأن العمل مع AST أكثر تعقيدًا من مجرد إرسال سلاسل ، حتى لو كان أكثر أمانًا من النوع.

ربما لديك مثال من مترجم protobuf لبعض اللغات الأخرى؟

neild لقد بدأت بالقول إنني لا أعتقد أن protobuf كان مثالًا جيدًا جدًا. هناك مكاسب يمكن تحقيقها باستخدام الأدوية الجنيسة ، لكنها تعتمد كثيرًا على مدى أهمية سلامة النوع بالنسبة لك ، ويمكن موازنة ذلك بمدى تطفُّل استخدام الأدوية الجنيسة. سيخرج التطبيق المثالي بعيدًا عن طريقك ، ما لم ترتكب خطأ ، وفي هذه الحالة ستفوق المزايا تكلفة المزيد من حالات الاستخدام.

بالنظر إلى حزمة go / ast ، لا تحتوي على تمثيل مكتوب لـ AST لأن ذلك يتطلب أدوية جنيسة و GADTs. على سبيل المثال ، يجب أن تكون العقدة "add" عامة في نوع المصطلحات التي يتم إضافتها. باستخدام AST غير الآمن من النوع ، يجب أن يتم ترميز منطق التحقق من النوع يدويًا مما يجعله مرهقًا.

باستخدام بنية قالب جيدة وكتابة تعبيرات آمنة ، يمكنك جعلها سهلة مثل إصدار السلاسل ، ولكن أيضًا الكتابة بأمان. على سبيل المثال انظر (هذا أكثر عن جانب التحليل): https://stackoverflow.com/questions/11104536/how-to-parse-strings-to-syntax-tree-using-gadts

على سبيل المثال ، ضع في اعتبارك أن JSX هي بناء جملة حرفي لـ HTML Dom في JavaScript مقابل TSX باعتبارها بناء جملة حرفي لـ Dom في TypeScript.

يمكننا كتابة تعبيرات عامة مكتوبة تتخصص في الكود النهائي. من السهل كتابتها مثل السلاسل ، ولكن تم التحقق من الكتابة (في شكلها العام).

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

معلمات النوع المتغير مفقودة في التصميم الحالي ، والذي يبدو وكأنه مفقود جدًا في وظائف الأدوية الجنيسة. تصميم إضافي (ربما) يتبع تصميم العقد الحالي:

contract Comparables(Ts...) {
    if  len(Ts) > 0 {
        Comparables(Ts[1:]...)
    } else {
        Comparable(Ts[0])
    }
}

contract Comparable(T) {
    T int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

type Keys(type Ts ...Comparables) struct {
    fs ...Ts
}

type Metric(type Ts ...Comparables) struct {
    mu sync.Mutex
    m  map[Keys(Ts...)]int
}

func (m *Metric(Ts...)) Add(vs ...Ts) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[Keys(Ts...))]int)
    }
    m[Keys(Ts...){vs...}]++
}


// To use the metric

m := Metric(int, float64, string){m: make(map[Keys(int, float64, string)]int}
m.Add(1, 2.0, "variadic")

مثال مستوحى من هنا .

ليس من الواضح بالنسبة لي كيف يضيف ذلك أي أمان فوق استخدام interface{} فقط. هل هناك مشكلة حقيقية في تمرير الأشخاص غير القابلة للمقارنة في مقياس؟

ليس من الواضح بالنسبة لي كيف يضيف ذلك أي أمان فوق استخدام interface{} فقط. هل هناك مشكلة حقيقية في تمرير الأشخاص غير القابلة للمقارنة في مقياس؟

يتطلب $ Comparables في هذا المثال Keys يجب أن يتكون من سلسلة من الأنواع القابلة للمقارنة. الفكرة الأساسية هي إظهار تصميم معلمات النوع المتغير ، وليس معنى النوع نفسه.

لا أريد أن أتعلق بالمثال كثيرًا ، لكنني أختاره لأنني أعتقد أن العديد من الأمثلة على "امتداد النوع" ينتهي بها الأمر إلى دفع مسك الدفاتر دون إضافة أي أمان عملي. في هذه الحالة ، إذا رأيت نوعًا سيئًا في وقت التشغيل أو من المحتمل أن يكون لديك طبيب بيطري ، فيمكنك تقديم شكوى بعد ذلك.

أيضًا ، أنا قلق قليلاً من أن السماح بأنواع مفتوحة من الأنواع مثل هذا قد يؤدي إلى مشكلة المراجع المتناقضة ، كما يحدث في منطق الدرجة الثانية. هل يمكنك تعريف C على أنه عقد من جميع الأنواع غير الموجودة في C؟

أيضًا ، أنا قلق قليلاً من أن السماح بأنواع مفتوحة من الأنواع مثل هذا قد يؤدي إلى مشكلة المراجع المتناقضة ، كما يحدث في منطق الدرجة الثانية. هل يمكنك تعريف C على أنه عقد من جميع الأنواع غير الموجودة في C؟

عذرًا ولكن لا أفهم كيف يسمح هذا المثال بأنواع مفتوحة ويتعلق بمفارقة راسل ، يتم تحديد Comparables بواسطة قائمة Comparable .

لا أحب فكرة كتابة كود Go داخل العقد. إذا كان بإمكاني كتابة كشف حساب if ، فهل يمكنني كتابة كشف حساب for ؟ هل يمكنني استدعاء وظيفة؟ هل يمكنني التصريح عن المتغيرات؟ ولم لا؟

يبدو أيضًا أنه غير ضروري. يعني func F(a ...int) أن قيمة a []int . بالقياس ، يعني func F(type Ts ...comparable) أن كل نوع في القائمة هو comparable .

في هذه السطور

type Keys(type Ts ...Comparables) struct {
    fs ...Ts
}

يبدو أنك تقوم بتعريف بنية بحقول متعددة تسمى جميعها fs . لست متأكدًا من كيفية عمل ذلك. هل هناك أي طريقة لاستخدام الإشارة إلى الحقول في هذا الهيكل بخلاف استخدام الانعكاس؟

لذا فإن السؤال هو: ماذا يمكن للمرء أن يفعل بمعلمات النوع المتغير؟ ماذا يريد المرء أن يفعل؟

هنا أعتقد أنك تستخدم معلمات نوع متغيرة لتحديد نوع مجموعة مع عدد تعسفي من الحقول.

ماذا يمكن للمرء أن يفعل أيضا؟

لا أحب فكرة كتابة كود Go داخل العقد. إذا كان بإمكاني كتابة كشف حساب if ، فهل يمكنني كتابة كشف حساب for ؟ هل يمكنني استدعاء وظيفة؟ هل يمكنني التصريح عن المتغيرات؟ ولم لا؟

يبدو أيضًا أنه غير ضروري. يعني func F(a ...int) أن قيمة a []int . بالقياس ، يعني func F(type Ts ...comparable) أن كل نوع في القائمة هو comparable .

بعد مراجعة المثال في اليوم التالي ، أعتقد أنك محق تمامًا. Comparables هي فكرة غبية. المثال يريد فقط نقل رسالة استخدام len(args) لتحديد عدد المعلمات. اتضح بالنسبة للوظائف ، func F(type Ts ...Comparable) جيد بما فيه الكفاية.

المثال المقتطع:

contract Comparable(T) {
    T int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

type Keys(type Ts ...Comparable) struct {
    fs ...Ts
}

type Metric(type Ts ...Comparable) struct {
    mu sync.Mutex
    m  map[Keys(Ts...)]int
}

func (m *Metric(Ts...)) Add(vs ...Ts) {
    m.mu.Lock()
    defer m.mu.Unlock()
    if m.m == nil {
        m.m = make(map[Keys(Ts...))]int)
    }
    m[Keys(Ts...){vs...}]++
}


// To use the metric

m := Metric(int, float64, string){m: make(map[Keys(int, float64, string)]int}
m.Add(1, 2.0, "variadic")

يبدو أنك تقوم بتعريف بنية بحقول متعددة تسمى جميعها fs . لست متأكدًا من كيفية عمل ذلك. هل هناك أي طريقة لاستخدام الإشارة إلى الحقول في هذا الهيكل بخلاف استخدام الانعكاس؟

لذا فإن السؤال هو: ماذا يمكن للمرء أن يفعل بمعلمات النوع المتغير؟ ماذا يريد المرء أن يفعل؟

هنا أعتقد أنك تستخدم معلمات نوع متغيرة لتحديد نوع مجموعة مع عدد تعسفي من الحقول.

ماذا يمكن للمرء أن يفعل أيضا؟

تهدف معلمات النوع المتغير إلى tuples حسب تعريفها إذا استخدمنا ... لها ، ولا يعني ذلك أن tuples هي حالة الاستخدام الوحيدة ، ولكن يمكن للمرء استخدامها في أي هياكل وأي وظائف.

نظرًا لوجود مكانين فقط يظهران مع معلمات النوع المتغير: البنية أو الوظيفة ، لذلك لدينا بسهولة ما هو واضح من قبل للوظائف:

func F(type Ts ...Comparable) (args ...Ts) {
    if len(args) > 1 {
        F(args[1:])
        return
    }
    // ... do stuff with args[0]
}

على سبيل المثال ، الدالة variadic Min غير ممكنة في التصميم الحالي ، ولكنها ممكنة مع معلمات النوع المتغير:

func Min(type T ...Comparable)(p1 T, pn ...T) T {
    switch l := len(pn); {
    case l > 1:
        return Min(pn[0], pn[1:]...)
    case l == 1:
        if p1 >= pn[0] { return pn[0] }
        return p1
    case l < 1:
        return p1
    }
}

لتعريف Tuple بمعلمات النوع المتغير:

type Tuple(type Ts ...Comparable) struct {
    fs ...Ts
}

عندما يتم إنشاء ثلاثة معلمات من النوع بواسطة "Ts" ، يمكن ترجمتها إلى

type Tuple(type T1, T2, T3 Comparable) struct {
    fs_1 T1
    fs_2 T2
    fs_3 T3
}

كتمثيل وسيط. لاستخدام fs ، هناك عدة طرق:

  1. المعلمات تفريغ
k := Tuple(int, float64, string){1, 2.0, "variadic"}
fs1, fs2, fs3 := k.fs // translated to fs1, fs2, fs3 := k.fs_1, k.fs_2, k.fs_3
println(fs1) // 1
println(fs2) // 2.0
println(fs3) // variadic
  1. استخدم حلقة for
for idx, f := range k.fs {
    println(idx, ": ", f)
}
// Output:
// 0: 1
// 1: 2.0
// 2: variadic
  1. استخدام الفهرس (لست متأكدًا مما إذا كان الأشخاص يرون أن هذا يعد غموضًا في المصفوفة / الشريحة أو الخريطة)
k.fs[0] = ... // translated to k.fs_1 = ...
f2 := k.fs[1] // translated to f2 := k.fs_2
  1. استخدم حزمة reflect ، تعمل بشكل أساسي مثل المصفوفة
t := Tuple(int, float64, string){1, 2.0, "variadic"}

fs := reflect.ValueOf(t).Elem().FieldByName("fs")
val := reflect.ValueOf(fs)
if val.Kind() == reflect.VariadicTypes {
    for i := 0; i < val.Len(); i++ {
        e := val.Index(i)
        switch e.Kind() {
        case reflect.Int:
            fmt.Printf("%v, ", e.Int())
        case reflect.Float64:
            fmt.Printf("%v, ", e.Float())
        case reflect.String:
            fmt.Printf("%v, ", e.String())
        }
    }
}

لا شيء جديد حقًا مقارنة باستخدام المصفوفة.

على سبيل المثال ، الدالة المتغيرة Min غير ممكنة في التصميم الحالي ، ولكنها ممكنة مع معلمات النوع المتغير:

func Min(type T ...Comparable)(p1 T, pn ...T) T {
    switch l := len(pn); {
    case l > 1:
        return Min(pn[0], pn[1:]...)
    case l == 1:
        if p1 >= pn[0] { return pn[0] }
        return p1
    case l < 1:
        return p1
    }
}

هذا لا معنى لي. تكون معلمات النوع المتغير منطقية فقط إذا كانت الأنواع يمكن أن تكون أنواعًا مختلفة. لكن استدعاء Min في قائمة الأنواع المختلفة لا معنى له. لا يدعم Go استخدام >= على قيم من أنواع مختلفة. حتى لو سمحنا بذلك بطريقة ما ، فقد يُطلب منا Min(int, string)(1, "a") . هذا ليس لديه أي نوع من الإجابة.

في حين أنه من الصحيح أن التصميم الحالي لا يسمح بـ Min من عدد متنوع من أنواع مختلفة ، فإنه يدعم استدعاء Min على عدد متنوع من القيم من نفس النوع. والتي أعتقد أنها الطريقة المعقولة الوحيدة لاستخدام Min على أي حال.

func Min(type T comparable)(s ...T) T {
    if len(s) == 0 {
        panic("Min of no elements")
    }
    r := s[0]
    for _, v := range s[1:] {
        if v < r {
            r = v
        }
    }
    return r
}

بالنسبة لبعض الأمثلة الأخرى في https://github.com/golang/go/issues/15292#issuecomment -599040081 ، من المهم ملاحظة أن شرائح ومصفوفات Go بها عناصر من نفس النوع. عند استخدام معلمات الأنواع المتغيرة ، تكون العناصر أنواعًا مختلفة. لذا فهي في الحقيقة ليست نفس الشريحة أو المصفوفة.

في حين أنه من الصحيح أن التصميم الحالي لا يسمح بـ Min من عدد متنوع من أنواع مختلفة ، إلا أنه يدعم استدعاء Min على عدد متنوع من القيم من نفس النوع. والتي أعتقد أنها الطريقة المعقولة الوحيدة لاستخدام Min على أي حال.

func Min(type T comparable)(s ...T) T {
    if len(s) == 0 {
        panic("Min of no elements")
    }
    r := s[0]
    for _, v := range s[1:] {
        if v < r {
            r = v
        }
    }
    return r
}

صحيح. كان Min مثالًا سيئًا. تمت إضافته متأخرًا ولم يكن لديه فكرة واضحة ، كما ترون من سجل تحرير التعليقات. المثال الحقيقي هو Metric الذي تجاهله.

من المهم ملاحظة أن الشرائح والمصفوفات في Go تحتوي على عناصر من نفس النوع. عند استخدام معلمات الأنواع المتغيرة ، تكون العناصر أنواعًا مختلفة. لذا فهي في الحقيقة ليست نفس الشريحة أو المصفوفة.

نرى؟ أنت هؤلاء الأشخاص الذين يرون أن هذا يعد غموضًا في المصفوفة / الشريحة أو الخريطة. كما قلت في https://github.com/golang/go/issues/15292#issuecomment -599040081 ، فإن بناء الجملة مشابه تمامًا للمصفوفة / الشريحة والخريطة ، ولكنه يصل إلى عناصر ذات أنواع مختلفة. هل هو مهم حقا؟ أم يمكن للمرء أن يثبت أن هذا غموض؟ ما هو ممكن في Go 1 هو:

m := map[interface{}]int{1: 2, "2": 3, 3.0: 4}
for i, e := range m {
    println(i, e)
}

هل يعتبر i من نفس النوع؟ على ما يبدو ، نقول إنني interface{} ، نفس النوع. ولكن هل تعبر الواجهة حقًا عن النوع؟ يجب على المبرمجين التحقق يدويًا من الأنواع الممكنة. عند استخدام for و [] وفك الحزمة ، هل يهم المستخدم حقًا أنهم لا يصلون إلى نفس النوع؟ ما هي الحجج ضد هذا؟ نفس الشيء بالنسبة لـ fs :

for idx, f := range k.fs {
    switch f.(type) { // compare to interface{}, here is zero overhead.
    case int:
        // ...
    case float64:
        // ...
    case string:
        // ...
    }
}

إذا كان عليك استخدام مفتاح نوع للوصول إلى عنصر من النوع العام المتغير ، فلا أرى الميزة. أستطيع أن أرى كيف أنه مع بعض خيارات تقنية الترجمة قد يكون أكثر فاعلية قليلاً في وقت التشغيل من استخدام interface{} . لكنني أعتقد أن الاختلاف سيكون صغيرًا إلى حد ما ، ولا أفهم لماذا سيكون أكثر أمانًا من النوع. ليس من الواضح على الفور أن الأمر يستحق جعل اللغة أكثر تعقيدًا.

لم أكن أنوي تجاهل المثال Metric ، فأنا لا أرى حتى الآن كيفية استخدام الأنواع العامة المتغيرة لتسهيل الكتابة. إذا كنت بحاجة إلى استخدام مفتاح نوع في نص Metric ، فأنا أعتقد أنني أفضل كتابة Metric2 و Metric3 .

ما هو تعريف "جعل اللغة أكثر تعقيدا"؟ نتفق جميعًا على أن الأدوية الجنسية هي شيء معقد ، ولن تجعل اللغة أبدًا أبسط من Go 1. لقد بذلت بالفعل جهودًا ضخمة في تصميمها وتنفيذها ، ولكن من غير الواضح تمامًا لمستخدمي Go: ما هو تعريف "الإحساس" كتابة ... اذهب "؟ هل هناك مقياس كمي لقياسه؟ كيف يمكن أن يجادل اقتراح اللغة بأنه لا يجعل اللغة أكثر تعقيدًا؟ في نموذج اقتراح لغة Go 2 ، تكون الأهداف واضحة تمامًا في انطباعها الأول:

  1. معالجة قضية مهمة لكثير من الناس ،
  2. تأثير ضئيل على أي شخص آخر ، و
  3. تأتي بحل واضح ومفهوم جيدًا.

ولكن ، يمكن أن تكون الأسئلة: كم عدد "عدد"؟ ماذا تعني كلمة "مهم"؟ كيف تقيس التأثير على السكان غير المعروفين؟ متى تكون المشكلة مفهومة جيدا؟ يهيمن Go على السحابة ، لكنه سيهيمن على مجالات أخرى مثل الحوسبة الرقمية العلمية (على سبيل المثال ، التعلم الآلي) ، والعرض الرسومي (على سبيل المثال ، سوق ثلاثي الأبعاد ضخم) يصبح أحد أهداف Go؟ هل المشكلة تتناسب أكثر مع "أفضّل إجراء" أ "بدلاً من" ب "في" الانتقال "ولا توجد حالة استخدام لأننا نستطيع القيام بذلك بطريقة أخرى" أو "لا يتم تقديم" ب "، وبالتالي لا نستخدم حالة الاستخدام" Go & The use " ليس هناك بعد لأن اللغة لا تستطيع التعبير عنها بسهولة "؟ ... لقد وجدت هذه الأسئلة مؤلمة ولا تنتهي ، وأحيانًا لا تستحق الإجابة عليها.

بالعودة إلى المثال Metric ، فإنه لا يظهر أي حاجة للوصول إلى الأفراد. يبدو أن مجموعة معلمات التفريغ ليست حاجة حقيقية هنا ، على الرغم من أن الحلول التي "تتطابق" مع اللغة الحالية تستخدم [ ] الفهرسة وخصم النوع يمكن أن يحل مشكلة النوع الآمن:

f2 := k.fs[1] // f2 is a float64

changkun إذا كانت هناك مقاييس واضحة وموضوعية لتحديد ميزات اللغة الجيدة والسيئة ، فلن نحتاج إلى مصممي اللغة - يمكننا فقط كتابة برنامج لتصميم لغة مثالية لنا. لكن لا يوجد - دائمًا ما يرجع ذلك إلى التفضيلات الشخصية لمجموعة من الأشخاص. وهو أيضًا ، راجع للشغل ، لماذا لا معنى للخلاف حول ما إذا كانت اللغة "جيدة" أم لا - والسؤال الوحيد هو ما إذا كنت ، شخصيًا ، تحبها. في حالة Go ، الأشخاص الذين يقررون تفضيلاتهم هم الأشخاص الموجودون في فريق Go والأشياء التي تقتبسها ليست مقاييس ، فهم يوجهون الأسئلة لمساعدتك في إقناعهم.

أنا شخصياً ، FWIW ، أشعر أن معلمات النوع المتغير تفشل في اثنين من هؤلاء الثلاثة. لا أعتقد أنهم يعالجون قضية مهمة لكثير من الناس - مثال المقاييس قد يستفيد منها ، لكن IMO فقط قليلًا وهي حالة استخدام متخصصة للغاية. ولا أعتقد أنهم يأتون بحل واضح ومفهوم جيدًا. لست على علم بأي لغة تدعم شيئًا كهذا. لكن قد أكون مخطئا. سيكون من المفيد بالتأكيد ، إذا كان لدى شخص ما أمثلة على اللغات الأخرى التي تدعم ذلك - فقد يوفر معلومات حول كيفية تنفيذها عادةً والأهم من ذلك ، كيفية استخدامها. ربما يتم استخدامه على نطاق أوسع مما أتخيل.

لدىMerovius Haskell وظائف متعددة المتغيرات كما أوضحنا في ورقة HList: http://okmij.org/ftp/Haskell/polyvariadic.html#polyvar -fn
من الواضح أن القيام بذلك في هاسكل أمر معقد ، ولكنه ليس مستحيلاً.

المثال الحافز هو كتابة الوصول الآمن إلى قاعدة البيانات حيث يمكن القيام بأشياء مثل كتابة الصلات الآمنة والإسقاطات وإعلان مخطط قاعدة البيانات باللغة.

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

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

يتم تضمين هذا النوع من الأشياء بشكل أكثر إتقانًا في C # باستخدام LINQ. يبدو أن معظم الناس يفكرون في LINQ على أنها تضيف وظائف lambda و monads إلى C # ، لكنها لن تعمل مع حالة الاستخدام الأساسية بدون المتغيرات المتعددة ، حيث لا يمكنك تحديد نوع عامل الانضمام الآمن بدون وظائف مماثلة.

أعتقد أن العوامل العلائقية مهمة. بعد العمليات الأساسية على أنواع Boolean و binary و int و float و string ، من المحتمل أن تأتي المجموعات التالية ، ثم العلاقات.

راجع للشغل ، C ++ يقدمها أيضًا على الرغم من أننا لا نريد المجادلة بأننا نريد هذه الميزة في Go نظرًا لأن XXX بها :)

أعتقد أنه سيكون من الغريب جدًا أن يكون لكل من k.fs[0] و k.fs[1] أنواع مختلفة. هذه ليست الطريقة التي تعمل بها القيم القابلة للفهرسة الأخرى في Go.

يعتمد مثال المقاييس على https://medium.com/@sameer_74231/go -experience-report-for-Genics-google-metrics-api-b019d597aaa4. أعتقد أن الكود يتطلب انعكاسًا لاسترداد القيم. أعتقد أننا إذا أردنا إضافة الأدوية الجنيسة المتنوعة إلى Go ، فيجب أن نحصل على شيء أفضل من التفكير لاسترداد القيم. وإلا فإنه لا يبدو أنه يساعد كثيرًا.

أعتقد أنه سيكون من الغريب جدًا أن يكون لكل من k.fs[0] و k.fs[1] أنواع مختلفة. هذه ليست الطريقة التي تعمل بها القيم القابلة للفهرسة الأخرى في Go.

يعتمد مثال المقاييس على https://medium.com/@sameer_74231/go -experience-report-for-Genics-google-metrics-api-b019d597aaa4. أعتقد أن الكود يتطلب انعكاسًا لاسترداد القيم. أعتقد أننا إذا أردنا إضافة الأدوية الجنيسة المتنوعة إلى Go ، فيجب أن نحصل على شيء أفضل من التفكير لاسترداد القيم. وإلا فإنه لا يبدو أنه يساعد كثيرًا.

نحن سوف. أنت تطلب شيئًا غير موجود. إذا لم يعجبك [``] ، فهناك خياران متبقيان: ( ) أو {``} ، وأرى أنه يمكنك المجادلة بأن الأقواس تبدو وكأنها استدعاء دالة و تبدو الأقواس المتعرجة مثل التهيئة المتغيرة. لا أحد يحب args.0 args.1 لأن هذا لا يشبه Go. بناء الجملة تافه.

في الواقع ، أمضيت بعض الوقت في عطلة نهاية الأسبوع في قراءة كتاب "تصميم وتطور C ++" ، وهناك العديد من الأفكار المثيرة للاهتمام حول القرارات والدروس على الرغم من كتابته في عام 1994:

_ "[...] في الماضي ، قللت من أهمية القيود في قابلية القراءة والكشف المبكر عن الأخطاء." _ ==> تصميم عقد رائع

"_ يبدو أيضًا تركيب الوظيفة للوهلة الأولى أجمل بدون كلمة رئيسية إضافية: _

T& index<class T>(vector<T>& v, int i) { /*...*/ }
int i = index(v1, 10);

_ يبدو أن هناك مشاكل مزعجة في هذه الصيغة الأبسط. انها ذكية جدا. من الصعب نسبيًا تحديد إعلان قالب في أحد البرامج لأنه [...] تم اختيار الأقواس <...> بدلاً من الأقواس لأن المستخدمين وجدوها أسهل في القراءة. [...] كما حدث ، أثبت Tom Pennello أن الأقواس كان من السهل تحليلها ، لكن هذا لا يغير الملاحظة الرئيسية بأن القراء (البشريين) يفضلون <...> _
"==> ألا يشبه func F(type T C)(v T) T ؟

_ "مع ذلك ، أعتقد أنني كنت شديد الحذر والمحافظة عندما يتعلق الأمر بتحديد ميزات النموذج. كان بإمكاني تضمين ميزات مثل [...]. لم تكن هذه الميزات لتضيف بشكل كبير إلى عبء المنفذين ، وكان من الممكن مساعدة المستخدمين ".

لماذا تشعر أنها مألوفة جدا؟

تحتاج معاملات نوع الفهرسة المتغيرة (أو tuple) منفصلة إلى فهرسة وقت التشغيل وفهرسة وقت الترجمة. أعتقد أنك قد تجادل فقط في أن عدم وجود دعم لفهرسة وقت التشغيل يمكن أن يربك المستخدمين لأنه لا يتوافق مع فهرسة وقت الترجمة. حتى بالنسبة لفهرسة وقت الترجمة ، فإن معلمة "قالب" غير من النوع مفقودة أيضًا في التصميم الحالي.

مع كل الأدلة ، يحاول الاقتراح (باستثناء تقرير التجربة) تجنب مناقشة هذه الميزة ، وأبدأ في الاعتقاد بأنه لا يتعلق بإضافة الأدوية البديلة إلى Go ولكن تمت إزالته فقط حسب التصميم.

أوافق على أن Design and Evolution of C ++ كتاب جيد ، لكن C ++ و Go لهما أهداف مختلفة. الاقتباس النهائي هناك واحد جيد ؛ لم يذكر Stroustrup تكلفة التعقيد اللغوي لمستخدمي اللغة. في Go نحاول دائمًا مراعاة هذه التكلفة. يقصد Go أن تكون لغة بسيطة. إذا أضفنا كل ميزة من شأنها أن تساعد المستخدمين ، فلن يكون الأمر بسيطًا. لأن C ++ ليست بسيطة.

مع كل الأدلة ، يحاول الاقتراح (باستثناء تقرير التجربة) تجنب مناقشة هذه الميزة ، وأبدأ في الاعتقاد بأنه لا يتعلق بإضافة الأدوية البديلة إلى Go ولكن تمت إزالته فقط حسب التصميم.

أنا آسف ، لا أعرف ما تعنيه هنا.

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

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

لم يذكر Stroustrup تكلفة التعقيد اللغوي لمستخدمي اللغة. في Go نحاول دائمًا مراعاة هذه التكلفة. يقصد Go أن تكون لغة بسيطة. إذا أضفنا كل ميزة من شأنها أن تساعد المستخدمين ، فلن يكون الأمر بسيطًا. لأن C ++ ليست بسيطة.

ليس صحيحًا IMO. يجب على المرء أن يلاحظ أن C ++ هو أول ممارس يتقدم بالأدوية (Well ML هي اللغة الأولى). من ما قرأته من الكتاب ، تلقيت رسالة مفادها أن C ++ كان يُقصد بها أن تكون لغة بسيطة (لا تقدم الأدوية الجنيسة في البداية ، حلقة التجربة - التبسيط - الشحن لتصميم اللغة ، نفس التاريخ). كان لدى C ++ أيضًا مرحلة تجميد ميزة لعدة سنوات وهو ما لدينا في Go "وعد التوافق". لكنه يخرج قليلاً عن السيطرة بمرور الوقت بسبب العديد من الأسباب المعقولة ، والتي ليس من الواضح أن Go إذا كان يمسك بالمسار القديم لـ C ++ بعد إصدار الأدوية الجنيسة.

هناك بالتأكيد وقت لإضافة الأدوية المتنوعة ، إذا أمكن ، لاحقًا.

نفس الشعور بالنسبة لي. الأدوية الجنيسة المتغيرة مفقودة أيضًا في الإصدار القياسي الأول من القوالب.

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

أنا أتفهم مخاوفك. ولكن تم حل المشكلة بشكل أساسي ولكن تحتاج فقط إلى ترجمتها بشكل صحيح إلى Go (وأعتقد أن لا أحد يحب كلمة "ترجم"). ما قرأته من اقتراح الأدوية الجنيسة التاريخي الخاص بك ، فهم يتبعون بشكل أساسي ما فشل في اقتراح C ++ المبكر وتعرضوا للخطر الذي ندم عليه Stroustrup. أنا مهتم بالحجج المضادة الخاصة بك حول هذا.

سيتعين علينا الاختلاف حول أهداف C ++. ربما كانت الأهداف الأصلية أكثر تشابهًا ، ولكن بالنظر إلى C ++ اليوم ، أعتقد أنه من الواضح أن أهدافهم مختلفة تمامًا عن أهداف Go ، وأعتقد أن هذا هو الحال لمدة 25 عامًا على الأقل.

عند كتابة مقترحات مختلفة لإضافة أدوية عامة إلى Go ، نظرت بالطبع في كيفية عمل قوالب C ++ ، بالإضافة إلى النظر في العديد من اللغات الأخرى (بعد كل شيء ، لم تخترع C ++ الأدوية الجنيسة). لم أنظر إلى ما ندمت عليه Stroustrup ، لذلك إذا جئنا إلى نفس المكان ، فهذا رائع. تفكيري هو أن الأدوية الجنيسة في Go تشبه الأدوية الجنيسة في Ada أو D أكثر من كونها مثل C ++. حتى اليوم ، لا يوجد لدى C ++ عقود ، والتي يسمونها مفاهيم ولكن لم يتم إضافتها إلى اللغة بعد. أيضًا ، تسمح C ++ عن قصد بالبرمجة المعقدة في وقت التجميع ، وفي الواقع ، تعد قوالب C ++ هي نفسها لغة تورينج كاملة (على الرغم من أنني لا أعرف ما إذا كان ذلك مقصودًا). لطالما اعتبرت أن هذا شيء يجب تجنبه لـ Go ، حيث أن التعقيد شديد (على الرغم من أنه أكثر تعقيدًا في C ++ مما سيكون عليه في Go بسبب التحميل الزائد للطريقة والقرار ، وهو ما لا يحتويه Go).

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

func Append(type T)(slice []T, elems ...T) []T {...}
func Copy(type T)(dst, src []T) int {...}
func Delete(type K, V)(m map[K]V, k K) {...}
func Make(type T, I Integer(I))(siz ...I) T {...}
func New(type T)() *T {...}
func Close(type T)(c chan<- T) {...}
func Panic(type T)(v T) {...}
func Recover(type T)() T {...}
func Print(type ...T)(args ...T) {...}
func Println(type ...T)(args ...T) {...}

هل سيرحلون في Go2؟ كيف يمكن لـ Go 2 التعامل مع مثل هذا التأثير الهائل على قاعدة بيانات Go 1 الحالية؟ يبدو أن هذه أسئلة مفتوحة.

علاوة على ذلك ، فإن هذين هما مميزان بعض الشيء:

func Len(type T C)(t T) int {...}
func Cap(type T C)(t T) int {...}

كيفية تنفيذ مثل هذا العقد C مع التصميم الحالي ، بحيث لا يمكن أن تكون معلمة النوع سوى شريحة عامة []Ts ، الخريطة map[Tk]Tv ، والقناة chan Tc حيث T Ts Tk Tv Tc مختلفة؟

changkun لا أعتقد أن عبارة "يمكن تنفيذها باستخدام الأدوية الجنيسة" هي سبب مقنع لإزالتها. وقد ذكرت سببًا واضحًا وقويًا لعدم إزالتها. لذلك لا أعتقد أنهم سيكونون كذلك. أعتقد أن هذا يجعل بقية الأسئلة عفا عليها الزمن.

changkun لا أعتقد أن عبارة "يمكن تنفيذها باستخدام الأدوية الجنيسة" هي سبب مقنع لإزالتها. وقد ذكرت سببًا واضحًا وقويًا لعدم إزالتها.

نعم ، أوافق على أنه ليس مقنعًا لإزالتها ، ولهذا قلتها صراحة. ومع ذلك ، فإن الاحتفاظ بها جنبًا إلى جنب مع الأدوية الجنيسة "ينتهك" الفلسفة الحالية لـ Go ، والتي تعتبر ميزات اللغة متعامدة. التوافق هو الشاغل الأكبر ، ولكن إضافة العقود من المرجح أن تقضي على الكود "القديم" الضخم الحالي.

لذلك لا أعتقد أنهم سيكونون كذلك. أعتقد أن هذا يجعل بقية الأسئلة عفا عليها الزمن.

دعنا نحاول ألا نتجاهل السؤال ونعتبره حالة استخدام حقيقية للعقود. إذا توصل المرء إلى متطلبات مماثلة ، فكيف يمكننا تنفيذه بالتصميم الحالي؟

من الواضح أننا لن نتخلص من الوظائف الحالية المُعلنة مسبقًا.

بينما من الممكن كتابة توقيع دالة ذات معلمات لـ delete ، close ، panic ، recover ، print ، و println ، لا أعتقد أنه من الممكن تنفيذها دون الاعتماد على وظائف السحر الداخلية.

هناك إصدارات جزئية من Append و Copy على https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-contracts.md#append. لم يكتمل ، لأن append و copy لهما حالات خاصة للوسيطة الثانية من النوع string ، والتي لا تدعمها مسودة التصميم الحالية.

لاحظ أن توقيع Make أعلاه غير صالح وفقًا لمسودة التصميم الحالية. New ليس تمامًا مثل new ، لكنه قريب بما فيه الكفاية.

مع مسودة التصميم الحالية Len و Cap يجب أن تأخذ وسيطة من النوع interface{} ، وعلى هذا النحو لن تكون compile-time-type-safe.

https://go-review.googlesource.com/c/go/+/187317

من فضلك لا تستخدم امتدادات الملفات .go2 ، لدينا وحدات للقيام بهذا النوع من الإصدار؟ أتفهم ما إذا كنت تفعل ذلك كحل مؤقت لتسهيل الحياة أثناء تجربة العقود ولكن يرجى التأكد من أن الملف go.mod سيهتم في النهاية بخلط go pacakges بدون تحتاج إلى ملحقات ملف .go2 . ستكون ضربة لمطوري الوحدات الذين يحاولون جاهدين التأكد من أن الوحدات تعمل بأفضل ما يمكن. استخدام امتدادات ملف .go2 يشبه القول ، كلا ، أنا لا أهتم بأشياء الوحدة الخاصة بك ستفعل ذلك بطريقتي على أي حال لأنني لا أريد ديناصور الذي يبلغ عمره 10 سنوات go لكسر مترجم .

ملفات gertcuykens .go2 مخصصة للتجربة فقط ؛ لن يتم استخدامها عندما تهبط الأدوية الجنيسة في المترجم.

(سأخفي تعليقاتنا لأنها لا تضيف حقًا إلى المناقشة وهي طويلة بما يكفي كما هي.)

لقد قمت مؤخرًا باستكشاف بناء جملة عام جديد في لغة K التي صممتها ، لأن K استعارت الكثير من القواعد من Go ، لذلك قد يكون لهذه القواعد العامة أيضًا بعض القيمة المرجعية لـ Go.

مشكلة identifier<T> هي أنها تتعارض مع عوامل المقارنة ومعاملات البت ، لذلك أنا لا أتفق مع هذا التصميم.

يتمتع Scala's identifier[T] بمظهر وإحساس أفضل من التصميم السابق ، ولكن بعد حل التعارض أعلاه ، لديه تعارض جديد مع تصميم الفهرس identifier[index] .
لهذا السبب ، تم تغيير تصميم فهرس Scala إلى identifier(index) . هذا لا يعمل بشكل جيد مع اللغات التي تستخدم بالفعل [] كفهرس.

في مسودة Go ، أُعلن أن الأدوية الجنيسة تستخدم (type T) ، والتي لن تسبب تعارضات ، لأن type هي كلمة أساسية ، لكن المترجم لا يزال بحاجة إلى مزيد من الحكم عندما يتم استدعاؤه لحل identifier(type)(params) . على الرغم من أنها أفضل من الحلول المذكورة أعلاه ، إلا أنها لا ترضيني.

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

ماذا لو وضعنا المعرف والعام ككل ووضعهما في [] معًا؟
يمكننا الحصول على [identifier T] . لا يتعارض هذا التصميم مع الفهرس ، لأنه يجب أن يحتوي على عنصرين على الأقل ، مفصولة بمسافات.
عندما يكون هناك العديد من الأدوية ، يمكننا كتابة [identifier T V] مثل هذا ، ولن يتعارض مع التصميم الحالي.

باستبدال هذا التصميم بـ Go ، يمكننا الحصول على المثال التالي.
على سبيل المثال

type [Item T] struct {
    Value T
}

func (it [Item T]) Print() {
    println(it.Value)
}

func [TestGenerics T V]() {
    var a = [Item T]{}
    a.Print()
    var b = [Item V]{}
    b.Print()
}

func main() {
    [TestGenerics int string]()
}

هذا يبدو واضحا جدا.

فائدة أخرى لاستخدام [] هي أنه يحتوي على بعض الميراث من تصميم Slice and Map الأصلي لـ Go ، ولن يسبب إحساسًا بالتجزئة.

[]int  ->  [slice int]

map[string]int  ->  [map string int]

يمكننا تقديم مثال أكثر تعقيدًا

var a map[int][]map[string]map[string][]string

var b [map int [slice [map string [map string [slice string]]]]]

لا يزال هذا المثال له تأثير واضح نسبيًا ، وفي نفس الوقت يكون له تأثير ضئيل على التجميع.

لقد قمت بتنفيذ واختبار هذا التصميم في K وهو يعمل بشكل جيد.

أعتقد أن هذا التصميم له قيمة مرجعية معينة وقد يستحق المناقشة.

لقد قمت مؤخرًا باستكشاف بناء جملة عام جديد في لغة K التي صممتها ، لأن K استعارت الكثير من القواعد من Go ، لذلك قد يكون لهذه القواعد العامة أيضًا بعض القيمة المرجعية لـ Go.

مشكلة identifier<T> هي أنها تتعارض مع عوامل المقارنة ومعاملات البت ، لذلك أنا لا أتفق مع هذا التصميم.

يتمتع Scala's identifier[T] بمظهر وإحساس أفضل من التصميم السابق ، ولكن بعد حل التعارض أعلاه ، لديه تعارض جديد مع تصميم الفهرس identifier[index] .
لهذا السبب ، تم تغيير تصميم فهرس Scala إلى identifier(index) . هذا لا يعمل بشكل جيد مع اللغات التي تستخدم بالفعل [] كفهرس.

في مسودة Go ، أُعلن أن الأدوية الجنيسة تستخدم (type T) ، والتي لن تسبب تعارضات ، لأن type هي كلمة أساسية ، لكن المترجم لا يزال بحاجة إلى مزيد من الحكم عندما يتم استدعاؤه لحل identifier(type)(params) . على الرغم من أنها أفضل من الحلول المذكورة أعلاه ، إلا أنها لا ترضيني.

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

ماذا لو وضعنا المعرف والعام ككل ووضعهما في [] معًا؟
يمكننا الحصول على [identifier T] . لا يتعارض هذا التصميم مع الفهرس ، لأنه يجب أن يحتوي على عنصرين على الأقل ، مفصولة بمسافات.
عندما يكون هناك العديد من الأدوية ، يمكننا كتابة [identifier T V] مثل هذا ، ولن يتعارض مع التصميم الحالي.

باستبدال هذا التصميم بـ Go ، يمكننا الحصول على المثال التالي.
على سبيل المثال

type [Item T] struct {
    Value T
}

func (it [Item T]) Print() {
    println(it.Value)
}

func [TestGenerics T V]() {
    var a = [Item T]{}
    a.Print()
    var b = [Item V]{}
    b.Print()
}

func main() {
    [TestGenerics int string]()
}

هذا يبدو واضحا جدا.

فائدة أخرى لاستخدام [] هي أنه يحتوي على بعض الميراث من تصميم Slice and Map الأصلي لـ Go ، ولن يسبب إحساسًا بالتجزئة.

[]int  ->  [slice int]

map[string]int  ->  [map string int]

يمكننا تقديم مثال أكثر تعقيدًا

var a map[int][]map[string]map[string][]string

var b [map int [slice [map string [map string [slice string]]]]]

لا يزال هذا المثال له تأثير واضح نسبيًا ، وفي نفس الوقت يكون له تأثير ضئيل على التجميع.

لقد قمت بتنفيذ واختبار هذا التصميم في K وهو يعمل بشكل جيد.

أعتقد أن هذا التصميم له قيمة مرجعية معينة وقد يستحق المناقشة.

باهر

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

لقد كتبت سلسلة من التغييرات المقترحة لمعالجة هذا:

النقاط الرئيسية هي:

  • أسلوب استدعاء / تأكيد النوع لإعلان العقد
  • "العقد الفارغ"
  • محددات غير بارينثالية

في ظل خطر استباق المقال ، سأقدم بضع أجزاء من تفسير بناء الجملة بدون تفسير ، محولة من عينات في مسودة تصميم العقود الحالية. لاحظ أن شكل المحددات F«T» توضيحي وليس إلزاميًا ؛ انظر الكتابة للحصول على التفاصيل.

type List«type Element contract{}» struct {
    next *List«Element»
    val  Element
}

و

contract viaStrings«To, From» {
    To.Set(string)
    From.String() string
}

func SetViaStrings«type To, From viaStrings»(s []From) []To {
    r := make([]To, len(s))
    for i, v := range s {
        r[i].Set(v.String())
    }
    return r
}

و

func Keys«type K comparable, V contract{}»(m map[K]V) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

k := maps.Keys(map[int]int{1:2, 2:4})

و

contract Numeric«T» {
    T.(int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        complex64, complex128)
}

func DotProduct«type T Numeric»(s1, s2 []T) T {
    if len(s1) != len(s2) {
        panic("DotProduct: slices of unequal length")
    }
    var r T
    for i := range s1 {
        r += s1[i] * s2[i]
    }
    return r
}

بدون تغيير العقود حقًا ، سيكون هذا أكثر قابلية للقراءة بالنسبة لي كمطور Go. أشعر أيضًا بثقة أكبر في تدريس هذا النموذج لشخص يتعلم Go (وإن كان متأخرًا في المناهج الدراسية).

ianlancetaylor بناءً على تعليقك على https://github.com/golang/go/issues/36533#issuecomment -579484523 أنشر في هذا الموضوع بدلاً من بدء إصدار جديد. إنه مدرج أيضًا في صفحة تعليقات Generics . لست متأكدًا مما إذا كنت بحاجة إلى القيام بأي شيء آخر "للنظر فيه رسميًا" (أي مجموعة مراجعة اقتراح Go 2 ؟) أو ما إذا كان لا يزال يتم جمع التعليقات بنشاط.

من مسودة تصميم العقود:

لماذا لا تستخدم بناء الجملة F<T> مثل C ++ و Java؟
عند تحليل التعليمات البرمجية داخل دالة ، مثل v := F<T> ، عند رؤية < ، يكون الأمر غامضًا سواء كنا نشهد إنشاء مثيل أو تعبير باستخدام عامل التشغيل < . حل هذا يتطلب نظرة غير محدودة بشكل فعال. بشكل عام ، نسعى جاهدين لإبقاء المحلل اللغوي Go بسيطًا.

لا يتعارض بشكل خاص مع آخر مشاركة لي: Angle Brace Delimiters for Go Contracts

فقط بعض الأفكار حول كيفية الالتفاف على هذه النقطة من المحلل اللغوي. عينات الزوجين:

// Lifted from the design draft
func New<type K, V>(compare func(K, K) int) *Map<K, V> {
    return &Map{<K, V> compare: compare}
}

// ...

func (m *Map<K, V>) InOrder() *Iterator<K, V> {
    sender, receiver := chans.Ranger(<keyValue<K, V>>)
    var f func(*node<K, V>) bool
    f = func(n *node<K, V>) bool {
        if n == nil {
            return true
        }
        // Stop sending values if sender.Send returns false,
        // meaning that nothing is listening at the receiver end.
        return f(n.left) &&
            sender.Send(keyValue{<K, V> n.key, n.val}) &&
            f(n.right)
    }
    go func() {
        f(m.root)
        sender.Close()
    }()
    return &Iterator{receiver}
}

// ...

بشكل أساسي ، مجرد موضع مختلف لمعلمات النوع في السيناريوهات حيث يمكن أن يكون < غامضًا.

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

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

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

هل من الجيد استخدام [] للأنواع العامة ، واستخدام () للوظائف العامة؟ سيكون هذا أكثر اتساقًا مع الأدوية الأساسية الحالية.

هل يمكن للمجتمع التصويت على ذلك؟ أنا شخصياً أفضل _ أي شيء _ على إضافة المزيد من الأقواس ، فمن الصعب بالفعل قراءة بعض تعريفات الوظائف للإغلاق وما إلى ذلك ، وهذا يضيف المزيد من الفوضى

لا أعتقد أن التصويت طريقة جيدة لتصميم لغة. خاصة مع وجود مجموعة كبيرة بشكل لا يصدق من الناخبين المؤهلين (ربما من المستحيل) تحديدها.

أثق بمصممي Go ومجتمعهم في الالتقاء حول أفضل الحلول و
لذلك لم أشعر بالحاجة إلى التفكير في أي شيء في هذه المحادثة.
ومع ذلك ، كان علي فقط أن أقول مدى سعادتي بشكل غير متوقع ببرنامج
اقتراح بناء جملة F «T».

(أقواس Unicode الأخرى:
https://unicode-search.net/unicode-namesearch.pl؟term=BRACKET.)

هتافات،

  • بوب

يوم الجمعة ، 1 مايو ، 2020 الساعة 7:43 مساءً ، كتب Matt Mc [email protected] :

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

لقد كتبت سلسلة من التغييرات المقترحة لمعالجة هذا:

النقاط الرئيسية هي:

  • أسلوب استدعاء / تأكيد النوع لإعلان العقد
  • "العقد الفارغ"
  • محددات غير بارينثالية

مع المخاطرة باستباق المقال ، سأقدم بعض القطع غير المدعومة
بناء الجملة ، محولة من العينات في مسودة تصميم العقود الحالية. ملحوظة
أن شكل F «T» من المحددات هو توضيح وليس إلزامي ؛ نرى
الكتابة للحصول على التفاصيل.

اكتب قائمة «نوع عقد عنصر {}» هيكل {
التالي * قائمة «العنصر»
عنصر فال
}

و

عقد عبر String «إلى ، From» {
To.Set (سلسلة نصية)
سلسلة From.String ()
}
func SetViaStrings «اكتب إلى ، من عبر السلاسل» (ق [] من) [] إلى {
r: = جعل ([] إلى ، لين (ق))
بالنسبة إلى i ، v: = النطاق s {
r [i] .Set (v.String ())
}
عودة ص
}

و

func Keys «نوع K قابل للمقارنة ، عقد V {}» (m map [K] V) [] K {
ص: = جعل ([] ك ، 0 ، لين (م))
بالنسبة إلى k: = النطاق m {
ص = إلحاق (ص ، ك)
}
عودة ص
}
k: = maps.Keys (map [int] int {1: 2، 2: 4})

و

عقد رقمي «T» {
T. (int، int8، int16، int32، int64،
uint ، uint8 ، uint16 ، uint32 ، uint64 ، uintptr ،
float32 ، float64 ،
مجمع 64 ، مجمع 128)
}
func DotProduct «type T Numeric» (s1، s2 [] T) T {
إذا كان len (s1)! = len (s2) {
الذعر ("DotProduct: شرائح غير متساوية الطول")
}
فار ص ت
بالنسبة إلى i: = النطاق s1 {
ص + = s1 [i] * s2 [i]
}
عودة ص
}

بدون تغيير العقود في الواقع ، هذا أكثر من ذلك بكثير
يمكن قراءتها بالنسبة لي كمطور Go. كما أنني أشعر بثقة أكبر
تعليم هذا النموذج لشخص يتعلم Go (وإن كان متأخرًا في
منهاج دراسي).

ianlancetaylor https://github.com/ianlancetaylor بناءً على تعليقك
في # 36533 (تعليق)
https://github.com/golang/go/issues/36533#issuecomment-579484523 أنا
النشر في هذا الموضوع بدلاً من بدء إصدار جديد. إنه مدرج أيضًا
في صفحة تعليقات Generics
https://github.com/golang/go/wiki/Go2Generics لست متأكدا إذا كنت
بحاجة إلى فعل أي شيء آخر حتى يتم اعتباره "رسميًا" (على سبيل المثال ، Go 2
مجموعة مراجعة الاقتراح https://github.com/golang/go/issues/33892 ؟) أو إذا
لا يزال يتم جمع التعليقات بنشاط.

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-622657596 ، أو
إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AACQ2NJRBNLLDGY2XGCCQCLRPOCEHANCNFSM4CA35RXQ
.

كلنا نريد أفضل بناء جملة ممكن لـ Go. تستخدم مسودة التصميم الأقواس لأنها عملت مع بقية Go دون التسبب في غموض تحليلي كبير. لقد بقينا معهم لأنهم كانوا أفضل حل في أذهاننا في ذلك الوقت ولأن هناك سمكة أكبر للقلي. حتى الآن صمدوا (الأقواس) بشكل جيد.

في نهاية اليوم ، إذا تم العثور على تدوين أفضل بكثير ، فمن السهل جدًا تغييره طالما لم يكن لدينا ضمان توافق للالتزام به (يتم تعديل المحلل اللغوي بشكل تافه ، ويمكن تحويل أي نص برمجي بسهولة مع gofmt).

ianlancetaylor شكرًا على الرد ، إنه موضع تقدير.

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

إذا كان من الصعب قراءة النحو ، (من الصعب معرفة ما يحدث في لمحة) فهل يتناسب حقًا بسهولة مع اللغة الحالية؟ هذا هو المكان الذي أعتقد أن الموقف فيه قصور.

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

الليلة الماضية ، عن طريق الصدفة ، واجهت بناء جملة القالب لـ D والذي يشبه بشكل مدهش في بعض النواحي:

template Square(T) {
    T Square(T t) {
        return t * t;
    }
}

writefln("The square of %s is %s", 3, Square!(int)(3));

template TCopy(T) {
    void copy(out T to, T from) {
        to = from;
    }
}

int i;
TCopy!(int).copy(i, 3);

هناك نوعان من الاختلافات الرئيسية التي أراها:

  1. لديهم ! كمعامل إنشاء لتوظيف القوالب.
  2. أسلوب الإعلان الخاص بهم (لا توجد قيم إرجاع متعددة ، طرق متداخلة في الفئات) يعني أن هناك عددًا أقل من الأقواس في الكود العادي ، لذا فإن استخدام الأقواس لمعلمات النوع لا يؤدي إلى نفس الغموض البصري.

عامل تجسيد

عند استخدام العقود ، يكون الغموض البصري الأساسي بين إنشاء مثيل واستدعاء وظيفة (أو تحويل نوع ، أو ...؟). جزء من سبب هذه المشكلة هو أن عمليات إنشاء التطبيقات عبارة عن وقت ترجمة واستدعاءات الوظائف هي وقت تشغيل. يحتوي Go على الكثير من القرائن المرئية التي تخبر القارئ عن المعسكر الذي تنتمي إليه كل جملة ، لكن البنية الجديدة تشوش هذه ، لذلك ليس من الواضح إذا كنت تبحث في الأنواع أو تدفق البرامج.

مثال واحد مفتعل:

// Instantiation with unexported types and then function call,
// or chained method call?
a := draw(square, ellipse)(canvas, color)

اقتراح: استخدم عامل إنشاء مثيل لتحديد معلمات النوع. يبدو أن ! الذي يستخدمه D مقبول تمامًا. بعض نماذج بناء الجملة:

// Lifted from the design draft
func New(type K, V)(compare func(K, K) int) *Map!(K, V) {
    return &Map!(K, V){compare: compare}
}

// ...

func (m *Map(K, V)) InOrder() *Iterator!(K, V) {
    sender, receiver := chans.Ranger!(keyValue!(K, V))()
    var f func(*node!(K, V)) bool
    f = func(n *node!(K, V)) bool {
        if n == nil {
            return true
        }
        // Stop sending values if sender.Send returns false,
        // meaning that nothing is listening at the receiver end.
        return f(n.left) &&
            sender.Send(keyValue!(K, V){n.key, n.val}) &&
            f(n.right)
    }
    go func() {
        f(m.root)
        sender.Close()
    }()
    return &Iterator{receiver}
}

// ...

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

نمط الإعلان

عند التصريح عن الأنواع والوظائف والطرق ، يقل "وقت التشغيل أو وقت الترجمة؟" مشكلة. يرى جوفر سطرًا يبدأ بـ type أو func ويعرف أنه ينظر إلى إعلان ، وليس سلوك البرنامج.

ومع ذلك ، لا تزال بعض الغموض البصري موجودًا:

// Type-parameterized function,
// or function with multiple return values?
func Draw(cvs canvas, t tool)(canvas, tool) {
    // ...
}
func Draw(type canvas, tool)(cvs canvas, t tool) {
    // ...
}

// Type-parameterized struct, or function call?
func Set(elem constructible) rect {
    // ...
}
type Set(type Elem comparable) struct{
    // ...
}

// Method call, or type-parameterized function?
func Map(type Element)(s []Element, f func(Element) Element) (results []Element) {
    // ...
}
func (t Element) Map(s []Element, f func(Element) Element) (results []Element) {
    // ...
}

أفكار:

  • أعتقد أن هذه القضايا أقل أهمية من مشكلة إنشاء مثيل.
  • سيكون الحل الأكثر وضوحًا هو تغيير المحددات المستخدمة في وسيطات النوع.
  • من المحتمل أن يؤدي وضع نوع آخر من المشغل أو الشخصية إلى هناك (قد تضيع ! ، ماذا عن # ؟) يمكن أن يزيل الغموض عن الأشياء.

تحرير: griesemer شكرا للتوضيح الإضافي!

شكرا. فقط لطرح السؤال الطبيعي: لماذا من المهم معرفة ما إذا كان يتم تقييم مكالمة معينة في وقت التشغيل أو في وقت التجميع؟ لماذا هذا هو السؤال الرئيسي؟

tooolbox

// Instantiation with unexported types and then function call,
// or chained method call?
a := draw(square, ellipse)(canvas, color)

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

في الواقع ، يعد استخدام () لمعلمات النوع أمرًا منطقيًا ، حيث يبدو أنك تستدعي دالة تقوم بإرجاع دالة - وهذا صحيح إلى حد ما. الفرق هو أن الوظيفة الأولى هي قبول الأنواع ، والتي عادة ما تكون كبيرة ، أو معروفة جيدًا.

في هذه المرحلة ، من الأهمية بمكان معرفة أبعاد السقيفة ، وليس لونها.

لا أعتقد أن ما يتحدث عنه tooolbox هو حقًا اختلاف بين وقت الترجمة ووقت التشغيل. نعم ، هذا اختلاف واحد ، لكنه ليس الاختلاف المهم. المهم هو: هل هذا استدعاء دالة أم تصريح نوع؟ تريد أن تعرف لأنهم يتصرفون بشكل مختلف ولا تريد أن تضطر إلى استنتاج ما إذا كان بعض التعبير يقوم باستدعائين وظيفيين أم واحد ، لأن هذا فرق كبير. أي تعبير مثل a := draw(square, ellipse)(canvas, color) غامض بدون القيام بعمل لفحص البيئة المحيطة.

من المهم أن تكون قادرًا على التحليل البصري لتدفق التحكم في البرنامج. أعتقد أن Go كان مثالًا رائعًا على ذلك.

شكرا. فقط لطرح السؤال الطبيعي: لماذا من المهم معرفة ما إذا كان يتم تقييم مكالمة معينة في وقت التشغيل أو في وقت التجميع؟ لماذا هذا هو السؤال الرئيسي؟

آسف ، يبدو أنني أخطأت في اتصالي. هذه هي النقطة الأساسية التي كنت أحاول تجاوزها:

ليس من الواضح ما إذا كنت تبحث في الأنواع أو تدفق البرنامج

(في الوقت الحالي ، يتم فرز أحدهما أثناء التجميع والآخر يحدث في وقت التشغيل ، ولكن هذه ... خصائص ، وليست النقطة الرئيسية ، التي التقطتهاinfogulch بحق - شكرًا!)


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

// Example from the Contracts draft
Print(int)([]int{1, 2, 3})

// New naming that communicates behavior and intent
MakePrintFunc(int)([]int{1, 2, 3}) // Chained function call, great!

هناك ، تبدو في الواقع كدالة تُرجع دالة ؛ أعتقد أن هذا مقروء تمامًا.

هناك طريقة أخرى للقيام بذلك وهي لصق كل شيء بـ Type ، لذلك يتضح من الاسم أنه عند "استدعاء" الوظيفة ، فإنك تحصل على نوع. بخلاف ذلك ، ليس من الواضح (على سبيل المثال) أن Pair(...) ينتج نوعًا هيكليًا وليس هيكلًا. ولكن إذا كانت هذه الاتفاقية سارية ، فسيصبح هذا الرمز واضحًا: a := drawType(square, ellipse)(canvas, color)

(أدرك أن سابقة هي اتفاقية "-er" للواجهات.)

لاحظ أنني لا أؤيد بشكل خاص ما ورد أعلاه كحل ، أنا فقط أوضح كيف أعتقد أن "الأدوية العامة كوظائف" لا يتم التعبير عنها بشكل كامل ولا لبس فيه من خلال البنية الحالية.


مرة أخرى ، لخصinfogulch وجهة نظري جيدًا. أنا أؤيد التمايز البصري بين حجج النوع بحيث يكون من الواضح أنها جزء من النوع .

ربما يتم تحسين الجزء المرئي منه عن طريق إبراز بنية المحرر.

لا أعرف الكثير عن موزعي البرامج وكيف لا يمكنك القيام بالكثير من النظرة إلى الأمام.

من وجهة نظر المستخدمين ، لا أرغب في رؤية شخصية أخرى في الكود الخاص بي ، لذلك لن يحصل «» على دعمي (لم أجدهم على لوحة المفاتيح!).

ومع ذلك ، فإن رؤية الأقواس المستديرة متبوعة بأقواس مستديرة لا ترضي العين أيضًا.

ماذا عن بسيطة باستخدام الأقواس المتعرجة؟

a := draw{square, ellipse}(canvas, color)

في Print(int)([]int{1,2,3}) الاختلاف السلوكي الوحيد هو "وقت الترجمة مقابل وقت التشغيل" ، على الرغم من ذلك. نعم ، سيؤكد MakePrintFunc بدلاً من Print هذا التشابه أكثر ، لكن ... أليست هذه حجة على عدم استخدام MakePrintFunc ؟ لأنه في الواقع يخفي الاختلاف السلوكي الحقيقي.

FWIW ، إذا كان هناك أي شيء يبدو أنك تقدم حجة لاستخدام فواصل مختلفة للوظائف البارامترية وأنواع المعاملات. نظرًا لأن Print(int) يمكن اعتباره مكافئًا لوظيفة تُرجع دالة (يتم تقييمها في وقت الترجمة) ، بينما لا يمكن اعتبارها Pair(int, string) - إنها وظيفة تُرجع نوعًا . Print(int) هو في الواقع تعبير صالح يتم تقييمه إلى قيمة func -value ، بينما Pair(int, string) ليس تعبيرًا صالحًا ، إنه نوع من المواصفات. لذا فإن الاختلاف الحقيقي في الاستخدام ليس "وظائف عامة مقابل وظائف غير عامة" إنه "وظائف عامة مقابل أنواع عامة". ومن هذا POV ، أعتقد أن هناك حجة قوية لاستخدام () على الأقل للوظائف البارامترية على أي حال ، لأنها تؤكد على طبيعة الدوال البارامترية لتمثيل القيم فعليًا - وربما يجب علينا استخدام <> للأنواع البارامترية.

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

لا أعتقد أن ما يتحدث عنه tooolbox هو حقًا اختلاف بين وقت الترجمة ووقت التشغيل. نعم ، هذا اختلاف واحد ، لكنه ليس الاختلاف المهم. المهم هو: هل هذا استدعاء دالة أم تصريح نوع؟ أنت تريد أن تعرف لأنهم يتصرفون بشكل مختلف ولا تريد أن تضطر إلى استنتاج ما إذا كان بعض التعبير يقوم باستدعائين للدالة أم واحد ، لأن هذا فرق كبير. أي تعبير مثل a := draw(square, ellipse)(canvas, color) غامض بدون القيام بعمل لفحص البيئة المحيطة.

من المهم أن تكون قادرًا على التحليل البصري لتدفق التحكم في البرنامج. أعتقد أن Go كان مثالًا رائعًا على ذلك.

سيكون من السهل جدًا رؤية إقرارات النوع ، نظرًا لأنها تبدأ جميعها بالكلمة الأساسية type . من الواضح أن مثالك ليس واحداً منهم.

ربما يتم تحسين الجزء المرئي منه عن طريق إبراز بنية المحرر.

أعتقد ، من الناحية المثالية ، يجب أن يكون بناء الجملة واضحًا بغض النظر عن لونه. كان هذا هو الحال بالنسبة لـ Go ، ولا أعتقد أنه سيكون من الجيد الخروج من هذا المعيار.

ماذا عن بسيطة باستخدام الأقواس المتعرجة؟

أعتقد أن هذا يتعارض مع الأسف مع بنية حرفية.

في Print(int)([]int{1,2,3}) الاختلاف السلوكي الوحيد هو "وقت الترجمة مقابل وقت التشغيل" ، على الرغم من ذلك. نعم ، سيؤكد MakePrintFunc بدلاً من Print هذا التشابه أكثر ، لكن ... أليست هذه حجة على عدم استخدام MakePrintFunc ؟ لأنه في الواقع يخفي الاختلاف السلوكي الحقيقي.

حسنًا ، على سبيل المثال ، هذا هو السبب في أنني سأدعم Print!(int)([]int{1,2,3}) على MakePrintFunc(int)([]int{1,2,3}) . من الواضح أن شيئًا فريدًا يحدث.

ولكن مرة أخرى ، السؤال الذي طرحهianlancetaylor سابقًا: لماذا يهم إذا كان نوع إنشاء مثيل / وظيفة إرجاع وظيفة هو وقت التجميع مقابل وقت التشغيل؟

بالتفكير في الأمر ، إذا كتبت بعض استدعاءات الوظائف وكان المترجم قادرًا على تحسينها وحساب نتيجتها في وقت الترجمة ، فستكون سعيدًا لاكتساب الأداء! بدلا من ذلك ، فإن الجانب المهم هو ما تفعله الكود ، ما هو السلوك؟ يجب أن يكون واضحا في لمحة.

عندما أرى Print(...) غريزتي الأولى هي "هذه مكالمة وظيفية تكتب إلى مكان ما". لا ينقل "هذا سيعيد وظيفة". في رأيي ، أي من هؤلاء أفضل لأنه يمكنه توصيل السلوك والنية:

  • MakePrintFunc(...)
  • Print!(...)
  • Print<...>

بعبارة أخرى ، هذا الجزء من الكود "مراجع" أو بطريقة ما "يعطيني" وظيفة يمكن الآن استدعاؤها في الجزء التالي من الكود.

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

لا ، أعلم أن الأمثلة القليلة الماضية كانت حول الوظائف ، لكنني أود أن أؤيد بناء جملة متسق للوظائف البارامترية وأنواع المعلمات. لا أعتقد أن فريق Go سيضيف Generics إلى Go ما لم يكن مفهومًا موحدًا ببنية موحدة.

عندما أرى Print(...) غريزتي الأولى هي "هذه مكالمة وظيفية تكتب إلى مكان ما". لا ينقل "هذا سيعيد وظيفة".

ولا يحدث أيضًا func Print(…) func(…) ، عند استدعائه كـ Print(…) . ومع ذلك ، فنحن جميعًا على ما يرام مع ذلك. بدون صيغة استدعاء خاصة ، إذا قامت الدالة بإرجاع func .
يخبرك بناء الجملة Print(…) إلى حد كبير بما يفعله اليوم: أن Print هي دالة تُرجع بعض القيمة ، وهو ما Print(…) . إذا كنت مهتمًا بالنوع الذي ترجع إليه هذه الدالة ، فابحث عن تعريفها.
أو ، على الأرجح ، استخدم حقيقة أنه في الواقع Print(…)(…) كمؤشر على أنه يقوم بإرجاع دالة.

بالتفكير في الأمر ، إذا كتبت بعض استدعاءات الوظائف وكان المترجم قادرًا على تحسينها وحساب نتيجتها في وقت الترجمة ، فستكون سعيدًا لاكتساب الأداء!

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

في رأيي ، أي من هؤلاء أفضل لأنه يمكنه توصيل السلوك والنية:

لاحظ أن الأول على الأقل متوافق بنسبة 100٪ مع التصميم. لا يصف أي نموذج للمعرفات المستخدمة وآمل ألا تقترح وصف ذلك (وإذا فعلت ذلك ، فسأكون مهتمًا بسبب عدم تطبيق نفس القواعد على مجرد إرجاع func ).

لا ، أعلم أن الأمثلة القليلة الماضية كانت حول الوظائف ، لكنني أود أن أؤيد بناء جملة متسق للوظائف البارامترية وأنواع المعلمات.

حسنًا ، أوافق ، كما قلت :) أنا فقط أقول أنني لا أفهم كيف يمكن تطبيق الحجج التي تقدمها على طول المحور "العام مقابل غير العام" ، حيث لا توجد تغييرات سلوكية مهمة بين الاثنان. ستكون منطقية على طول محور "النوع مقابل الوظيفة" ، لأن ما إذا كان شيء ما محددًا لنوع أو تعبيرًا مهم جدًا للسياق الذي يمكن استخدامه فيه. ما زلت لا أوافق ، ولكن على الأقل سأفهم معهم :)

@ Merovius شكرا لتعليقك.

ولا يحدث أيضًا func Print(…) func(…) عند استدعائه كـ Print(…) . ومع ذلك ، فنحن جميعًا على ما يرام مع ذلك. بدون صيغة استدعاء خاصة ، إذا قامت دالة بإرجاع func.
تخبرك بنية Print(…) بما تفعله اليوم تمامًا: أن Print هي دالة تُرجع بعض القيمة ، وهو ما Print(…) . إذا كنت مهتمًا بالنوع الذي ترجع إليه هذه الدالة ، فابحث عن تعريفها.

لدي رأي مفاده أن اسم الوظيفة يجب أن يكون مرتبطًا بما تفعله. لذلك أتوقع Print(...) لطباعة شيء ما ، بغض النظر عما يتم إرجاعه. أعتقد أن هذا توقع معقول ، ويمكن العثور عليه في غالبية كود Go الحالي.

إذا رأيت Print(...)(...) ، فقد أبلغت أن أول () قد طبع شيئًا ما ، وأن الوظيفة أعادت وظيفة من نوع ما ، وأن الوظيفة الثانية () تنفذ هذا السلوك الإضافي .

(سأندهش إذا كان هذا رأيًا غير عادي أو نادرًا ، لكنني لن أجادل في بعض نتائج الاستطلاع.)

لاحظ أن الأول على الأقل متوافق بنسبة 100٪ مع التصميم. لا يصف أي شكل من أشكال المعرفات المستخدمة وآمل ألا تقترح وصف ذلك (وإذا فعلت ذلك ، فسأكون مهتمًا لماذا لا تنطبق نفس القواعد على إعادة وظيفة فقط).

أنت على حق ، لقد اقترحت ذلك :)

انظر ، لقد قمت بإدراج 3 طرق يمكن أن أفكر بها لإصلاح الغموض البصري المقدم بواسطة معلمات النوع في الوظائف والأنواع. إذا كنت لا ترى أي غموض ، فلن تعجبك أي من الاقتراحات!

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

انظر أعلاه النقاط حول الغموض و 3 حلول مقترحة.

معلمات النوع هي شيء جديد.

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

أنا أفضل السابق. في كلتا الحالتين ، ستكون التغييرات عامة عبر بناء جملة معلمة النوع ، كما تمت مناقشته.

هناك طريقتان أخريان لإلقاء الضوء على هذا:

  1. الدراسة الاستقصائية
  2. درس تعليمي

1. المسح

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

ليس لدي الوسائل للقيام بذلك ، ولكن سأكون مهتمًا بمعرفة ما سيحدث إذا قمت باستطلاع آراء بضعة آلاف من Gophers حول "ترتيب هذه الأشياء حسب الوضوح".

حدود:

// Lifted from the design draft
func New(type K, V)(compare func(K, K) int) *Map(K, V) {
    return &Map(K, V){compare: compare}
}

// ...

func (m *Map(K, V)) InOrder() *Iterator(K, V) {
    sender, receiver := chans.Ranger(keyValue(K, V))()
    var f func(*node(K, V)) bool
    f = func(n *node(K, V)) bool {
        if n == nil {
            return true
        }
        // Stop sending values if sender.Send returns false,
        // meaning that nothing is listening at the receiver end.
        return f(n.left) &&
            sender.Send(keyValue(K, V){n.key, n.val}) &&
            f(n.right)
    }
    go func() {
        f(m.root)
        sender.Close()
    }()
    return &Iterator{receiver}
}

// ...

عامل التماثل:

// Lifted from the design draft
func New(type K, V)(compare func(K, K) int) *Map!(K, V) {
    return &Map!(K, V){compare: compare}
}

// ...

func (m *Map(K, V)) InOrder() *Iterator!(K, V) {
    sender, receiver := chans.Ranger!(keyValue!(K, V))()
    var f func(*node!(K, V)) bool
    f = func(n *node!(K, V)) bool {
        if n == nil {
            return true
        }
        // Stop sending values if sender.Send returns false,
        // meaning that nothing is listening at the receiver end.
        return f(n.left) &&
            sender.Send(keyValue!(K, V){n.key, n.val}) &&
            f(n.right)
    }
    go func() {
        f(m.root)
        sender.Close()
    }()
    return &Iterator{receiver}
}

// ...

الأقواس الزاوية: (أو الأقواس المزدوجة الزاوية ، في كلتا الحالتين)

// Lifted from the design draft
func New<type K, V>(compare func(K, K) int) *Map<K, V> {
    return &Map<K, V>{compare: compare}
}

// ...

func (m *Map<K, V>) InOrder() *Iterator<K, V> {
    sender, receiver := chans.Ranger<keyValue<K, V>>()
    var f func(*node<K, V>) bool
    f = func(n *node<K, V>) bool {
        if n == nil {
            return true
        }
        // Stop sending values if sender.Send returns false,
        // meaning that nothing is listening at the receiver end.
        return f(n.left) &&
            sender.Send(keyValue<K, V>{n.key, n.val}) &&
            f(n.right)
    }
    go func() {
        f(m.root)
        sender.Close()
    }()
    return &Iterator{receiver}
}

// ...

وظائف مسماة بشكل مناسب:

// Lifted from the design draft
func NewConstructor(type K, V)(compare func(K, K) int) *MapType(K, V) {
    return &MapType(K, V){compare: compare}
}

// ...

func (m *MapType(K, V)) InOrder() *IteratorType(K, V) {
    sender, receiver := chans.RangerType(keyValueType(K, V))()
    var f func(*nodeType(K, V)) bool
    f = func(n *nodeType(K, V)) bool {
        if n == nil {
            return true
        }
        // Stop sending values if sender.Send returns false,
        // meaning that nothing is listening at the receiver end.
        return f(n.left) &&
            sender.Send(keyValueType(K, V){n.key, n.val}) &&
            f(n.right)
    }
    go func() {
        f(m.root)
        sender.Close()
    }()
    return &Iterator{receiver}
}

// ...

... مضحك ، أنا في الواقع أحب آخر واحد.

(ما رأيك في أداء هذه الأشياء في العالم الواسع لـ GophersMerovius ؟)

2. البرنامج التعليمي

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

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

يمكن أن يقدم هذا نوعًا من زاوية أو شريحة مختلفة من البيانات عن تقارير التعليقات النموذجية.

tooolbox أعتقد أن ما لم تجب عليه بعد هو: لماذا تعتبر هذه مشكلة للوظائف البارامترية ، ولكن ليس للدوال غير المعلمية التي تعيد func ؟ أستطيع اليوم أن أكتب

func Print(a string) func(string) {
    return func(b string) {
        fmt.Println(a+b)
    }
}

func main() {
    Print("foo")("bar")
}

لماذا هذا مقبول ولا يقودك إلى الخلط الشديد بسبب الغموض ، ولكن بمجرد أن يأخذ Print معلمة نوع بدلاً من معلمة قيمة ، يصبح هذا أمرًا لا يطاق؟ وهل تقترح أيضًا (بغض النظر عن أسئلة التوافق الواضحة) أن نضيف تقييدًا للعمل بشكل صحيح ، وأن هذا لا ينبغي أن يكون ممكنًا ، إلا إذا تمت إعادة تسمية Print إلى MakeXFunc مقابل بعض X ؟ إذا لم يكن كذلك ، فلماذا؟

tooolbox هل ستكون هذه مشكلة حقًا عندما يكون الافتراض أن الاستدلال بالنوع قد يزيل الحاجة إلى تحديد الأنواع البارامترية للوظائف ، تاركًا مجرد استدعاء دالة بسيط المظهر؟

Merovius لا أعتقد أن المشكلة تتعلق بالصيغة Print("foo")("bar") نفسها ، لأنها ممكنة بالفعل في Go 1 ، على وجه التحديد لأنها تحتوي على تفسير واحد محتمل . تكمن المشكلة في أنه مع الاقتراح غير المعدل ، أصبح التعبير Foo(X)(Y) غامضًا الآن وقد يعني أنك تجري استدعائين للدالة (كما في Go 1) ، أو قد يعني أنك تجري استدعاء دالة واحدة باستخدام وسيطات من النوع . تكمن المشكلة في القدرة على استنتاج ما يفعله البرنامج محليًا ، وهذان التفسيران الدلاليان المحتملان مختلفان تمامًا .

urandom أوافق على أن الاستدلال بالنوع قد يكون قادرًا على التخلص من الجزء الأكبر من معلمات النوع المقدمة صراحة ، لكنني لا أعتقد أن دفع كل التعقيد المعرفي في الزوايا المظلمة للغة لمجرد أنه نادرًا ما يتم استخدامها هو فكرة جيدة إما. حتى لو كان من النادر أن معظم الناس لا يصادفهم عادةً ، فسيظلون يواجهونها أحيانًا ، والسماح لبعض الشفرات بأن يكون لها تدفق تحكم محير طالما أنها ليست "معظم" الشفرة تترك طعمًا سيئًا في فمي. خاصة وأن Go حاليًا سهل الوصول إليه عند قراءة رمز "السباكة" بما في ذلك stdlib. ربما يكون الاستدلال بالكتابة جيدًا لدرجة أن "نادر" يصبح "أبدًا" ، ويظل مبرمجو Go منضبطين للغاية ولا يصممون أبدًا نظامًا تكون فيه معلمات النوع ضرورية ؛ إذن فهذه القضية برمتها محل نقاش. لكنني لن أراهن على ذلك.

أعتقد أن الدافع الرئيسي لحجة tooolbox هو أنه لا ينبغي لنا أن نفرط في تحميل بناء الجملة الحالي بدلالات حساسة للسياق ، وعلينا بدلاً من ذلك أن نجد بعض الصيغ الأخرى غير الغامضة (حتى لو كانت مجرد إضافة صغيرة مثل Foo(X)!(Y) .) أعتقد أن هذا مقياس مهم عند التفكير في خيارات بناء الجملة.

لقد استخدمت وقرأت قليلاً من رمز D ، في الأيام (~ 2008-2009) ، ويجب أن أقول إن ! كان دائمًا يعرقلني.

اسمحوا لي أن أرسم هذه السقيفة إما بـ # أو $ أو @ ، بدلاً من ذلك (حيث ليس لديهم أي معنى في Go أو C).
قد يفتح هذا بعد ذلك إمكانية استخدام الأقواس المتعرجة بدون أي تشويش مع الخرائط أو الشرائح أو الهياكل.

  • Foo@{X}(Y)
  • Foo${X}(Y)
  • Foo#{X}(Y)
    أو الأقواس المربعة.

في مثل هذه المناقشات ، من الضروري النظر إلى الكود الحقيقي.

على سبيل المثال ، ضع في اعتبارك أن قلة من الناس يكتبون Foo(X)(Y) . في Go ، اكتب الأسماء واسم المتغير وأسماء الوظائف متشابهة تمامًا ، ومع ذلك نادرًا ما يشعر الناس بالارتباك بشأن ما يبحثون عنه. يفهم الناس أن int64(v) هو نوع التحويل وأن F(v) هو استدعاء دالة ، على الرغم من أنهما متماثلان تمامًا.

نحتاج إلى إلقاء نظرة على الكود الحقيقي لنرى ما إذا كانت وسيطات النوع محيرة حقًا في الممارسة. إذا كانت كذلك ، فيجب علينا تعديل بناء الجملة. في حالة عدم وجود رمز حقيقي ، فإننا ببساطة لا نعرف.

في الأربعاء 6 مايو 2020 الساعة 13:00 ، كتب إيان لانس تايلور:

يفهم الناس أن int64(v) هو نوع التحويل وأن F(v) هو تحويل
استدعاء الوظيفة ، على الرغم من أنها تبدو متشابهة تمامًا.

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

—صام

-
سام ويتيد

ianlancetaylor أحد الأشياء التي لاحظتها في مثالك هو أنه يمكنك كتابة دالة تأخذ نوعًا وإرجاع نوع آخر بنفس المعنى ، لذا فإن استدعاء النوع كتحويل نوع أساسي مثل int64(v) يكون منطقيًا في بنفس الطريقة التي يكون بها strconv.Atoi(v) منطقية.

لكن بينما يمكنك القيام بـ UseConverter(strconv.Atoi) ، فإن UseConverter(int64) غير ممكن في Go 1. قد يؤدي وجود أقواس لمعلمة النوع إلى فتح بعض الاحتمالات إذا كان من الممكن استخدام العام للإرسال مثل:

func StrToNumber(type K)(s string) K {
  asInt := strconb.Atoi(s)
  return K(asInt)
}

لماذا هذا جيد ولا يقودك إلى الخلط الشديد بسبب الغموض

المثال الخاص بك ليس على ما يرام. لا يهمني إذا كانت المكالمة الأولى تأخذ وسيطات أو معلمات من النوع. لديك وظيفة Print لا تطبع أي شيء. هل يمكنك تخيل قراءة / مراجعة هذا الرمز؟ يبدو Print("foo") مع المجموعة الثانية من الأقواس المحذوفة جيدًا ، لكنه في الخفاء عبارة عن no-op.

إذا قمت بإرسال هذا الرمز إليّ في العلاقات العامة ، فسأطلب منك تغيير الاسم إلى PrintFunc أو MakePrintFunc أو PrintPlusFunc أو شيء يعبّر عن سلوكه.

لقد استخدمت وقرأت قليلاً من كود D ، في الأيام (~ 2008-2009) ، ويجب أن أقول! كان دائما يزعجني.

ها ، ممتع. ليس لدي أي تفضيل معين لمشغل إنشاء مثيل ؛ هذه تبدو وكأنها خيارات لائقة.

في Go ، اكتب الأسماء واسم المتغير وأسماء الوظائف متشابهة تمامًا ، ومع ذلك نادرًا ما يشعر الناس بالارتباك بشأن ما يبحثون عنه. يفهم الناس أن int64 (v) هو تحويل نوع وأن F (v) هي استدعاء وظيفي ، على الرغم من أنها تبدو متشابهة تمامًا.

أوافق ، يمكن للناس عادةً التفريق بسرعة بين تحويلات النوع والمكالمات الوظيفية. لماذا تعتقد ذلك؟

نظريتي الشخصية هي أن الأنواع عادة ما تكون أسماء ، والوظائف عادة ما تكون أفعال. لذلك عندما ترى Noun(...) فمن الواضح تمامًا أنه تحويل نوع ، وعندما ترى Verb(...) فهي مكالمة وظيفية.

نحتاج إلى إلقاء نظرة على الكود الحقيقي لنرى ما إذا كانت وسيطات النوع محيرة حقًا في الممارسة. إذا كانت كذلك ، فيجب علينا تعديل بناء الجملة. في حالة عدم وجود رمز حقيقي ، فإننا ببساطة لا نعرف.

منطقي.

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

هناك الكثير من "التعليمات البرمجية الحقيقية" في الجزء السفلي من مسودة العقود ، والتي تتعامل مع جميع حالات الاستخدام الشائعة ، وهو أمر رائع! ومع ذلك ، أجد صعوبة في التحليل المرئي ؛ أنا أبطأ في قراءة وفهم الشفرة. يبدو لي أنه يجب عليّ أن أنظر إلى حجج الأشياء والسياق الأوسع لأعرف ما هي الأشياء وما هو تدفق التحكم ، ويبدو أن هذه خطوة إلى أسفل من الكود العادي.

لنأخذ هذا الكود الحقيقي:

import "container/orderedmap"

var m = orderedmap.New(string, string)(strings.Compare)

func Add(a, b string) {
    m.Insert(a, b)
}

عندما أقرأ orderedmap.New( أتوقع أن يكون ما يلي هو الوسيطات لوظيفة New ، تلك الأجزاء الأساسية من المعلومات التي تحتاجها الخريطة المرتبة للعمل. لكن هؤلاء موجودون بالفعل في المجموعة الثانية من الأقواس. لقد ألقيت بهذا. إنه يجعل من الشيفرة أكثر صعوبة.

(هذا مجرد مثال واحد ، ليس كل ما أراه غامضًا ، لكن من الصعب إجراء مناقشة مفصلة حول مجموعة واسعة من النقاط.)

هذا ما أود أن أقترحه:

// Instantiation operator
var m = orderedmap.New!(string, string)(strings.Compare)
// Alternate delimiters -- notice I don't insist on any particular kind
var m = orderedmap.New<|string, string|>(strings.Compare)
// Appropriately named function
var m = orderedmap.MakeConstructor(string, string)(strings.Compare)

في المثالين الأولين ، تعمل صيغة مختلفة على كسر افتراضاتي بأن المجموعة الأولى من الأقواس تحتوي على وسيطات لـ New() ، لذا فإن الكود أقل إثارة للدهشة ويكون التدفق أكثر وضوحًا من مستوى عالٍ.

يستخدم الخيار الثالث التسمية لجعل التدفق غير مفاجئ. أتوقع الآن أن تحتوي المجموعة الأولى من الأقواس على الحجج اللازمة لإنشاء دالة مُنشئ ، وأتوقع أن القيمة المعادة هي دالة مُنشئ يمكن استدعاؤها بدورها لإنتاج خريطة مرتبة.


يمكنني بالتأكيد قراءة التعليمات البرمجية في النمط الحالي. تمكنت من قراءة جميع التعليمات البرمجية في مسودة العقود. إنها أبطأ لأنها تستغرق وقتًا أطول لمعالجتها. لقد بذلت قصارى جهدي لتحليل سبب ذلك والإبلاغ عنه: بالإضافة إلى المثال orderedmap.New ، يحتوي https://github.com/golang/go/issues/15292#issuecomment -623649521 على ملخص جيد ، على الرغم من أنه يمكنني على الأرجح التوصل إلى المزيد. تختلف درجة الغموض بين الأمثلة المختلفة.

أقر بأنني لن أحصل على موافقة الجميع ، لأن سهولة القراءة والوضوح أمران غير موضوعيين إلى حد ما وربما يتأثران بخلفية الشخص ولغاته المفضلة. أعتقد أن 4 أنواع من تحليل الغموض هو مؤشر جيد على أن لدينا مشكلة ، على الرغم من ذلك.

import "container/orderedmap"

var m = orderedmap.NewOf(string, string)(strings.Compare)

func Add(a, b string) {
    m.Insert(a, b)
}

أعتقد أن قراءة NewOf أفضل من New لأن New عادةً ما يُرجع مثيلاً ، وليس عامًا يُنشئ مثيلاً.


لديك وظيفة Print لا تطبع أي شيء.

للتوضيح ، نظرًا لوجود نوع من الاستدلال التلقائي ، فإن Print(foo) العام سيكون إما مكالمة طباعة حقيقية عبر الاستدلال أو الخطأ. في Go today ، غير مسموح بالمعرفات المجردة:

package main

import (
    "fmt"
)

func main() {
    fmt.Println
}

./prog.go:8:5: fmt.Println evaluated but not used

أتساءل عما إذا كانت هناك طريقة ما لجعل الاستدلال العام أقل إرباكًا.

tooolbox

المثال الخاص بك ليس على ما يرام. لا يهمني إذا كانت المكالمة الأولى تأخذ وسيطات أو معلمات من النوع. لديك وظيفة طباعة لا تطبع أي شيء. هل يمكنك تخيل قراءة / مراجعة هذا الرمز؟

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

الذي كان سؤالي المتابعة. هل تعتقد أن Go هي لغة أسوأ ، لأنها لم تضع قيدًا على الأسماء للوظائف التي تعود- func ؟ إذا لم يكن الأمر كذلك ، فلماذا ستكون لغة أسوأ إذا لم نضع هذا القيد على مثل هذه الوظائف ، عندما يأخذون وسيطة نوع بدلاً من وسيطة قيمة؟

تضمين التغريدة

لكنك تطالب بفرض هذا القيد على مستوى اللغة.

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

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

تحرير: أعتقد أننا ننحرف قليلاً عن المسار. أعتقد أن المزيد من كود المثال "الحقيقي" سيكون مفيدًا جدًا للمحادثة في هذه المرحلة.

لا ، إنه يجادل بأن الاعتماد على معايير التسمية هو حل محتمل صالح للمشكلة.

هل هم؟ حاولت أن أسأل على وجه التحديد:

لاحظ أن الأول على الأقل متوافق بنسبة 100٪ مع التصميم. لا يصف أي شكل من أشكال المعرفات المستخدمة وآمل ألا تقترح وصف ذلك (وإذا فعلت ذلك ، فسأكون مهتمًا لماذا لا تنطبق نفس القواعد على إعادة وظيفة فقط).

أنت على حق ، لقد اقترحت ذلك :)

أوافق على أن كلمة "وصف" ليست محددة للغاية هنا ، ولكن هذا على الأقل هو السؤال الذي قصدته. إذا كانوا لا يجادلون بالفعل لصالح متطلبات مستوى اللغة المضمنة في التصميم ، فأنا أعتذر بالطبع عن سوء الفهم. لكني أشعر أن من المبرر افتراض أن "وصف" على الأقل أقوى من "قاعدة غير رسمية" ، على الأقل. خاصة إذا تم وضعهما في سياق الاقتراحين الآخرين اللذين قدموهما (على قدم المساواة) وهما عبارة عن تركيبات على مستوى اللغة لأنها لا تستخدم حتى المعرفات الصالحة حاليًا.

هل ستكون هناك خطة تشبه vgo للسماح للمجتمع بتجربة أحدث عرض عام؟

بعد اللعب قليلاً في الملعب الممكّن بموجب العقد ، لا أرى حقًا سبب كل هذا العناء حول الحاجة إلى التمييز بين وسيطات النوع والحجج العادية.

تأمل في هذا المثال . تركت مُهيئ النوع في جميع الوظائف ، على الرغم من أنه يمكنني حذفها جميعًا وسيظل تجميعها جيدًا. يبدو أن هذا يشير إلى أن الغالبية العظمى من هذه التعليمات البرمجية المحتملة لن تتضمنها حتى ، وهذا بدوره لن يسبب أي ارتباك.

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

تدور الضجة حول كيفية ظهوره بدون أدوية https://play.golang.org/p/7BRdM2S5dwQ وبالنسبة لشخص جديد في برمجة Stack منفصل لكل نوع مثل StackString و StackInt ... ثم Stack (T) في اقتراح بناء الجملة العام الحالي. ليس لدي شك في أن الاقتراح الحالي مدروس جيدًا كما هو موضح في المثال الخاص بك ولكن قيمة البساطة والوضوح تقل عن طريق التخصيص. أفهم أن الأولوية الأولى هي معرفة ما إذا كان يعمل عن طريق الاختبار ، ولكن بمجرد أن نتفق على الاقتراح الحالي يغطي معظم الحالات ولا توجد صعوبات فنية في المترجم ، فإن الأولوية الأعلى هي جعله مفهومًا للجميع والذي كان دائمًا السبب الأول وراء انطلق بنجاح من البداية.

Merovius لا ، يبدو الأمر كما قال infogulch ، لقد قصدت إنشاء اتفاقية على غرار -er على الواجهات. ذكرت ذلك أعلاه ، آسف للارتباك. (أنا "هو" بالمناسبة).

تأمل في هذا المثال. تركت مُهيئ النوع في جميع الوظائف ، على الرغم من أنه يمكنني حذفها جميعًا وسيظل تجميعها جيدًا. يبدو أن هذا يشير إلى أن الغالبية العظمى من هذه التعليمات البرمجية المحتملة لن تتضمنها حتى ، وهذا بدوره لن يسبب أي ارتباك.

ماذا عن نفس المثال في نسخة متشعبة لملعب الأدوية الجنيسة؟

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

كما قلت سابقًا ، لم أكن عالقًا في ! لنوع إنشاء مثيل (وأعتقد أن :: يبدو أفضل عند المراجعة). وهي تساعد فقط في الأماكن التي تستخدم فيها الأدوية الجنيسة ، وليس كثيرًا في الإعلانات. لذلك يجمع هذا إلى حد ما بين الاثنين ، مع حذف <> عندما يكون غير ضروري ، إلى حد ما مثل حذف () لمعلمات إرجاع الوظيفة إذا كان هناك واحد فقط.

مقتطفات عينة:

type Stack::<type E> []E

func (s Stack::E) Peek() E {
    return s[len(s)-1]
}

func (s *Stack::E) Pop() {
    *s = (*s)[:len(*s)-1]
}

func (s *Stack::E) Push(value E) {
    *s = append(*s, value)
}

type StackIterator::<type E> struct{
    stack Stack::E
    current int
}

func (s *Stack::E) Iter() Iterator::E {
    it := StackIterator::E{stack: *s, current: len(*s)}

    return &it
}

func (i *StackIterator::E) Next() (bool) { 
    i.current--

    if i.current < 0 { 
        return false
    }

    return true
}

func (i *StackIterator::E) Value() E { 
    if i.current < 0 {
        var zero E
        return zero
    }

    return i.stack[i.current]
}

// ...

var it Iterator::string = stack.Iter()

it = Filter::string(it, func(s string) bool {
    return s == "foo" || s == "beta" || s == "delta"
})

it = Map::<string, string>(it, func(s string) string {
    return s + ":1"
})

it = Distinct::string(it)

println(Reduce(it, "", func(a, b string) string {
    if a == "" {
        return b
    }
        return a + ":" + b
}))

في هذا المثال ، قمت أيضًا بضبط أسماء المتغيرات ، أعتقد أن "العنصر" هو E أكثر قابلية للقراءة من "النوع" T .

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

لا بأس أيضًا مع المزيد من الاستدلال على الكتابة:

var it Iterator::string = stack.Iter()

it = Filter(it, func(s string) bool {
    return s == "foo" || s == "beta" || s == "delta"
})

it = Map::<string, string>(it, func(s string) string {
    return s + ":1"
})

it = Distinct(it)

println(Reduce(it, "", func(a, b string) string {
    if a == "" {
        return b
    }
        return a + ":" + b
}))

tooolbox Apologies ، إذن ، كنا نتحدث مع بعضنا البعض :)

شخص جديد في برمجة Stack منفصل لكل نوع مثل StackString ، StackInt ، ... أسهل في البرمجة ثم Stack (T)

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

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

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

لا أعتقد أن gertcuykens يقصد منه تعطيل تنفيذ Stack الخاص بك ، يبدو أنه شعر أن بناء جملة الأدوية غير مألوف ويصعب فهمه.

في حالة تضمين معلمات النوع هذه ، يمكن إجراء بعض الملاحظات:
(ا ب ت ث)...

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

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

بالنسبة لتلك الحالات المرضية الأكثر تحديدًا ، ذكرت infogulch ذلك جيدًا:

لا أعتقد أن دفع كل التعقيدات المعرفية في الزوايا المظلمة للغة لمجرد أنها لا تُستخدم إلا نادرًا هو فكرة جيدة أيضًا. حتى لو كان من النادر أن معظم الناس لا يصادفهم عادةً ، فسيظلون يواجهونها أحيانًا ، والسماح لبعض الشفرات بأن يكون لها تدفق تحكم محير طالما أنها ليست "معظم" الشفرة تترك طعمًا سيئًا في فمي.

أعتقد ، في هذه المرحلة ، أننا وصلنا إلى التشبع المفصلي حول هذه الشريحة المحددة من الموضوع. بغض النظر عن مقدار ما نتحدث عنه ، سيكون الاختبار الحمضي هو مدى سرعة ومدى قدرة Go devs على تعلمها وقراءتها وكتابتها.

(ونعم ، قبل الإشارة إلى ذلك ، يجب أن يقع العبء على مؤلف المكتبة ، وليس على مطور العميل ، لكنني لا أعتقد أننا نريد "تأثير التعزيز" حيث تكون المكتبات العامة غير مفهومة للرجل في الشارع. لا أريد أن يتحول Go إلى مخيم عام ، لكنني أثق جزئيًا في أن حذف التصميم سيحد من الانتشار .)

لدينا ملعب ويمكننا عمل شوكات لتراكيب أخرى ، وهو أمر رائع. ربما نحتاج إلى المزيد من الأدوات!

لقد قدم الناس ملاحظات . أنا متأكد من أن هناك حاجة إلى مزيد من التعليقات ، وربما نحتاج إلى أنظمة ملاحظات أفضل أو أكثر انسيابية.

tooolbox هل تعتقد أنه من الممكن تحليل الكود عندما تحذف دائمًا <> و type هكذا؟ ربما يتطلب اقتراحًا أكثر صرامة فيما يمكن عمله ، لكن ربما يستحق المقايضة؟

type Stack::E []E

func (s Stack::E) Peek() E {
    return s[len(s)-1]
}

func (s *Stack::E) Pop() {
    *s = (*s)[:len(*s)-1]
}

func (s *Stack::E) Push(value E) {
    *s = append(*s, value)
}

type StackIterator::E struct{
    stack Stack::E
    current int
}

func (s *Stack::E) Iter() Iterator::E {
    it := StackIterator::E{stack: *s, current: len(*s)}

    return &it
}

func (i *StackIterator::E) Next() (bool) { 
    i.current--

    if i.current < 0 { 
        return false
    }

    return true
}

func (i *StackIterator::E) Value() E { 
    if i.current < 0 {
        var zero E
        return zero
    }

    return i.stack[i.current]
}

// ...

var it Iterator::string = stack.Iter()

it = Filter::string(it, func(s string) bool {
    return s == "foo" || s == "beta" || s == "delta"
})

it = Map::string, string (it, func(s string) string {
    return s + ":1"
})

it = Distinct::string(it)

println(Reduce(it, "", func(a, b string) string {
    if a == "" {
        return b
    }
        return a + ":" + b
}))

لا أعرف لماذا ، لكن هذا Map::string, string (... يبدو غريبًا. يبدو كما لو أن هذا يؤدي إلى إنشاء رمزين مميزين ، و Map::string ، واستدعاء دالة string .

أيضًا ، على الرغم من عدم استخدام هذا في Go ، فإن استخدام "المعرف :: Identifier" قد يعطي انطباعًا خاطئًا للمستخدمين لأول مرة ، معتقدين أن هناك فئة / مساحة اسم Filter مع string تعمل فيه. سيؤدي إعادة استخدام الرموز المميزة من اللغات الأخرى المعتمدة على نطاق واسع لشيء مختلف تمامًا إلى الكثير من الالتباس.

هل تعتقد أنه من الممكن تحليل الكود عندما تحذف دائمًا <> وتكتب هكذا؟ ربما يتطلب اقتراحًا أكثر صرامة فيما يمكن عمله ، لكن ربما يستحق المقايضة؟

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

أيضًا ، على الرغم من عدم استخدام هذا في Go ، فإن استخدام "Identifier :: Identifier" قد يعطي انطباعًا خاطئًا للمستخدمين لأول مرة ، معتقدين أن هناك فئة تصفية / مساحة اسم بها وظيفة سلسلة. سيؤدي إعادة استخدام الرموز المميزة من اللغات الأخرى المعتمدة على نطاق واسع لشيء مختلف تمامًا إلى الكثير من الالتباس.

لم أستخدم لغة بها :: لكني رأيتها في الجوار. ربما يكون ! أفضل من ذلك لأنه سيتطابق مع D ، على الرغم من أنني أجد :: يبدو أفضل بصريًا.

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

  • شيء آخر غير bare identifier() بحيث لا يبدو وكأنه استدعاء وظيفي.
  • شيء يمكن أن يحيط بمعلمات نوع متعددة ، لتوحيدها بصريًا بالطريقة التي تستطيع بها الأقواس.
  • شيء يبدو متصلاً بالمعرف بحيث يبدو وكأنه وحدة.
  • شيء غير غامض للمحلل.
  • شيء لا يتعارض مع مفهوم مختلف لديه مشاركة قوية في عقل المطور.
  • إذا كان ذلك ممكنًا ، فسيؤثر شيء ما على التعريفات وكذلك استخدامات الأدوية الجنيسة ، بحيث تصبح قراءتها أسهل أيضًا.

هناك الكثير من الأشياء التي يمكن أن تناسب.

  • identifier!(a, b) ( ملعب )
  • identifier@(a, b)
  • identifier#(a, b)
  • identifier$(a, b)
  • identifier<:a, b:>
  • identifier.<a, b> يشبه تأكيد النوع!
  • identifier:<a, b>
  • إلخ.

أي شخص لديه أي أفكار حول كيفية زيادة تضييق مجموعة الإمكانات؟

مجرد ملاحظة سريعة أننا نظرنا في كل هذه الأفكار ، كما نظرنا في أفكار مثل

func F(T : a, b T) { }
func G() { F(int : 1, 2) }

لكن مرة أخرى ، الدليل على الحلوى يكمن في الأكل. المناقشات المجردة في حالة عدم وجود رمز تستحق الخوض فيها ولكنها لا تؤدي إلى استنتاجات نهائية.

(لست متأكدًا مما إذا كان قد تم الحديث عن هذا من قبل) أرى أنه في الحالات التي نتلقى فيها بنية ، لن نتمكن من "توسيع" واجهة برمجة تطبيقات موجودة للتعامل مع الأنواع العامة دون كسر رمز الاتصال الحالي.

على سبيل المثال ، بالنظر إلى هذه الوظيفة غير العامة

func Repeat(v, n int) []int {
    var r []int
    for i := n; i > 0; i-- {
        r = append(r, v)
    }
    return r
}

Repeat(4, 4)

يمكننا جعلها عامة دون كسر التوافق مع الإصدارات السابقة

func Repeat(type T)(v T, n int) []T {
    var r []T
    for i := n; i > 0; i-- {
        r = append(r, v)
    }
    return r
}

Repeat("a", 5)

ولكن إذا أردنا أن نفعل الشيء نفسه مع وظيفة تتلقى struct عام

type XY struct {
    X, Y int
}

func RangeRepeat(arr []XY) []int {
    var r []int
    for _, n := range arr {
        for i := n.Y; i > 0; i-- {
            r = append(r, n.X)
        }
    }
    return r
}

RangeRepeat([]XY{{1, 1}, {2, 2}, {3, 3}})

يبدو أن رمز الاتصال بحاجة إلى التحديث

type XY(type T) struct {
    X T
    Y int
}

func RangeRepeat(type T)(arr []XY(T)) []T {
    var r []T
    for _, n := range arr {
        for i := n.Y; i > 0; i-- {
            r = append(r, n.X)
        }
    }
    return r
}

// error: cannot use generic type XY(type T any) without instantiation
// RangeRepeat([]XY{{1, 1}, {2, 2}, {3, 3}}) // error in old code
RangeRepeat([](XY(int)){{1, 1}, {2, 2}, {3, 3}}) // API changed
// RangeRepeat([]XY{{"1", 1}, {"2", 2}, {"3", 3}}) // error
RangeRepeat([](XY(string)){{"1", 1}, {"2", 2}, {"3", 3}}) // ok

سيكون من الرائع أن تكون قادرًا على اشتقاق أنواع من الهياكل أيضًا.

تضمين التغريدة

تذكر مسودة العقد أن methods may not take additional type arguments . ومع ذلك ، لا يوجد ذكر لاستبدال العقد بطرق معينة. ستكون هذه الميزة مفيدة جدًا لتنفيذ الواجهات اعتمادًا على العقد المرتبط به النوع البارامترى.

هل ناقشت مثل هذا الاحتمال؟

سؤال آخر لمشروع العقد. هل ستقتصر عمليات الفصل على الأنواع المضمنة؟ إذا لم يكن الأمر كذلك ، فهل سيكون من الممكن استخدام الأنواع ذات المعلمات ، خاصة الواجهات في قائمة الفصل؟

شيء مثل

type Getter(T) interface {
    Get() T
}

contract(G, T) {
    G Getter(T)
}

سيكون مفيدًا للغاية ، ليس فقط لتجنب تكرار الطريقة المحددة من الواجهة إلى العقد ، ولكن أيضًا لإنشاء مثيل لنوع محدد عند فشل استنتاج النوع ، وليس لديك حق الوصول إلى النوع الملموس (على سبيل المثال ، لم يتم تصديره)

ianlancetaylor لست متأكدًا مما إذا كان هذا قد تمت مناقشته من قبل ، ولكن فيما يتعلق بصياغة وسيطات النوع إلى دالة ، هل من الممكن ربط قائمة الوسائط بقائمة وسيطات النوع؟ لذلك بالنسبة لمثال الرسم البياني ، بدلاً من

var g = graph.New(*Vertex, *FromTo)([]*Vertex{ ... })

سوف تستخدم

var g = graph.New(*Vertex, *FromTo, []*Vertex{ ... })

بشكل أساسي ، تتوافق وسيطات K الأولى في قائمة الوسائط مع قائمة وسيطات من النوع بطول K. تتوافق بقية قائمة الوسائط مع الوسائط العادية للدالة. هذا له فائدة انعكاس بناء الجملة

make(Type, size)

الذي يأخذ النوع باعتباره الوسيطة الأولى.

هذا من شأنه تبسيط القواعد ، لكنه يحتاج إلى معلومات الكتابة لمعرفة أين تنتهي وسيطات النوع ، وتبدأ الوسيطات العادية.

@ smasher164 قال بعض التعليقات مرة أخرى أنهم اعتبروها (مما يعني أنهم تخلوا عنها ، على الرغم من أنني أشعر بالفضول لماذا).

func F(T : a, b T) { }
func G() { F(int : 1, 2) }

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

أريد أن أعود إلى شيء قاله Inuart :

يمكننا جعلها عامة دون كسر التوافق مع الإصدارات السابقة

هل سيأخذ فريق Go في الاعتبار تغيير المكتبة القياسية بهذه الطريقة لتتوافق مع ضمان توافق Go 1؟ على سبيل المثال ، ماذا لو تم استبدال strings.Repeat(s string, count int) string بـ Repeat(type S stringlike)(s S, count int) S ؟ يمكنك أيضًا إضافة تعليق //Deprecated إلى bytes.Repeat لكن اتركه هناك لاستخدام الكود القديم. هل هذا شيء قد يفكر فيه فريق Go؟

تعديل: لكي أكون واضحًا ، أعني ، هل سيتم اعتبار ذلك ضمن Go1Compat بشكل عام؟ تجاهل المثال المحدد إذا لم يعجبك.

carlmjohnson لا. هذا الرمز سيتعطل: f := strings.Repeat ، حيث لا يمكن الإشارة إلى الدوال متعددة الأشكال بدون إنشاء مثيل لها أولاً.

ومن هنا ، أعتقد أن تسلسل حجج النوع ووسيطات القيمة سيكون خطأً ، لأنه يمنع بناء جملة طبيعي للإشارة إلى نسخة مُنشأة من وظيفة. سيكون من الطبيعي أكثر إذا كان Go قد تناول بالفعل الكاري ، لكنه ليس كذلك. يبدو غريبًا أن يكون لديك تعبيرات foo(int, 42) و foo(int) be مع وجود نوعين مختلفين تمامًا.

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

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

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

إذا تم وضع معلمات النوع داخل نفس الأقواس مثل المعلمات العادية ، فإن griesemer قال في # 36177 (مشاركته الثانية) أنه يحب استخدام الفاصلة المنقوطة بدلاً من النقطتين كفاصل لأنه (نتيجة لذلك من الإدراج التلقائي للفاصلة المنقوطة) سمح للفرد بنشر المعلمات على عدة أسطر بطريقة لطيفة.

أنا شخصياً أحب استخدام الأشرطة الرأسية ( |..| ) لتضمين معلمات النوع كما ترى أحيانًا هذه المستخدمة في لغات أخرى (Ruby ، ​​Crystal ، إلخ) لتضمين كتلة معلمة. لذلك سيكون لدينا أشياء مثل:

func F(|T| a, b T) { }
func G() { F(|int| 1, 2) }

المزايا تشمل:

  • إنها توفر تمييزًا مرئيًا لطيفًا (على الأقل لعيني) بين النوع والمعلمات العادية.
  • لن تحتاج إلى استخدام الكلمة الرئيسية type .
  • عدم وجود معلمات منتظمة ليست مشكلة.
  • يوجد حرف الشريط العمودي ، بالطبع ، في مجموعة ASCII ، وبالتالي يجب أن يكون متاحًا في معظم لوحات المفاتيح.

قد تكون قادرًا على استخدامه خارج الأقواس ولكن من المفترض أنك ستواجه نفس صعوبات التحليل كما هو الحال مع <...> أو [...] حيث يمكن أن يكون مخطئًا بالنسبة إلى عامل التشغيل "أو" على الرغم من احتمال ستكون الصعوبات أقل حدة.

لا أفهم كيف تساعد الأشرطة الرأسية في حالة عدم وجود معلمات منتظمة. لا أفهم كيف يمكنك تمييز إنشاء مثيل لوظيفة من استدعاء دالة.

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

أوافق على أن ذلك يمكن أن ينجح ، لكنه يبدو دقيقًا للغاية. لا أعتقد أنه سيكون واضحًا للقارئ ما يحدث.

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

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

على سبيل المثال ، لدي فكرة أنك ترغب في كتابة المزيد من التعليمات البرمجية بأسلوب الاقتراح ، وذلك لتقييم الاقتراح بشكل أفضل ، من الناحية التركيبية وغير ذلك؟ و / أو أشياء أخرى؟

tooolbox نعم. نحن نعمل على أداة لجعل ذلك أسهل ، لكنها ليست جاهزة بعد. ريال قريبا الآن.

هل يمكنك قول المزيد عن الأداة؟ هل سيسمح بتنفيذ التعليمات البرمجية؟

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

تبدو بنية F(T:) / G() { F(T:)} جيدة بالنسبة لي. لا أعتقد أن إنشاء مثيل يشبه استدعاء دالة سيكون بديهيًا للقراء عديمي الخبرة.

لا أفهم بالضبط ما هي المخاوف حول التوافق مع الإصدارات السابقة. أعتقد أن هناك قيودًا في المسودة على إعلان العقد إلا على المستوى الأعلى. قد يكون من المفيد أن تزن (وقياس) مقدار الكود الذي سيتم كسره بالفعل إذا تم السماح بذلك. ما أفهمه هو فقط الكود الذي يستخدم الكلمة الأساسية contract ، والتي تبدو وكأنها لا تحتوي على الكثير من الرموز (والتي يمكن دعمها بأي حال من خلال تحديد go1 في الجزء العلوي من الملفات القديمة). وازن ذلك مقابل عقود من القوة للمبرمجين. بشكل عام ، يبدو من السهل جدًا حماية الكود القديم بمثل هذه الآليات خاصة مع الاستخدام الواسع النطاق لأدوات go's الشهيرة.

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

أود أيضًا أن أرى أمثلة على كيف يمكن للعقود أن تساعد في تقليل الإسهاب في if err != nil ، والأهم من ذلك عندما لا تكون كافية. هل شيء مثل F() (X, error) {return IfError(foo(), func(i, j int) X { return X(i*j}), Identity )} ممكن؟

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

نتطلع إلى تجربة الاقتراح. آسف للعديد من المواضيع.

أنا شخصياً أشك في أن الكثير من الناس يرغبون في كتابة أساليب داخل الهيئات الوظيفية. من النادر جدًا تحديد الأنواع داخل الهيئات الوظيفية اليوم ؛ الإعلان عن الأساليب سيكون أكثر ندرة. ومع ذلك ، انظر # 25860 (لا علاقة لها بالأدوية).

لا أرى كيف تساعد الأدوية الجنيسة في معالجة الأخطاء (بالفعل موضوع مطول للغاية في حد ذاته). أنا لا أفهم مثالك ، آسف.

الصيغة الحرفية للوظيفة الأقصر ، غير المرتبطة أيضًا بالأدوية العامة ، هي # 21498.

عندما نشرت الليلة الماضية لم أكن أدرك أنه من الممكن اللعب بالمسودة
التنفيذ (!!). واو ، إنه لأمر رائع أن تكون قادرًا أخيرًا على كتابة المزيد من التعليمات البرمجية المجردة. ليس لدي أي مشاكل مع مسودة بناء الجملة.

استمرار المناقشة أعلاه ...


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

يمكننا الحصول على مناقشة الواجهة في # 25860. أود فقط أن أقول ذلك في العصر
من العقود ، ستصبح الأساليب أكثر أهمية ، لذلك أقترح الخطأ في
جانب من تمكين الأنواع المحلية والأشخاص الذين يحبون كتابة الإغلاق ، لا
إضعافهم.

(وللتأكيد ، من فضلك لا تستخدم التوافق الصارم مع go1 [مقابل تقريبًا
توافق بنسبة 99.999٪ ، كما أفهمها] كعامل في اتخاذ القرار بشأن هذا الأمر
خاصية.)


فيما يتعلق بمعالجة الأخطاء ، كنت أشتبه في أن الأدوية الجنيسة قد تسمح بالتجريد
الأنماط الشائعة للتعامل مع (T1, T2, ..., error) إرجاع المجموعات. انا لا
لديك أي شيء مفصل في الاعتبار. شيء مثل type ErrPair(type T) struct{T T; Err Error} قد يكون مفيدًا لربط الإجراءات معًا ، مثل Promise in
جافا / TypeScript. ربما فكر شخص ما من خلال هذا أكثر. محاولة في
قد تكون كتابة مكتبة مساعدة وكود يستخدم المكتبة أمرًا يستحق البحث
في إذا كنت تبحث عن استخدام حقيقي.

مع بعض التجارب انتهى بي الأمر بما يلي. أود أن أجرب هذا
تقنية في مثال أكبر لمعرفة ما إذا كان استخدام ErrPair(T) يساعد بالفعل.

type result struct {min, max point}

// with a generic ErrPair type and generic function errMap2 (like Java's Optional#map() function).
func minMax2(msg *inputTimeSeries) (result, error) {
    return errMap2(
        MakeErrPair(time.Parse(layout, msg.start)).withMessage("bad start"),
        MakeErrPair(time.Parse(layout, msg.end)).withMessage("bad end"),
        func(start, end time.Time) (result, error) {
            min, max := argminmax(msg.inputPoints, func(p inputPoint) float64 {
                return float64(p.value)
            })
            mkPoint := func(ip inputPoint) point {
                return point{interpTime(start, end, ip.interp).Format(layout), ip.value}
            }
            return result{mkPoint(*min), mkPoint(*max)}, nil
        }).tuple()
}

// without generics, lots of if err != nil 
func minMax(msg *inputTimeSeries) (result, error) { 
    start, err := time.Parse(layout, msg.start)
    if err != nil {
        return result{}, fmt.Errorf("bad start: %w", err)
    }
    end, err := time.Parse(layout, msg.end)
    if err != nil {
        return result{}, fmt.Errorf("bad end: %w", err)
    }
    min, max := argminmax(msg.inputPoints, func(p inputPoint) float64 {
        return float64(p.value)
    })
    mkPoint := func(ip inputPoint) point {
        return point{interpTime(start, end, ip.interp).Format(layout), ip.value}
    }
    return result{mkPoint(*min), mkPoint(*max)}, nil
}

// Most languages look more like this.
func minMaxWithThrowing(msg *inputTimeSeries) result {
    start := time.Parse(layout, msg.start)) // might throw
    end := time.Parse(layout, msg.end)) // might throw
    min, max := argminmax(msg.inputPoints, func(p inputPoint) float64 {
        return float64(p.value)
    })
    mkPoint := func(ip inputPoint) point {
        return point{interpTime(start, end, ip.interp).Format(layout), ip.value}
    }
    return result{mkPoint(*min), mkPoint(*max)}
}

(أكمل مثال الكود المتاح هنا )


للتجربة العامة ، حاولت كتابة حزمة S-Expression
هنا .
لقد واجهت بعض الذعر في التنفيذ التجريبي أثناء محاولتي
العمل مع أنواع مركبة مثل Form([]*Form(T)) . يمكنني تقديم المزيد من الملاحظات
بعد حل هذه المشكلة ، إذا كان ذلك مفيدًا.

لم أكن متأكدًا أيضًا من كيفية كتابة نوع بدائي -> دالة سلسلة:

contract PrimitiveType(T) {
    T bool, int, int8, int16, int32, int64, string, uint, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128
    // string(T) is not a contract
}

func primitiveString(type T PrimitiveType(T))(t T) string  {
    // I'm not sure if this is an artifact of the experimental implementation or not.
    return string(t) // error: `cannot convert t (variable of type T) to string`
}

كانت الوظيفة الفعلية التي كنت أحاول كتابتها هي:

// basicFormAdapter implements FormAdapter() for the primitive types.
type basicFormAdapter(type T PrimitiveType) struct{}


func (a *basicFormAdapter(T)) Format(e T, fc *FormatContext) error {
    //This doesn't work: fc.Print(string(e)) -- cannot convert e (variable of type T) to string
    // This also doesn't work: cannot type switch on non-interface value e (type int)
    // switch ee := e.(type) {
    // case int: fc.Print(string(ee))
    // default: fc.Print(fmt.Sprintf("!!! unsupported type %v", e))
    // }
    // IMO, the proposal to allow switching on T is most natural:
    // switch T.(type) {
    //  case int: fc.Print(string(e))
    //  default: fc.Print(fmt.Sprintf("!!! unsupported type %v", e))
    // }

    // This can't be the only way, right?
    rv := reflect.ValueOf(e)
    switch rv.Kind() {
    case reflect.Bool: fc.Print(fmt.Sprintf("%v", e))
    case reflect.Int:fc.Print(fmt.Sprintf("%v", e))
    case reflect.Int8: fc.Print(fmt.Sprintf("int8:%v", e))
    case reflect.Int16: fc.Print(fmt.Sprintf("int16:%v", e))
    case reflect.Int32: fc.Print(fmt.Sprintf("int32:%v", e))
    case reflect.Int64: fc.Print(fmt.Sprintf("int64:%v", e))
    case reflect.Uint: fc.Print(fmt.Sprintf("uint:%v", e))
    case reflect.Uint8: fc.Print(fmt.Sprintf("uint8:%v", e))
    case reflect.Uint16: fc.Print(fmt.Sprintf("uint16:%v", e))
    case reflect.Uint32: fc.Print(fmt.Sprintf("uint32:%v", e))
    case reflect.Uint64: fc.Print(fmt.Sprintf("uint64:%v", e))
    case reflect.Uintptr: fc.Print(fmt.Sprintf("uintptr:%v", e))
    case reflect.Float32: fc.Print(fmt.Sprintf("float32:%v", e))
    case reflect.Float64: fc.Print(fmt.Sprintf("float64:%v", e))
    case reflect.Complex64: fc.Print(fmt.Sprintf("(complex64 %f %f)", real(rv.Complex()), imag(rv.Complex())))
    case reflect.Complex128:
         fc.Print(fmt.Sprintf("(complex128 %f %f)", real(rv.Complex()), imag(rv.Complex())))
    case reflect.String:
        fc.Print(fmt.Sprintf("%q", rv.String()))
    }
    return nil
}

لقد حاولت أيضًا إنشاء نوع من أنواع مثل "نتيجة"

type Result(type T) struct {
    Value T
    Err error
}

func NewResult(type T)(value T, err error) Result(T) {
    return Result(T){
        Value: value,
        Err: err,
    }
}

func then(type T, R)(r Result(T), f func(T) R) Result(R) {
    if r.Err != nil {
        return Result(R){Err: r.Err}
    }

    v := f(r.Value)
    return  Result(R){
        Value: v,
        Err: nil,
    }
}

func thenTry(type T, R)(r Result(T), f func(T)(R, error)) Result(R) {
    if r.Err != nil {
        return Result(R){Err: r.Err}
    }

    v, err := f(r.Value)
    return  Result(R){
        Value: v,
        Err: err,
    }
}

على سبيل المثال

    r := NewResult(GetInput())
    r2 := thenTry(r, UppercaseAndErr)
    r3 := thenTry(r2, strconv.Atoi)
    r4 := then(r3, Add5)
    if r4.Err != nil {
        // handle err
    }
    return r4.Value, nil

من الناحية المثالية ، سيكون لديك وظائف then هي طرق على نوع النتيجة.

كما لا يبدو أن مثال الاختلاف المطلق في المسودة يتم تجميعه.
أعتقد أن ما يلي:

func (a ComplexAbs(T)) Abs() T {
    r := float64(real(a))
    i := float64(imag(a))
    d := math.Sqrt(r * r + i * i)
    return T(complex(d, 0))
}

يجب ان يكون:

func (a ComplexAbs(T)) Abs() ComplexAbs(T) {
    r := float64(real(a))
    i := float64(imag(a))
    d := math.Sqrt(r * r + i * i)
    return ComplexAbs(T)(complex(d, 0))
}

لدي القليل من القلق بشأن القدرة على استخدام contract لربط معلمة من نوع واحد .

في Scala ، من الشائع تحديد وظيفة مثل:

def compute[A: PointLike: HasTime: IsWGS](points: Vector[A]): Map[Int, A] = ???

PointLike ، HasTime و IsWGS صغيرة contract (يطلق عليها Scala type class ).

الصدأ لديه أيضًا آلية مماثلة:

fn f<F: A + B>(a F) {}

ويمكننا استخدام واجهة مجهولة عند تعريف دالة.

type I1 interface {
    A()
}
type I2 interface {
    B()
}
func f(a interface{
    I1
    I2
})

IMO ، الواجهة المجهولة هي ممارسة سيئة ، لأن interface هو نوع حقيقي ، قد يضطر المتصل بهذه الوظيفة إلى التصريح عن متغير بهذا النوع. لكن contract مجرد قيد على معلمة النوع ، فالمتصل يلعب دائمًا بنوع حقيقي أو مجرد معامل نوع آخر ، أعتقد أنه من الآمن السماح بالتعاقد بشكل مجهول في تعريف الوظيفة.

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

contract C1(T) {
    T A()
}
contract C2(T) {
    T B()
}
contract C3(T) {
    T C()
}

contract PART(T) {
    C1(T)
    C2(T)
}

contract ALL(T) {
    C1(T)
    C2(T)
    C3(T)
}

func f1(type A PART) (a A) {}

func f2(type A ALL) (a A) {
    f1(a)
}

لقد جربت هذه في مسودة المترجم ، وكلهم لا يمكن التحقق من نوعهم.

func f(type A C1, C2)(x A)

func f1(type A contract C(A1) {
    C1(A)
    C2(A)
}) (x A)

func f2(type A ((type A1) interface {
    I1(A1)
    I2(A1)
})(A)) (x A)

وفقًا للملاحظات الواردة في CL

لن تحصل معلمة النوع المقيدة بالعقود المتعددة على النوع الصحيح المربوط.

أعتقد أن هذا المقتطف الغريب صالح بعد حل هذه المشكلة

func f1(type A C1, _ C2(A)) (x A)

وهنا بعض من أفكاري:

  • إذا تعاملنا مع contract على أنه نوع معلمة النوع ، type a A <=> var a A ، يمكننا إضافة سكر بناء مثل type a { A1(a); A2(a) } لتعريف مجهول يتعاقد بسرعة.
  • بخلاف ذلك ، يمكننا التعامل مع الجزء الأخير من قائمة النوع وهو قائمة المتطلبات ، type a, b, A1(a), A2(a), A3(a, b) ، هذا النمط تمامًا مثل استخدام interface لمعلمات نوع القيد.

bobotu من الشائع الانتقال إلى وظيفة الإنشاء باستخدام التضمين. يبدو من الطبيعي أن تؤلف العقود بنفس الطريقة التي تفعلها مع الهياكل أو الواجهات.

azunymous شخصيًا لا أعرف ما أشعر به حيال تغيير مجتمع Go بأكمله من عوائد متعددة إلى Result ، على الرغم من أنه يبدو أن اقتراح العقود سيمكن هذا إلى حد ما. يبدو أن فريق Go يتجنب التغييرات اللغوية التي تعرض "إحساس" اللغة للخطر ، وهو ما أتفق معه ، ولكن يبدو أن هذا أحد هذه التغييرات.

مجرد فكرة؛ أتساءل عما إذا كان هناك أي رأي حول هذه النقطة.

tooolbox لا أعتقد أنه من الممكن في الواقع استخدام شيء مثل نوع Result على نطاق واسع خارج الحالة التي تمر فيها فقط بالقيم ، إلا إذا كان لديك كتلة عامة Result s ووظائف كل مجموعة من المعلمات وأنواع الإرجاع. مع العديد من الوظائف المرقمة أو باستخدام الإغلاق ، ستفقد إمكانية القراءة.

أعتقد أنه من المرجح أن ترى شيئًا معادلاً لـ errWriter حيث ستستخدم شيئًا كهذا في بعض الأحيان عندما يكون مناسبًا ، يسمى بحالة الاستخدام.

أنا شخصياً لا أعرف كيف أشعر حيال تغيير مجتمع Go بأكمله من عوائد متعددة إلى نتيجة

لا أعتقد أن هذا سيحدث. كما قال azunymous ، فإن الكثير من الوظائف لها أنواع إرجاع متعددة وخطأ ولكن النتيجة لا يمكن أن تحتوي على كل تلك القيم الأخرى التي تم إرجاعها في نفس الوقت. ليس تعدد الأشكال البارامترى هو الميزة الوحيدة اللازمة لعمل شيء كهذا ؛ ستحتاج أيضًا إلى tuples والتدمير.

شكرا! كما قلت ، ليس شيئًا كنت أفكر فيه بعمق ولكن من الجيد أن أعرف أن قلقي في غير محله.

tooolbox لا أهدف إلى تقديم بعض الصيغ الجديدة ، فالمشكلة الرئيسية هنا هي عدم القدرة على استخدام عقد مجهول تمامًا مثل الواجهة المجهولة.

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

func f1(type A, B, C, D contract {
    C1(A)
    C2(A, B)
    C3(A, C)
}) (a A, b B, c C, d D)

// Or a more verbose style

func f2(type A, B, C, D (contract (_A, _B, _C) {
    C1(_A)
    C2(_A, _B)
    C3(_A, _C)
})(A, B, C)) (a A, b B, c C, d D)

IMO ، هذا امتداد طبيعي للصيغة الحالية. لا يزال هذا عقدًا في نهاية قائمة معلمات النوع ، وما زلنا نستخدم التضمين لتكوين الوظائف. إذا كان بإمكان Go توفير بعض السكر لإنشاء معلمات نوع العقد تلقائيًا مثل المقتطف الأول ، فستكون الشفرة أسهل في القراءة والكتابة.

func fff(type A C1(A), B C2(B, A), C C3(B, C, A)) (a A, b B, c C)

// is more verbose than

func fff(type A, B, C contract {
    C1(A)
    C2(B, A)
    C3(B, C, A)
}) (a A, b B, c C)

أواجه بعض المشاكل عندما أحاول تنفيذ مكرر كسول دون استدعاء الطريقة الديناميكية ، تمامًا مثل Rust's Iterator.

أريد تحديد عقد بسيط Iterator

contract Iterator(T, E) {
    T Next() (E, bool)
}

لأن Go ليس لديه مفهوم type member ، أحتاج إلى التصريح عن E كمعامل نوع الإدخال.

وظيفة لجمع النتائج

func Collect(type I, E Iterator) (input I) []E {
    var results []E
    for {
        e, ok := input.Next()
        if !ok {
            return results
        }
        results = append(results, e)
    }
}

وظيفة لتعيين العناصر

contract MapIO(I, E, O, R) {
    Iterator(I, E)
    Iterator(O, R)
}

func Map(type I, E, O, R MapIO) (input I, f func (e E) R) O {
    return &lazyIterator(I, E, R){
        parent: input,
        f:      f,
    }
}

لدي مشكلتان هنا:

  1. لا يمكنني إرجاع lazyIterator هنا ، يقول المترجم cannot convert &(lazyIterator(I, E, R) literal) (value of type *lazyIterator(I, E, R)) to O .
  2. أحتاج إلى إعلان عقد جديد باسم MapIO والذي يحتاج إلى 4 أسطر بينما Map يحتاج فقط إلى 6 سطور. من الصعب على المستخدمين قراءة الكود.

لنفترض أنه يمكن التحقق من نوع Map ، آمل أن أتمكن من كتابة شيء مثل

type staticIterator(type E) struct {
    elem []E
}

func (it *(staticIterator(E))) Next() (E, bool) { panic("todo") }

func main() {
    inpuit := &staticIterator{
        elem: []int{1, 2, 3, 4},
    }
    mapped := Map(input, func (i int) float32 { return float32(i + 1) })
    fmt.Printf("%v\n", Collect(mapped))
}

للأسف ، يشكو المترجم من أنه لا يستطيع استنتاج الأنواع. يتوقف عن الشكوى من هذا بعد أن أقوم بتغيير الرمز إلى

func main() {
    input := &staticIterator(int){
        elem: []int{1, 2, 3, 4},
    }
    mapped := Map(*staticIterator(int), int, *lazyIterator(*staticIterator(int), int, float32), float32)(input, func (i int) float32 { return float32(i + 1) })
    result := Collect(*lazyIterator(*staticIterator(int), int, float32), float32)(mapped)
    fmt.Printf("%v\n", result)
}

من الصعب جدًا قراءة الكود وكتابته ، وهناك عدد كبير جدًا من تلميحات الكتابة المكررة.

راجع للشغل ، سيصاب المترجم بالذعر من:

panic: interface conversion: ast.Expr is *ast.ParenExpr, not *ast.CallExpr

goroutine 1 [running]:
go/go2go.(*translator).instantiateTypeDecl(0xc000251950, 0x0, 0xc0001af860, 0xc0001a5dd0, 0xc00018ac90, 0x1, 0x1, 0xc00018bca0, 0x1, 0x1, ...)
        /home/tuzi/go-tip/src/go/go2go/instantiate.go:191 +0xd49
go/go2go.(*translator).translateTypeInstantiation(0xc000251950, 0xc000189380)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:671 +0x3f3
go/go2go.(*translator).translateExpr(0xc000251950, 0xc000189380)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:518 +0x501
go/go2go.(*translator).translateExpr(0xc000251950, 0xc0001af990)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:496 +0xe3
go/go2go.(*translator).translateExpr(0xc000251950, 0xc00018ace0)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:524 +0x1c3
go/go2go.(*translator).translateExprList(0xc000251950, 0xc00018ace0, 0x1, 0x1)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:593 +0x45
go/go2go.(*translator).translateStmt(0xc000251950, 0xc000189840)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:419 +0x26a
go/go2go.(*translator).translateBlockStmt(0xc000251950, 0xc00018d830)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:380 +0x52
go/go2go.(*translator).translateFuncDecl(0xc000251950, 0xc0001c0390)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:373 +0xbc
go/go2go.(*translator).translate(0xc000251950, 0xc0001b0400)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:301 +0x35c
go/go2go.rewriteAST(0xc000188280, 0xc000188240, 0x0, 0x0, 0xc0001f6280, 0xc0001b0400, 0x1, 0xc000195360, 0xc0001f6280)
        /home/tuzi/go-tip/src/go/go2go/rewrite.go:122 +0x101
go/go2go.RewriteBuffer(0xc000188240, 0x7ffe07d6c027, 0xa, 0xc0001ec000, 0x4fe, 0x6fe, 0x0, 0xc00011ed58, 0x40d288, 0x30, ...)
        /home/tuzi/go-tip/src/go/go2go/go2go.go:132 +0x2c6
main.translateFile(0xc000188240, 0x7ffe07d6c027, 0xa)
        /home/tuzi/go-tip/src/cmd/go2go/translate.go:26 +0xa9
main.main()
        /home/tuzi/go-tip/src/cmd/go2go/main.go:64 +0x434

أجد أيضًا أنه من المستحيل تحديد وظيفة تعمل مع إرجاع نوع معين Iterator .

type User struct {}

func UpdateUsers(type A Iterator(A, User)) (it A) bool { 
    // Access `User`'s field.
}

// And I found this may be possible

contract checkInts(A, B) {
    Iterator(A, B)
    B int
}

func CheckInts(type A, B checkInts) (it A) bool { panic("todo") }

يمكن أن يعمل المقتطف الثاني في بعض السيناريوهات ، ولكن من الصعب فهمه ويبدو نوع B غير المستخدم غريبًا.

في الواقع ، يمكننا استخدام واجهة لإكمال هذه المهمة.

type Iterator(type E) interface {
    Next() (E, bool)
}

أحاول فقط استكشاف مدى تعبير تصميم Go.

راجع للشغل ، رمز الصدأ الذي أشير إليه هو

fn main() {
    let input = vec![1, 2, 3, 4];
    let mapped = input.iter().map(|x| x * 3);
    let result = f(mapped);
    println!("{:?}", result.collect::<Vec<_>>());
}

fn f<I: Iterator<Item = i32>>(it: I) -> impl Iterator<Item = f32> {
    it.map(|i| i as f32 * 2.0)
}

// The definition of `map` in stdlib is
pub struct Map<I, F> {
    iter: I,
    f: F,
}

fn map<B, F: FnMut(Self::Item) -> B>(self, f: F) -> Map<Self, F>

فيما يلي تلخيص لـ https://github.com/golang/go/issues/15292#issuecomment -633233479

  1. قد نحتاج إلى شيء للتعبير عن existential type مقابل func Collect(type I, E Iterator) (input I) []E

    • لا يمكن استنتاج النوع الفعلي للمعامل الكمي العالمي E ، لأنه ظهر فقط في قائمة المرتجعات. نظرًا لعدم وجود type member لجعل E وجوديًا افتراضيًا ، أعتقد أننا قد نواجه هذه المشكلة في العديد من الأماكن.

    • ربما يمكننا استخدام أبسط existential type مثل حرف البدل Java ? لحل نوع الاستدلال func Consume(type I, E Iterator) (input I) . يمكننا استخدام _ لاستبدال E ، func Consume(type I Iterator(I, _)) (input I) .

    • لكنها لا تزال غير قادرة على حل مشكلة الاستدلال النوع لـ Collect ، لا أعرف ما إذا كان من الصعب استنتاج E ، لكن يبدو أن Rust قادر على القيام بذلك.

    • أو يمكننا استخدام _ كعنصر نائب للأنواع التي يمكن للمجمع أن يستنتجها ، وملء الأنواع المفقودة يدويًا ، مثل Collect(_, float32) (...) للقيام بالتجميع على مكرر من float32.

  1. نظرًا لعدم القدرة على إرجاع existential type ، لدينا أيضًا مشاكل لأشياء مثل func Map(type I, E, O, R MapIO) (input I, f func (e E) R) O

    • يدعم الصدأ هذا باستخدام impl Iterator<E> . إذا كان بإمكان Go توفير شيء مثل هذا ، فيمكننا إعادة مكرر جديد بدون ملاكمة ، وقد يكون مفيدًا لبعض التعليمات البرمجية ذات الأهمية الحاسمة للأداء.

    • أو يمكننا ببساطة إرجاع كائن محاصر ، فهذه هي الطريقة التي يحل بها Rust هذه المشكلة قبل أن يدعم existential type عند موضع الإرجاع. لكن السؤال هو العلاقة بين contract و interface ، فربما نحتاج إلى تحديد بعض قواعد التحويل والسماح للمترجم بتحويلها تلقائيًا. بخلاف ذلك ، قد نحتاج إلى تحديد contract و interface بالطرق المماثلة لهذه الحالة.

    • بخلاف ذلك ، يمكننا فقط استخدام CPS لنقل معلمة النوع من موضع الإرجاع إلى قائمة الإدخال. على سبيل المثال ، func Map(type I, E, O, R MapIO) (input I, f func (e E) R, f1 func (outout O)) . لكن هذا لا فائدة منه من الناحية العملية ، لأنه ببساطة يجب علينا كتابة النوع الفعلي O عندما نمرر دالة إلى Map .

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

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

لقد رأيت للتو ورقة بحثية عن وزن الريشة تم نشرها على Arxiv وهي عبارة عن تعاون بين فريق Go والعديد من الخبراء في نظرية النوع. يبدو أن هناك المزيد من الأوراق المخطط لها في هذا السياق.

لمتابعة تعليقي السابق ، أجرى Phil Wadler من شهرة Haskell وأحد المؤلفين على الورقة محاضرة مجدولة في "Featherweight Go" يوم الاثنين ، 8 يونيو @ 7am PDT / 10am EDT: http://chalmersfp.org/ . رابط يوتيوب

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

في غياب ذلك ، أعتقد أن التركيب اللغوي هو ببساطة ما يراه الناس أولاً ويعلقون عليه أولاً. قد تكون مشكلة كبيرة ، قد لا تكون كذلك. لا نعرف بعد.

لمتابعة تعليقي السابق ، أجرى فيل وادلر من Haskell الشهرة وأحد المؤلفين على الورقة محاضرة مجدولة في "Featherweight Go" يوم الاثنين

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

وتجدر الإشارة إلى أن بايك طلب من وادلر المشاركة ؛ يبدو أنهم يعرفون بعضهم البعض من Bell Labs. بالنسبة لي ، تمتلك Haskell مجموعة مختلفة جدًا من القيم والنماذج ، ومن المثير للاهتمام أن نرى كيف يفكر (منشئها؟ مصمم رئيسي؟) في Go and genics in Go.

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

يبدو أن فريق Go يعمل على أو لديه نموذج أولي لهذا! سيكون ذلك ممتعًا. في غضون ذلك ، كيف سيبدو هذا؟

package graph

type Node(type e) interface{
    Edges() []e
}

type Edge(type n) interface{
    Nodes() (from n, to n)
}

type Graph(type n Node(e), e Edge(n)) struct { ... }
func New(type n Node(e), e Edge(n))(nodes []n) *Graph(n, e) { ... }
func (g *Graph(type n Node(e), e Edge(n))) ShortestPath(from, to n) []e { ... }

هل لدي هذا الحق؟ اعتقد ذلك. إذا فعلت ... ليس سيئًا ، في الواقع. لا يحل مشكلة تقطع الأقواس تمامًا ، ولكن يبدو أنه تم تحسينه بطريقة ما. هدأت بعض الاضطرابات التي لا اسم لها بداخلي.

ماذا عن مثال المكدس من urandom ؟ (الاسم المستعار interface{} إلى Any وباستخدام قدر معين من نوع الاستدلال.)

package main

type Any interface{}

type Stack(type t Any) []t

func (s Stack(type t Any)) Peek() t {
    return s[len(s)-1]
}

func (s *Stack(type t Any)) Pop() {
    *s = (*s)[:len(*s)-1]
}

func (s *Stack(type t Any)) Push(value t) {
    *s = append(*s, value)
}

type StackIterator(type t Any) struct{
    stack Stack(t)
    current int
}

func (s *Stack(type t Any)) Iter() *StackIterator(t) {
    it := StackIterator(t){stack: *s, current: len(*s)}

    return &it
}

func (i *StackIterator(type t Any)) Next() (bool) { 
    i.current--

    if i.current < 0 { 
        return false
    }

    return true
}

func (i *StackIterator(type t Any)) Value() t {
    if i.current < 0 {
        var zero t
        return zero
    }

    return i.stack[i.current]
}

type Iterator(type t Any) interface {
    Next() bool
    Value() t
}

func Map(type t Any, u Any)(it Iterator(t), mapF func(t) u) Iterator(u) {
    return mapIt(t, u){it, mapF}
}

type mapIt(type t Any, u Any) struct {
    parent Iterator(t)
    mapF func(t) u
}

func (i mapIt(type t Any, u Any)) Next() bool {
    return i.parent.Next()
}

func (i mapIt(type t Any, u Any)) Value() u {
    return i.mapF(i.parent.Value())
}

func Filter(type t Any)(it Iterator(t), predicate func(t) bool) Iterator(t) {
    return filter(t){it, predicate}
}

type filter(type t Any) struct {
    parent Iterator(t)
    predicateF func(t) bool
}

func (i filter(type t Any)) Next() bool {
    if !i.parent.Next() {
        return false
    }

    n := true
    for n && !i.predicateF(i.parent.Value()) {
        n = i.parent.Next()
    }

    return n
}

func (i filter(type t Any)) Value() t {
    return i.parent.Value()
}

func Distinct(type t comparable)(it Iterator(t)) Iterator(t) {
    return distinct(t){it, map[t]struct{}{}}
}

type distinct(type t comparable) struct {
    parent Iterator(t)
    set map[t]struct{}
}

func (i distinct(type t Any)) Next() bool {
    if !i.parent.Next() {
        return false
    }

    n := true
    for n {
        _, ok := i.set[i.parent.Value()]
        if !ok {
            i.set[i.parent.Value()] = struct{}{}
            break
        }
        n = i.parent.Next()
    }


    return n
}

func (i distinct(type t Any)) Value() t {
    return i.parent.Value()
}

func ToSlice(type t Any)(it Iterator(t)) []t {
    var res []t

    for it.Next() {
        res = append(res, it.Value())
    }

    return res
}

func ToSet(type t comparable)(it Iterator(t)) map[t]struct{} {
    var res map[t]struct{}

    for it.Next() {
        res[it.Value()] = struct{}{}
    }

    return res
}

func Reduce(type t Any)(it Iterator(t), id t, acc func(a, b t) t) t {
    for it.Next() {
        id = acc(id, it.Value())
    }

    return id
}

func main() {
    var stack Stack(string)
    stack.Push("foo")
    stack.Push("bar")
    stack.Pop()
    stack.Push("alpha")
    stack.Push("beta")
    stack.Push("foo")
    stack.Push("gamma")
    stack.Push("beta")
    stack.Push("delta")


    var it Iterator(string) = stack.Iter()

    it = Filter(string)(it, func(s string) bool {
        return s == "foo" || s == "beta" || s == "delta"
    })

    it = Map(string, string)(it, func(s string) string {
        return s + ":1"
    })

    it = Distinct(string)(it)

    println(Reduce(it, "", func(a, b string) string {
        if a == "" {
            return b
        }
        return a + ":" + b
    }))


}

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

انطباعات:

  • أنا أحب أن يكون نمط معلمات النوع في الأساليب يتطابق مع تعريفات النوع. أي قول "اكتب" وذكر الأنواع صراحة ، ("type" param paramType, param paramType...) بدلاً من (param, param) . إنها تجعلها متسقة بصريًا ، لذا فإن الكود أكثر قابلية للنظرة.
  • أحب أن تكون معلمات النوع صغيرة. تشير المتغيرات أحادية الحرف في Go إلى استخدام محلي للغاية ، ولكن الكتابة بالأحرف الكبيرة تعني أنه تم تصديرها ، وتبدو متناقضة عند وضعها معًا. تبدو الأحرف الصغيرة أفضل حيث يتم تحديد نطاق معلمات النوع للوظيفة / النوع.

حسنًا ، ماذا عن العقود؟

حسنًا ، الشيء الوحيد الذي أحبه هو أن Stringer لم يمسها ؛ لن يكون لديك واجهة Stringer وعقد Stringer .

type Stringer interface {
    String() string
}

func Stringify(type t Stringer)(s []t) (ret []string) {
    for _, v := range s {
        ret = append(ret, v.String())
    }
    return ret
}

لدينا أيضًا المثال viaStrings :

type ToString interface {
    Set(string)
}

type FromString interface {
    String() string
}

func SetViaStrings(type to ToString, from FromString)(s []from) []to {
    r := make([]to, len(s))
    for i, v := range s {
        r[i].Set(v.String())
    }
    return r
}

مثير للاهتمام. لست متأكدًا بنسبة 100٪ مما أكسبنا إياه العقد في هذه الحالة. ربما كان جزءًا منها هو القاعدة التي تنص على أن الوظيفة يمكن أن تحتوي على معلمات نوع متعددة ولكن عقد واحد فقط.

تمت تغطية المساواة في الورقة / الحديث:

contract equal(T) {
    T Equal(T) bool
}

// becomes

type equal(type t equal(t)) interface{
    Equal(t) bool
}

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

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

8 - الخلاصة

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

يقتصر وزن الريشة Go على مجموعة فرعية صغيرة من Go. نخطط لنموذج لميزات مهمة أخرى مثل التخصيصات والمصفوفات والشرائح والحزم ، والتي سنطلق عليها Bantamweight Go ؛ ونموذج من آلية التزامن المبتكرة الخاصة بـ Go والتي تعتمد على "goroutines" وتمرير الرسائل ، والتي سنطلق عليها اسم Cruiserweight Go.

يبدو وزن الريشة رائعًا بالنسبة لي. فكرة ممتازة لإشراك بعض خبراء نظرية النوع. هذا يشبه إلى حد كبير نوع الشيء الذي كنت أدافع عنه في هذا الموضوع.

من الجيد سماع أن خبراء نظرية النوع يعملون بنشاط على هذا!

حتى أنه يبدو مشابهًا (باستثناء البنية المختلفة قليلاً) لاقتراحي القديم "العقود عبارة عن واجهات" https://github.com/cosmos72/gomacro/blob/master/doc/generics-cti.md

tooolbox
من خلال السماح بطرق ذات قيود مختلفة عن النوع الفعلي (بالإضافة إلى أنواع مختلفة تمامًا) ، يفتح FGG عددًا قليلاً من الاحتمالات التي لم تكن ممكنة في مسودة العقود الحالية. على سبيل المثال ، مع FGG ، يجب أن يكون المرء قادرًا على تحديد كل من المكرر والمكرر العكسي ، وأن يكون لديه المكرر الوسيط والنهائي (الخريطة ، يقلل الفلتر) يدعم كلاهما (على سبيل المثال ، مع Next () و NextFromBack () للعكسات) ، اعتمادًا على ماهية مكرر الأصل.

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

  1. يسمح بإضافة معلمات نوع جديدة إلى الأساليب (كما هو موضح في مثال "القائمة والخرائط" في الحديث). AFAICT سيسمح هذا بتنفيذ Functor (في الواقع ، هذا هو مثال قائمته ، إذا لم أكن مخطئًا) ، Monad وأصدقائهم. لا أعتقد أن هذه الأنواع المحددة مثيرة للاهتمام لجوفر ، ولكن هناك حالات استخدام مثيرة للاهتمام لهذا (على سبيل المثال ، من المحتمل أن يستفيد منفذ Go من Flume أو مفاهيم مماثلة). أنا شخصياً أشعر أنه تغيير إيجابي ، على الرغم من أنني لا أرى حتى الآن ما هي الآثار المترتبة على التفكير وما شابه. أشعر أن إعلانات الطريقة التي تستخدم هذا بدأت في الحصول على صعوبة في القراءة - خاصةً إذا كان يجب أيضًا إدراج معلمات النوع من النوع العام في جهاز الاستقبال.
  2. يسمح لمعلمات النوع بأن يكون لها حدود أكثر صرامة على طرق الأنواع العامة أكثر من النوع نفسه. كما ذكر آخرون ، يتيح لك هذا أن يكون لديك نفس النوع العام لتنفيذ طرق مختلفة ، اعتمادًا على الأنواع التي تم إنشاء مثيل لها بها. لست متأكدًا من أن هذا تغيير جيد شخصيًا. يبدو أنها وصفة للارتباك ، حيث ينتهي الأمر بامتلاك Map(int, T) بأساليب لا تتوفر في Map(string, T) . على الأقل ، يحتاج المترجم إلى تقديم رسائل خطأ ممتازة ، إذا حدث شيء كهذا. في هذه الأثناء ، تبدو الفائدة صغيرة نسبيًا - لا سيما بالنظر إلى أن العامل المحفز من الحديث (تجميع منفصل) ليس وثيق الصلة بـ Go: حيث يجب الإعلان عن الأساليب في نفس الحزمة مثل نوع جهاز الاستقبال الخاص بهم وبالنظر إلى أن الحزم هي الوحدة من التحويل البرمجي ، لا يمكنك حقًا تمديد النوع بشكل منفصل. أعلم أن الحديث عن التجميع هو طريقة ملموسة للحديث عن فائدة أكثر تجريدًا ، لكن مع ذلك ، لا أشعر أن هذه الفائدة تساعد كثيرًا.

إنني أتطلع إلى الخطوات التالية ، على أي حال :)

أعتقد أنه من المهم أن نضع في اعتبارنا أن FGG ليس بشكل قاطع حيث سينتهي الأمر بالأدوية الجنيسة في Go.

@ ميروفيوس لماذا تقول ذلك؟

arl
إن FG عبارة عن ورقة بحثية حول ما _ يمكن _ القيام به. لم يقل أحد صراحة أن هذه هي الطريقة التي ستعمل بها تعدد الأشكال في Go في المستقبل. على الرغم من إدراج مطوري 2 Go الأساسيين كمؤلفين في البحث ، فإن هذا لا يعني أنه سيتم تنفيذ ذلك في Go.

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

نعم ، نقطة جيدة جدا.

سألاحظ أيضًا أن Wadler يعمل كجزء من فريق ، وأن المنتج الناتج يبني عليه وهو قريب جدًا من اقتراح العقود ، وهو نتيجة سنوات من العمل من المطورين الأساسيين.

من خلال السماح بطرق ذات قيود مختلفة عن النوع الفعلي (بالإضافة إلى أنواع مختلفة تمامًا) ، يفتح FGG عددًا قليلاً من الاحتمالات التي لم تكن ممكنة في مسودة العقود الحالية. ...

urandom أنا أشعر بالفضول كيف يبدو هذا المثال التكراري ؛ هل تمانع في رمي شيء معا؟

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

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

ربما يمكن أن يساعد gofmt بطريقة ما؟ ربما نحتاج للذهاب إلى خطوط متعددة. ربما يستحق اللعب معه.

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

أرى ما تقوله @ Merovius

تم استدعاؤه من قبل Wadler على أنه اختلاف ، وهو يتيح له حل مشكلة التعبير الخاصة به ، لكنك تشير بشكل جيد إلى أن حزم Go المحكم يبدو أنها تحد مما يمكنك / يجب أن تفعله بهذا. هل يمكنك التفكير في أي حالة فعلية حيث تريد القيام بذلك؟

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

أرى ما تقوله @ Merovius

تم استدعاؤه من قبل Wadler على أنه اختلاف ، وهو يتيح له حل مشكلة التعبير الخاصة به ، لكنك تشير بشكل جيد إلى أن حزم Go المحكم يبدو أنها تحد مما يمكنك / يجب أن تفعله بهذا. هل يمكنك التفكير في أي حالة فعلية حيث تريد القيام بذلك؟

ومن المفارقات ، أن فكرتي الأولى كانت أنه يمكن استخدامها لحل بعض التحديات الموضحة في هذه المقالة: https://blog.merovius.de/2017/07/30/the-trouble-with-optional-interfaces.html

تضمين التغريدة

بشكل منفصل ، أنا مهتم بما يمكن أن تفعله الأدوية الجنسية بخلاف الخرائط والفلاتر والأشياء الوظيفية ،

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

  1. توفير وصول معمم إلى أي هيكل بيانات تابع لجهة خارجية. يمكن جعل مثال map و filter للعمل فوق أشجار الأدوية ، أو الخرائط المصنفة ، أو… أيضًا. لذلك ، يمكنك تبديل ما تم تعيينه للحصول على مزيد من القوة. والأهم من ذلك
  2. يمكنك مبادلة كيفية رسمها. على سبيل المثال ، يمكنك إنشاء إصدار من Compose يمكنه إنتاج العديد من goroutines لكل وظيفة وتشغيلها بشكل متزامن باستخدام القنوات. سيجعل هذا من السهل تشغيل خطوط أنابيب معالجة البيانات المتزامنة وتوسيع رقبة الزجاجة تلقائيًا ، بينما تحتاج فقط إلى كتابة func(A) B s. أو يمكنك وضع نفس الوظائف في إطار عمل يقوم بتشغيل آلاف النسخ من البرنامج في مجموعة ، وجدولة دفعات من البيانات عبرها (هذا ما أشرت إليه عندما قمت بالربط بـ Flume أعلاه).

لذلك ، في حين أن القدرة على كتابة Map و Filter و Reduce قد تبدو مملة على السطح ، فإن نفس الأساليب تفتح بعض الاحتمالات المثيرة حقًا لجعل الحوسبة القابلة للتطوير أسهل.

تضمين التغريدة

ومن المفارقات ، أن فكرتي الأولى كانت أنه يمكن استخدامها لحل بعض التحديات الموضحة في هذه المقالة: https://blog.merovius.de/2017/07/30/the-trouble-with-optional-interfaces.html

إنها فكرة مثيرة للاهتمام وتشعر بالتأكيد كما ينبغي. لكني لا أرى كيف بعد. إذا أخذت المثال ResponseWriter ، فيبدو أن هذا قد يمكّنك من كتابة أغلفة عامة آمنة من النوع ، بطرق مختلفة اعتمادًا على ما يدعمه ResponseWriter المغلف. ولكن ، حتى إذا كان بإمكانك استخدام حدود مختلفة على طرق مختلفة ، فلا يزال عليك تدوينها. لذلك ، في حين أنه يمكن أن يجعل الوضع آمنًا بمعنى أنك لا تضيف طرقًا لا تدعمها ، ما زلت بحاجة إلى تعداد جميع الطرق التي يمكنك دعمها ، لذلك قد تظل الأدوات المتوسطة تخفي بعض الواجهات الاختيارية فقط من خلال عدم المعرفة عنها. وفي الوقت نفسه ، يمكنك أيضًا (حتى بدون هذه الميزة) القيام بذلك

type Middleware (type RW http.ResponseWriter) struct {
    RW
}

واستبدل الطرق الانتقائية التي تهتم بها - وقم بترقية جميع الطرق الأخرى RW . لذلك ليس عليك حتى كتابة أغلفة وشفافية حتى تحصل على تلك الطرق التي لم تكن تعرفها.

لذلك ، بافتراض أننا حصلنا على طرق مُروَّجة لمعلمات النوع المضمنة في الهياكل العامة (وآمل أن نفعل ذلك) ، يبدو أن المشكلات قد تم حلها بشكل أفضل بهذه الطريقة.

أعتقد أن الحل المحدد لـ http.ResponseWriter يشبه الأخطاء . ليس هناك حاجة لتغيير اللغة ، فقط إضافة مكتبة لإنشاء طريقة قياسية لتغليف ResponseWriter وطريقة للاستعلام عما إذا كان أي من ResponseWriters في سلسلة يمكنه التعامل مع egwPush. أنا متشكك في أن الأدوية الجنسية ستكون مناسبة تمامًا لشيء كهذا لأن بيت القصيد هو أن يكون لديك خيار وقت التشغيل بين الواجهات الاختيارية ، على سبيل المثال ، يتوفر Push فقط في http2 وليس إذا كنت أقوم بتدوير خادم http1 محلي مطور.

بالنظر إلى Github ، لا أعتقد أنني قد خلقت مشكلة لهذه الفكرة ، لذا ربما سأفعل ذلك الآن.

تحرير: # 39558.

tooolbox
أعتقد أنه سيبدو مثل هذا ، جنبًا إلى جنب مع رمز monomorphisation الداخلي:

package iter

type Any interface{}

type Iterator(type T Any) interface {
    Next() bool
    Value() T
}

type ReversibleIterator(type T Any) interface {
    Iterator(T)
    NextBack() bool
}

type mapIt(type I Iterator(T), T Any, U Any) struct {
    parent I
    mapF func(T) U
}

func (i mapIt(type I Iterator(T))) Next() bool {
    return i.parent.Next()
}

func (i mapIt(type I Iterator(T), T Any, U Any)) Value() U { 
    return i.mapF(i.parent.Value())
}

func (i mapIt(type I ReversibleIterator(T))) NextBack() bool { 
    return i.parent.NextBack()
}

// Monomorphisation
type mapIt<OnlyForward, int, float64> struct {
    parent OnlyForward,
    mapF func(int) float64
}

func (i mapIt<OnlyForward, int, float64>) Next() bool {
    return i.parent.Next()
}

func (i mapIt<OnlyForward, int, float64>) Value() float64 {
    return i.mapF(i.parent.Value())
}

type mapIt<Slice, int, string> struct {
    parent Slice,
    mapF func(int) string
}

func (i mapIt<Slice, int, string>) Next() bool {
    return i.parent.Next()
}

func (i mapIt<Slice, int, string>) Value() string {
    return i.mapF(i.parent.Value())
}

func (i mapIt<Slice, int, string>) NextBack() bool {
    return i.parent.NextBack()
}



أعتقد أنه سيبدو مثل هذا ، جنبًا إلى جنب مع رمز monomorphisation الداخلي:

FWIW هذه تغريدة لي من بضع سنوات ماضية لاستكشاف كيف يمكن أن يعمل التكرارات في Go with genics. إذا قمت باستبدال عالمي لاستبدال <T> بـ (type T) ، فلديك شيء ليس بعيدًا عن الاقتراح الحالي: https://twitter.com/rogpeppe/status/425035488425037824

FWIW ، يجب توضيح أن هذا نوع من بيع "الخرائط والفلاتر والأشياء الوظيفية" باختصار. أنا شخصياً لا أريد الخريطة والتصفية على هياكل البيانات المضمنة في الكود الخاص بي ، على سبيل المثال (أفضل حلقات for-loops). ولكن يمكن أن يعني أيضًا ...

أرى وجهة نظرك ولا أعارضها ، ونعم سنستفيد من الأشياء التي تغطيها أمثلتك.
لكن ما زلت أتساءل عن كيفية تأثر شيء مثل k8s ، أو قاعدة بيانات أخرى ذات أنواع بيانات "عامة" حيث لا تكون أنواع الإجراءات التي يتم تنفيذها خرائط أو مرشحات ، أو على الأقل تتجاوز ذلك. أتساءل عن مدى فعالية العقود أو FGG في زيادة أمان النوع والأداء في تلك الأنواع من السياقات.

هل تتساءل عما إذا كان بإمكان أي شخص الإشارة إلى قاعدة بيانات ، نأمل أن تكون أبسط من k8s ، والتي تناسب هذا النوع من الفئة؟

تضمين التغريدة لذلك إذا قمت بإنشاء مثيل mapIt باستخدام parent الذي ينفذ ReversibleIterator فإن mapIt يحتوي على طريقة NextBack() وإذا لم يكن الأمر كذلك ، فلن يكون ' ر. هل أقرأ هذا صحيح؟

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

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

على أي حال ، مثير جدا للاهتمام.

لا يعني ذلك أنني سأفعل هذا ، لكنني أفترض أن هذا سيكون ممكنًا:

type OverrideX interface {
    GetX() int
}

type OverrideY interface {
    GetY() int
}

type Inheritor(type child Any) struct {
    Parent
    c child
}

func (i Inheritor(type child OverrideX)) GetX() int {
    return i.c.GetX()
}

func (i Inheritor(type child OverrideY)) GetY() int {
    return i.c.GetY()
}

type Parent struct {
    x, y int
}

func (p Parent) GetX() int {
    return p.x
}

func (p Parent) GetY() int {
    return p.y
}

type Child struct {
    x int
}

func (c Child) GetX() int {
    return c.x
}

func main() {
    i := Inheritor(Child){Parent{5, 6}, Child{3}}
    x, y := i.GetX(), i.GetY() // 3, 6
}

مرة أخرى ، في الغالب مزحة ، لكنني أعتقد أنه من الجيد استكشاف حدود ما هو ممكن.

تحرير: حسنًا ، يُظهر كيف يمكنك الحصول على مجموعات طرق مختلفة اعتمادًا على معلمة النوع ، ولكنها تنتج نفس التأثير تمامًا مثل تضمين Parent في Child . مرة أخرى ، مثال سخيف ؛)

لست من أشد المعجبين بامتلاك طرق لا يمكن تسميتها إلا في حالة وجود نوع معين. بالنظر إلى مثالtooolbox ، فمن المحتمل أن يكون اختبارًا مؤلمًا نظرًا لحقيقة أن بعض الطرق قابلة للاستدعاء فقط في حالة وجود طفل معين - من المحتمل أن يفوت المختبر بعض الحالات. كما أنه من غير الواضح إلى حد كبير الطرق المتاحة ولا تتطلب Go من IDE تقديم اقتراحات. ومع ذلك ، يمكنك تنفيذ ذلك باستخدام النوع المعطى من قبل البنية فقط عن طريق إجراء تأكيد النوع في الطريقة.

func (i Inheritor(type child Any)) GetX() int {
    if c, ok := i.c.(OverrideX); ok {
        return c.GetX()
    }
    return i.Parent.GetX()
}

func (i Inheritor(type child Any)) GetY() int {
    if c, ok := i.c.(OverrideY); ok {
        return c.GetY()
    }
    return i.Parent.GetY()
} 

هذا الرمز أيضًا آمن من النوع ، وواضح ، وسهل الاختبار ، ومن المحتمل أن يعمل بشكل مماثل للأصل دون التباس.

تضمين التغريدة
هذا المثال المعين هو نوع آمن ، لكن البعض الآخر ليس كذلك ، وسيتطلب ذعر وقت التشغيل مع أنواع غير متوافقة.

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

urandom لم أقصد بالضرورة بهذا المثال المحدد الذي قد يغيب عن شخص ما قضية - فهو قصير جدًا. قصدت أنه عندما يكون لديك عدد كبير من الطرق يمكن استدعاؤها فقط نظرًا لأنواع معينة. لذلك أقف بعدم استخدام التصنيف الفرعي (كما أحب أن أسميه). بل إنه من الممكن حل "مشكلة التعبير" بدون استخدام تأكيدات النوع أو التصنيف الفرعي. إليك الطريقة:

type Any interface {}

type Evaler(type t Any) interface {
    Eval() t
}

type Num struct {
    value int
}

func (n Num) Eval() int {
    return n.value
}

type Plus(type a Evaler(type t Any)) struct {
    left a
    right a
}

func (p Plus(type a Evaler(type t Any)) Eval() t {
    return p.left.Eval() + p.right.Eval()
}

func (p Plus(type a Evaler(type t Any)) String() string {
    return fmt.Sprintf("(%s+%s)", p.left, p.right)
}

type Expr interface {
    Evaler
    fmt.Stringer
}

func main() {
    var e Expr = Plus(Num){Num{1}, Num{2}}
    var v int = e.Eval() // 3
    var s string = e.String() // "(1+2)"
}

يجب اكتشاف أي إساءة استخدام لطريقة Eval في وقت التجميع نظرًا لحقيقة أنه لا يُسمح باستدعاء Eval on Plus بنوع لا يطبق الإضافة. على الرغم من أنه من الممكن استخدام String () بشكل غير صحيح (ربما إضافة هياكل) ، فإن الاختبار الجيد يجب أن يكتشف تلك الحالات. وعادة ما يحتضن Go البساطة على "الصواب". الشيء الوحيد الذي يتم اكتسابه من خلال التصنيف الفرعي هو المزيد من الارتباك في المستندات وفي الاستخدام. إذا كان بإمكانك تقديم مثال يتطلب تصنيفًا فرعيًا ، فقد أكون أكثر ميلًا للاعتقاد بأنها فكرة جيدة ، لكنني حاليًا غير مقتنع.
تحرير: إصلاح الخطأ وتحسينه

TotallyGamerJet في المثال الخاص بك ، يجب أن تستدعي طريقة String String بشكل متكرر ، وليس Eval

TotallyGamerJet في المثال الخاص بك ، يجب أن تستدعي طريقة String String بشكل متكرر ، وليس Eval

magical
أنا لست متأكدا ماذا يعني لك. نوع بنية Plus هو مُقيِّم لا يضمن استيفاء fmt.Stringer. يتطلب استدعاء السلسلة () على كلا المقيّمين تأكيد نوع وبالتالي لا يكون آمنًا.

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

urandom
انت على حق! من المثير للدهشة أن Sprintf ستقوم بهذا النوع من التأكيد نيابة عنك. لذلك ، يمكنك فقط الإرسال في الحقلين الأيمن والأيسر. على الرغم من أنه لا يزال من الممكن الشعور بالذعر إذا كانت الأنواع الموجودة في Plus لا تنفذ Stringer ، لكنني على ما يرام مع ذلك لأنه من الممكن تجنب الذعر باستخدام الفعل %v لطباعة البنية (سوف تستدعي String ( ) إذا كان متاحًا). أعتقد أن هذا الحل واضح ويجب توثيق أي شكوك أخرى في الكود. لذلك ما زلت غير مقتنع لماذا يعد التصنيف الفرعي ضروريًا.

تضمين التغريدة
أنا شخصياً ما زلت أخفق في معرفة المشكلات التي يمكن أن تنشأ إذا سُمح لها باستخدام طرق ذات قيود مختلفة. الطريقة لا تزال موجودة ، والكود يصف بوضوح الحجج المطلوبة (والمستلم ، في الحالة الخاصة).
تمامًا مثل وجود طريقة ، فإن قبول وسيطة string ، أو مستقبل MyType ، يمكن قراءته بوضوح ولا لبس فيه ، كذلك سيكون التعريف التالي أيضًا:

func (rec MyType(type T SomeInterface(T)) Foo() T

المتطلبات محددة بوضوح في التوقيع نفسه. أي أنه من MyType(type T SomeInterface(T)) ولا شيء غير ذلك.

تغيير https://golang.org/cl/238003 يذكر هذه المشكلة: design: add go2draft-type-parameters.md

تغيير https://golang.org/cl/238241 يذكر هذه المشكلة: content: add generics-next-step article

عيد الميلاد مبكر!

  • أستطيع أن أرى الكثير من الجهد المبذول لجعل وثيقة التصميم سهلة المنال ، فهي تظهر وهي رائعة وتحظى بتقدير كبير.
  • هذا التكرار هو تحسن كبير في عيني ويمكنني أن أرى هذا يتم تنفيذه كما هو.
  • نتفق مع كل المنطق والمنطق.
  • على هذا النحو ، إذا حددت قيدًا لمعامل نوع واحد ، فيجب عليك القيام بذلك للجميع.
  • المقارنة تبدو جيدة.
  • قوائم النوع في الواجهات ليست سيئة ؛ أوافق على أنه أفضل من طرق المشغل ، لكن في رأيي ربما يكون أكبر مجال لمزيد من المناقشة.
  • نوع الاستدلال (لا يزال) رائعًا.
  • يبدو الاستدلال على القيود ذات المعلمات الأحادية الوسيطة وكأنه ذكاء فوق الوضوح.
  • يعجبني "نحن لا ندعي أن هذا بسيط" في مثال الرسم البياني. هذا جيد.
  • يبدو أن (type *T constraint) حلاً جيدًا لمشكلة المؤشر.
  • موافق تمامًا على تغيير func(x(T)) .
  • أعتقد أننا نريد كتابة الاستدلال للحرفية المركبة من الخفاش؟ 😄

شكرا لفريق Go! 🎉

https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#comparable -types-in-limits

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

type ComparableHasher interface {
    comparable
    Hash() uintptr
}

يحتاج لأن يكون

type ComparableHasher interface {
    type comparable
    Hash() uintptr
}

يبدو أن الملعب يشير أيضًا إلى أنه يجب أن يكون type comparable
https://go2goplay.golang.org/p/mhrl0xYsMyj

تحرير: يعمل كل من Ian Lance Taylor و Robert Griesemer على إصلاح أداة go2go (كان خطأ صغيرًا في مترجم go2go ، وليس المسودة. كانت مسودة التصميم صحيحة)

هل كانت هناك أفكار حول تمكين الأشخاص من كتابة جداول التجزئة العامة الخاصة بهم وما شابه ذلك؟ ISTM محدودة للغاية حاليًا (خاصةً بالمقارنة مع الخريطة المضمنة). بشكل أساسي ، تحتوي الخريطة المضمنة على comparable كقيد مفتاح ، ولكن بالطبع ، == و != لا يكفيان لتطبيق جدول التجزئة. واجهة مثل ComparableHasher تنقل فقط مسؤولية كتابة دالة تجزئة إلى المتصل ، ولا تجيب على سؤال كيف ستبدو بالفعل (أيضًا ، ربما لا ينبغي أن يكون المتصل مسؤولاً عن ذلك ؛ كتابة وظائف تجزئة جيدة أمر صعب). أخيرًا ، قد يكون استخدام المؤشرات كمفاتيح مستحيلًا بشكل أساسي - تحويل المؤشر إلى uintptr لاستخدامه كمؤشر من شأنه أن يخاطر GC بتحريك النقطة حولها وبالتالي تغيير الجرافة (باستثناء هذه المشكلة ، الكشف عن func hash(type T comparable)(v T) uintptr محدد مسبقًا

يمكنني قبول عبارة "هذا ليس ممكنًا حقًا" كإجابة ، فأنا أشعر بالفضول لمعرفة ما إذا كنت قد فكرت في الأمر :)

gertcuykens لقد قمت بإصلاح أداة go2go للتعامل مع comparable على النحو المنشود.

Merovius نتوقع أن يقدم الأشخاص الذين يكتبون جدول تجزئة عام وظيفة التجزئة الخاصة بهم ، وربما وظيفة المقارنة الخاصة بهم. عند كتابة دالة التجزئة الخاصة بك ، قد تكون حزمة https://golang.org/pkg/hash/maphash/ مفيدة. أنت محق في أن تجزئة قيمة المؤشر يجب أن تعتمد على القيمة التي يشير إليها هذا المؤشر ؛ لا يمكن أن تعتمد على قيمة المؤشر المحولة إلى uintptr .

لست متأكدًا مما إذا كان هذا يمثل قيودًا على التنفيذ الحالي للأداة ، ولكن محاولة إرجاع نوع عام مقيد بواسطة واجهة تؤدي إلى ظهور خطأ:
https://go2goplay.golang.org/p/KYRFL-vrcUF

لقد نفذت حالة استخدام في العالم الحقيقي كانت لدي بالأمس للأدوية . إنه تجريد عام لخط الأنابيب يسمح بقياس مراحل خط الأنابيب بشكل مستقل ويدعم الإلغاء ومعالجة الأخطاء (لا يتم تشغيله في الملعب ، لأنه يعتمد على errgroup ، لكن تشغيله باستخدام أداة go2go يبدو أنه الشغل). بعض الملاحظات:

  • كانت ممتعة جدا. ساعد وجود مدقق نوع يعمل كثيرًا في الواقع عند التكرار على التصميم ، من خلال ترجمة عيوب التصميم إلى أخطاء في النوع. النتيجة النهائية ~ 100 LOC بما في ذلك التعليقات. لذا ، بشكل عام ، فإن تجربة كتابة التعليمات البرمجية العامة ممتعة ، IMO.
  • تعمل حالة الاستخدام هذه على الأقل بسلاسة مع استدلال النوع ، ولا توجد حاجة إلى عمليات إنشاء فورية صريحة. أعتقد أن هذا يبشر بالخير لتصميم الاستدلال.
  • أعتقد أن هذا المثال سيستفيد من القدرة على الحصول على طرق ذات معلمات نوع إضافية. إن الحاجة إلى وظيفة من المستوى الأعلى لـ Compose تعني أن إنشاء خط الأنابيب يحدث بشكل عكسي - يجب إنشاء المراحل الأخيرة من خط الأنابيب لتمريرها إلى الوظائف التي تبني المراحل السابقة. إذا كان من الممكن أن تحتوي الطرق على معلمات نوع ، فقد يكون لديك Stage ليكون نوعًا ملموسًا ويفعل func (s *Stage(A, B)) Compose(type C)(n int, f func(B) C) *Stage(A, C) . وسيكون بناء خط الأنابيب بالترتيب نفسه الذي تم توصيله به (انظر التعليق في الملعب). قد يكون هناك بالطبع واجهة برمجة تطبيقات أكثر أناقة في المسودة الحالية التي لا أراها - من الصعب إثبات وجود سلبي. سأكون مهتمًا برؤية مثال عملي لذلك.

بشكل عام ، أحب المسودة الجديدة ، FWIW :) يعد إسقاط IMO للعقود تحسنًا وكذلك الطريقة الجديدة لتحديد المشغلين المطلوبين عبر قوائم النوع.

[تحرير: إصلاح الخلل في الكود الخاص بي حيث يمكن أن يحدث طريق مسدود إذا فشلت مرحلة خط الأنابيب. التزامن صعب]

سؤال لفرع الأداة: هل سيواكب الإصدار الأخير (إذن v1.15 ، v1.15.1 ، ...)؟

urandom : لاحظ أن القيمة التي تعيدها في الكود الخاص بك هي من النوع Foo (T). كل
ينتج مثل هذا النوع من إنشاء مثيل نوع جديد معرف ، في هذه الحالة Foo (T).
(بالطبع ، إذا كان لديك Foo (T) متعدد في الكود ، فكلهم متماثلون
نوع محدد).

لكن نوع نتيجة وظيفتك هو V ، وهو نوع معلمة. ملحوظة
أن معلمة النوع مقيدة بواجهة Valuer ، لكنها كذلك
_not_ واجهة (أو حتى تلك الواجهة). V هي معلمة نوع وهي
نوع جديد من النوع الذي نعرف عنه الأشياء الموصوفة بقيده.
فيما يتعلق بإمكانية التخصيص ، فإنها تعمل كنوع محدد يسمى V.

لذا فأنت تحاول تعيين قيمة من النوع Foo (T) لمتغير من النوع V.
(وهي ليست Foo (T) ولا Valuer (T) ، فهي تحتوي فقط على خصائص موصوفة بواسطة
المثمن (T)). وبالتالي فشل التخصيص.

(جانبا ، ما زلنا نعمل على تحسين فهمنا لمعلمات النوع
وفي النهاية نحتاج إلى تهجئتها بدقة كافية حتى نتمكن من كتابة ملف
المواصفات. لكن ضع في اعتبارك أن كل معلمة نوع جديدة بشكل فعال
نوع محدد عن نعرف فقط بقدر ما يحدده نوع القيد.)

ربما قصدت كتابة هذا: https://go2goplay.golang.org/p/8Hz6eWSn8Ek؟

Inuart إذا كنت تقصد من خلال فرع الأداة فرع dev.go2go: هذا نموذج أولي ، تم إنشاؤه مع مراعاة النفعية ولأغراض التجريب. نحن نريد من الناس أن يلعبوا بها وأن يحاولوا كتابة كود ، ولكن ليس من الجيد الاعتماد على المترجم لبرامج الإنتاج. يمكن أن تتغير الكثير من الأشياء (حتى البنية ، إذا لزم الأمر). سنقوم بإصلاح الأخطاء وتعديل التصميم كما نتعلم من الملاحظات. يبدو أن مواكبة أحدث إصدارات Go أقل أهمية.

لقد نفذت حالة استخدام في العالم الحقيقي كانت لدي بالأمس للأدوية. إنه تجريد عام لخط الأنابيب يسمح بقياس مراحل خط الأنابيب بشكل مستقل ويدعم الإلغاء ومعالجة الأخطاء (لا يتم تشغيله في الملعب ، لأنه يعتمد على Ergroup ، ولكن تشغيله باستخدام أداة go2go يبدو أنه يعمل).

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

أحد الأشياء التي قد تساعد في سهولة القراءة والمراجعة هو ما إذا كانت أداة Go لديها طريقة لعرض الإصدار أحادي الشكل من الشفرة العامة ، حتى تتمكن من رؤية كيف تسير الأمور. قد يكون غير ممكن ، جزئيًا لأن الوظائف قد لا تكون أحادية الشكل في تنفيذ المترجم النهائي ، لكنني أعتقد أنه سيكون ذا قيمة إذا كان ممكنًا على الإطلاق.

أعتقد أن هذا المثال سيستفيد من القدرة على الحصول على طرق ذات معلمات نوع إضافية.

رأيت هذا التعليق في ملعبك أيضًا ؛ من المؤكد أن بناء جملة الاستدعاء البديل يبدو أكثر قابلية للقراءة ومباشرًا. هل يمكنك شرح هذا بمزيد من التفصيل؟ بعد أن ألفت رأسي بالكاد حول رمز المثال الخاص بك ، أواجه مشكلة في القيام بالقفزة :)

لذا فأنت تحاول تعيين قيمة من النوع Foo (T) لمتغير من النوع V.
(وهي ليست Foo (T) ولا Valuer (T) ، فهي تحتوي فقط على خصائص موصوفة بواسطة
المثمن (T)). وبالتالي فشل التخصيص.

شرح رائع.

... وإلا ، فمن المحزن أن نرى أن HN تم اختطافه من قبل حشد Rust. كان من الجيد الحصول على مزيد من التعليقات من Gophers على الاقتراح.

سؤالان لفريق Go:

  • هل تريد استخدام مشكلة github هذه للأسئلة / الأخطاء مع النماذج الأولية للأدوية أو هل هناك منتدى آخر تفضله؟
  • كنت أتلاعب بقوائم الأنواع: مثال: https://go2goplay.golang.org/p/AAwSof_wT6t

هل هناك فرق بين هذين أم أنه خطأ في ملعب go2؟ الأول يجمع ، والثاني يعطي خطأ

type Addable interface {
    type int, float64
}

func Add(type T Addable)(a, b T) T {
  return a + b
}
type Addable interface {
    type int, float64, string
}

func Add(type T Addable)(a, b T) T {
  return a + b
}

فشل مع: invalid operation: operator + not defined for a (variable of type T)

حسنًا ، كانت هذه مفاجأة سارة وغير متوقعة. كنت أتمنى إيجاد طريقة لتجربة ذلك بالفعل في وقت ما ، لكنني لم أتوقع ذلك في أي وقت قريب.

بادئ ذي بدء ، تم العثور على خطأ: https://go2goplay.golang.org/p/1r0NQnJE-NZ

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

تحرير: أيضًا ، كما قال العديد من الأشخاص ، أعتقد أن السماح بإضافة أنواع جديدة أثناء تعريفات الطريقة سيكون مفيدًا للغاية. بقدر ما يذهب تنفيذ الواجهة ، يمكنك إما ببساطة عدم السماح بتنفيذ الواجهة ، والسماح بالتنفيذ فقط إذا كانت الواجهة تستدعي أيضًا الأدوية الجنيسة هناك ( type Example interface { Method(type T someConstraint)(v T) bool } ) ، أو ربما يمكنك جعلها تنفذ الواجهة إذا _ أيًا_ ممكن متغير منه يقوم بتنفيذ الواجهة ، ثم يكون استدعاءه مقيدًا بما تريده الواجهة إذا تم استدعاؤها من خلال الواجهة. فمثلا،

"اذهب
اكتب واجهة الواجهة {
الحصول على (سلسلة) سلسلة
}

اكتب مثال (نوع T) هيكل {
ت
}

// سيعمل هذا فقط لأن Interface.Get أكثر تحديدًا من Example.Get.
func (e Example (T)) Get (type R) (v R) T {
إرجاع fmt.Sprintf ("٪ v:٪ v"، v، ev)
}

func DoSomething (واجهة) {
// الأساسي هو مثال (سلسلة) ومثال (سلسلة). يُفترض الحصول على (سلسلة نصية) لأنه مطلوب.
fmt.Println (inter.Get ("example"))
}

func main () {
// مسموح به لأن المثال (سلسلة). الحصول على (سلسلة) ممكن.
DoSomething (مثال (سلسلة نصية) {v: "مثال."})
}

DeedleFake أول شيء تبلغ عنه ليس خطأ. ستحتاج إلى كتابة https://go2goplay.golang.org/p/qo3hnviiN4k في الوقت الحالي. هذا موثق في مسودة التصميم. في قائمة المعلمات ، يتم تفسير كتابة a(b) على أنه a (b) ( a من النوع بين قوسين b ) للتوافق مع الإصدارات السابقة. قد نغير ذلك في المستقبل.

مثال التكرار مثير للاهتمام - يبدو وكأنه خطأ للوهلة الأولى. يرجى تقديم خطأ (الإرشادات في منشور المدونة) وتخصيصه لي. شكرا.

Kashomon يقترح منشور المدونة (https://blog.golang.org/generics-next-step) القائمة البريدية للمناقشة وتقديم مشكلات منفصلة للأخطاء. شكرا.

أعتقد أن مشكلة + قد تم حلها بالفعل.

tooolbox

أحد الأشياء التي قد تساعد في سهولة القراءة والمراجعة هو ما إذا كانت أداة Go لديها طريقة لعرض الإصدار أحادي الشكل من الشفرة العامة ، حتى تتمكن من رؤية كيف تسير الأمور. قد يكون غير ممكن ، جزئيًا لأن الوظائف قد لا تكون أحادية الشكل في تنفيذ المترجم النهائي ، لكنني أعتقد أنه سيكون ذا قيمة إذا كان ممكنًا على الإطلاق.

يمكن لأداة go2go القيام بذلك. بدلاً من استخدام go tool go2go run x.go2 ، اكتب go tool go2go translate x.go2 . سيؤدي ذلك إلى إنتاج ملف x.go مع الكود المترجم.

بعد قولي هذا ، يجب أن أقول إن القراءة صعبة إلى حد ما. ليس مستحيلاً ولكنه ليس سهلاً.

تضمين التغريدة

أفهم أن وسيطة الإرجاع يمكن أن تكون واجهة بدلاً من ذلك ، لكنني لا أفهم حقًا لماذا لا يمكن أن تكون من النوع العام نفسه.

يمكنك ، على سبيل المثال ، استخدام نفس النوع العام كمعامل إدخال ، وهذا يعمل بشكل جيد:
https://go2goplay.golang.org/p/LuDrlT3zLRb
هل يعمل هذا لأنه تم إنشاء مثيل للنوع بالفعل؟

كتب urandom :

أفهم أن وسيطة الإرجاع يمكن أن تكون واجهة بدلاً من ذلك ، لكنني لا أفهم حقًا لماذا لا يمكن أن تكون من النوع العام نفسه.

من الناحية النظرية ، يمكن ، ولكن ليس من المنطقي جعل نوع الإرجاع عامًا عندما لا يكون نوع الإرجاع عامًا لأنه يتم تحديده بواسطة كتلة الوظيفة ، أي من خلال القيمة المرتجعة.

عادةً ، يتم تحديد المعلمات العامة إما بالكامل بواسطة قيمة المعلمة tuple أو حسب نوع تطبيق الوظيفة في موقع الاستدعاء (يحدد إنشاء مثيل لنوع الإرجاع العام).

من الناحية النظرية ، يمكنك أيضًا السماح بمعلمات النوع العامة التي لا يتم تحديدها بواسطة قيمة المعلمة tuple ويجب تقديمها بشكل صريح ، على سبيل المثال:

func f(type S)(i int) int
{
    s S =...
    return 2
}

لا أعرف مدى معنى هذا.

urandom لم أقصد بالضرورة بهذا المثال المحدد الذي قد يغيب عن شخص ما قضية - فهو قصير جدًا. قصدت أنه عندما يكون لديك عدد كبير من الطرق يمكن استدعاؤها فقط نظرًا لأنواع معينة. لذلك أقف بعدم استخدام التصنيف الفرعي (كما أحب أن أسميه). بل إنه من الممكن حل "مشكلة التعبير" بدون استخدام تأكيدات النوع أو التصنيف الفرعي. إليك الطريقة:

type Any interface {}

type Evaler(type t Any) interface {
  Eval() t
}

type Num struct {
  value int
}

func (n Num) Eval() int {
  return n.value
}

type Plus(type a Evaler(type t Any)) struct {
  left a
  right a
}

func (p Plus(type a Evaler(type t Any)) Eval() t {
  return p.left.Eval() + p.right.Eval()
}

func (p Plus(type a Evaler(type t Any)) String() string {
  return fmt.Sprintf("(%s+%s)", p.left, p.right)
}

type Expr interface {
  Evaler
  fmt.Stringer
}

func main() {
  var e Expr = Plus(Num){Num{1}, Num{2}}
  var v int = e.Eval() // 3
  var s string = e.String() // "(1+2)"
}

يجب اكتشاف أي إساءة استخدام لطريقة Eval في وقت التجميع نظرًا لحقيقة أنه لا يُسمح باستدعاء Eval on Plus بنوع لا يطبق الإضافة. على الرغم من أنه من الممكن استخدام String () بشكل غير صحيح (ربما إضافة هياكل) ، فإن الاختبار الجيد يجب أن يكتشف تلك الحالات. وعادة ما يحتضن Go البساطة على "الصواب". الشيء الوحيد الذي يتم اكتسابه من خلال التصنيف الفرعي هو المزيد من الارتباك في المستندات وفي الاستخدام. إذا كان بإمكانك تقديم مثال يتطلب تصنيفًا فرعيًا ، فقد أكون أكثر ميلًا للاعتقاد بأنها فكرة جيدة ، لكنني حاليًا غير مقتنع.
تحرير: إصلاح الخطأ وتحسينه

لا أعرف ، لماذا لا تستخدم "<>"؟

@ 99yun
يرجى إلقاء نظرة على الأسئلة الشائعة المتضمنة في المسودة المحدثة

لماذا لا تستخدم بناء الجملة F \مثل C ++ و Java؟
عند تحليل التعليمات البرمجية داخل دالة ، مثل v: = F \، عند رؤية <، من الغموض ما إذا كنا نشاهد نوع مثيل أو تعبير باستخدام عامل التشغيل <. حل هذا يتطلب نظرة غير محدودة بشكل فعال. بشكل عام نحن نسعى جاهدين للحفاظ على كفاءة محلل Go.

urandom دائمًا ما يتم التحقق من نوع النص الأساسي للوظيفة العامة بدون إنشاء مثيل (*) ؛ بشكل عام (إذا تم تصديره ، على سبيل المثال) لا يمكننا معرفة كيفية إنشاء مثيل له. عند فحص النوع ، يمكن الاعتماد فقط على المعلومات المتاحة. إذا كان نوع النتيجة عبارة عن معلمة نوع وكان تعبير الإرجاع من نوع مختلف غير متوافق مع التعيين ، فلن يعمل الإرجاع. أو بعبارة أخرى ، إذا تم استدعاء دالة عامة باستخدام وسيطات نوع (ربما يتم استنتاجها) ، فلن يتم التحقق من نوع جسم الوظيفة مرة أخرى باستخدام وسيطات النوع هذه. يتحقق فقط من استيفاء وسيطات النوع لقيود الوظيفة العامة (بعد إنشاء مثيل لتوقيع الوظيفة باستخدام وسيطات النوع هذه). امل ان يساعد.

(*) بتعبير أدق ، يتم فحص نوع الوظيفة العامة حيث تم إنشاء مثيل لها باستخدام معلمات النوع الخاصة بها ؛ معلمات النوع هي أنواع حقيقية ؛ نحن فقط نعرف عنهم بقدر ما تخبرنا قيودهم.

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

لا يبدو أن هناك طريقة لاستخدام دالة لإنشاء قيمة صفرية لبنية عامة. خذ على سبيل المثال هذه الوظيفة:

func zero(type T)() T {
    var zero T
    return zero
}

يبدو أنه يعمل مع الأنواع الأساسية (int ، float32 وما إلى ذلك). ومع ذلك ، عندما يكون لديك هيكل يحتوي على مجال عام ، تصبح الأمور غريبة. خذ هذا المثال:

type Opt(type T) struct {
    val T
}

func (o Opt(T)) Do() { /*stuff*/ }

يبدو كل شيء على ما يرام. ومع ذلك ، عند القيام بما يلي:

opt := zero(Opt(int))
opt.Do() 

لا تجمع مع إعطاء الخطأ: opt.Do undefined (type func() Opt(int) has no field or method Do) يمكنني أن أفهم ما إذا كان من غير الممكن القيام بذلك ولكن من الغريب الاعتقاد بأنها وظيفة عندما يكون من المفترض أن تكون int جزءًا من نوع Opt. لكن الأغرب أنه من الممكن القيام بذلك:

opt := zero(Opt)      //  But somehow this line compiles
opt(int).Do()         // This will panic

لست متأكدًا من الجزء الذي يمثل خطأ وأي جزء مخصص.
الكود: https://go2goplay.golang.org/p/M0VvyEYwbQU

تضمين التغريدة

لا تحتوي وظيفتك zero() على وسيطات ، لذا لا يوجد استدلال نوع يحدث. يجب عليك إنشاء مثيل لـ func zero ثم تسميته.

opt := zero(Opt(int))()
opt.Do()

https://go2goplay.golang.org/p/N6ip-nm1BP-

tooolbox
أه نعم. اعتقدت أنني كنت أقدم النوع لكنني نسيت المجموعة الثانية من الأقواس لاستدعاء الوظيفة بالفعل. ما زلت أعتاد على هذه الأدوية.

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

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

أنا آسف ، ليس المقصود من الحط من قدر أي شخص ، ولكن هذه هي الطريقة التي يبدأ بها تدمير لغة مصممة بشكل جميل. ماذا بعد؟ إذا واصلنا تغيير الأشياء ، مثل الكثير من الأشخاص الذين يرغبون في ذلك ، فسننتهي بـ "C ++" أو "JavaScript".

فقط اترك Go بالطريقة التي كان من المفترض أن تكون!

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

قرأت الأسئلة الشائعة المتعلقة بـ <> ولكن بالنسبة لشخص غبي مثلي ، كيف يصعب على المحلل اللغوي تحديد ما إذا كانت مكالمة عامة إذا كانت تبدو مثل هذا v := F<T> بدلاً من v := F(T) ؟ أليس الأمر أكثر صعوبة مع الأقواس لأنه لن يعرف ما إذا كان استدعاء دالة مع T كوسيطة عادية؟

علاوة على ذلك ، أعتقد أن المحلل اللغوي يجب أن يظل سريعًا بالطبع ، لكن دعونا لا ننسى أيضًا أيهما أسهل للمبرمج في قراءته ، وهو أمر لا يقل أهمية عن IMO. هل من الأسهل فهم ما يفعله v := F(T) على الفور؟ أم أن v := F<T> أسهل؟ من المهم أيضًا أن تؤخذ في الاعتبار :)

عدم المجادلة لصالح أو ضد v := F<T> ، فقط إثارة بعض الأفكار التي قد تكون جديرة بالاهتمام.

هذا هو الذهاب القانوني اليوم :

    f, c, d, e := 1, 2, 3, 4
    a, b := f < c, d > (e)
    fmt.Println(a, b) // true false

لا فائدة من مناقشة أقواس الزاوية ما لم تقدم اقتراحًا لما يجب فعله حيال ذلك (كسر التوافق؟). إنها قضية ميتة لجميع النوايا والأغراض. لا توجد أي فرصة فعليًا لتبني أقواس الزاوية من قبل فريق Go. الرجاء مناقشة أي شيء آخر.

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

شكرا لتعليقك @ iio7. هناك دائمًا مخاطرة غير صفرية بأن الأمور تخرج عن السيطرة. ولهذا السبب كنا نتوخى أقصى درجات الحذر على طول الطريق. أعتقد أن ما لدينا الآن هو تصميم أنظف ومتعامد أكثر مما كان لدينا العام الماضي ؛ وشخصيًا آمل أن نتمكن من تبسيط الأمر ، لا سيما عندما يتعلق الأمر بقوائم الكتابة - لكننا سنكتشف ذلك كلما تعلمنا المزيد. (ومن المفارقات إلى حد ما ، أنه كلما أصبح التصميم متعامدًا ونظيفًا ، كلما كان أكثر قوة وكلما زاد تعقيد الكود الذي يمكن للمرء كتابته.) لم يتم نطق الكلمات الأخيرة بعد. في العام الماضي ، عندما كان لدينا أول تصميم قابل للتطبيق ، كان رد فعل الكثير من الناس مشابهًا لك: "هل نريد هذا حقًا؟" هذا سؤال ممتاز ويجب أن نحاول الإجابة عليه بأفضل ما نستطيع.

ملاحظةgertcuykens صحيحة أيضًا - فمن الطبيعي أن الأشخاص الذين يلعبون مع النموذج الأولي go2go يستكشفون حدوده قدر الإمكان (وهو ما نريده) ، ولكن في هذه العملية أيضًا ينتجون رمزًا ربما لن يجتاز عملية الإنتاج المناسبة إعدادات. لقد رأيت الآن الكثير من الشفرات العامة التي يصعب حقًا فك شفرتها.

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

للمقارنة: في الأيام الأولى لـ Go ، كنا نميل جميعًا إلى الإفراط في استخدام goroutines والقنوات. استغرق الأمر بعض الوقت لمعرفة متى كانت مناسبة ومتى لا. الآن لدينا بعض الإرشادات الراسخة إلى حد ما ولا نستخدمها إلا عندما يكون ذلك مناسبًا حقًا. آمل أن يحدث نفس الشيء إذا كان لدينا الأدوية الجنيسة.

شكرا.

من قسم مسودة التصميم على الصيغ المستندة إلى [T] :

تسمح اللغة عمومًا بفاصلة لاحقة في قائمة مفصولة بفاصلة ، لذلك يجب السماح بـ A [T ،] إذا كان A من النوع العام ، ولكن عادةً لا يُسمح به لتعبير فهرس. ومع ذلك ، لا يستطيع المحلل اللغوي معرفة ما إذا كان A هو نوع عام أو قيمة شريحة أو مصفوفة أو نوع خريطة ، لذلك لا يمكن الإبلاغ عن خطأ التحليل هذا إلا بعد اكتمال فحص النوع. مرة أخرى ، قابل للحل ولكنه معقد.

ألا يمكن حل هذا بسهولة عن طريق جعل الفاصلة اللاحقة قانونية تمامًا في تعبيرات الفهرس ثم مجرد إزالة gofmt ؟

تضمين التغريدة سيكون ذلك بالتأكيد طريقة سهلة للخروج ؛ لكنها تبدو أيضًا قبيحة بعض الشيء ، من الناحية التركيبية. لا أتذكر كل التفاصيل ، لكن إصدارًا سابقًا كان يدعم معلمات نوع النمط [النوع T]. راجع فرع dev.go2go ، قم بتنفيذ 3d4810b5ba حيث تمت إزالة الدعم. يمكن للمرء أن يحفر ذلك مرة أخرى والتحقيق.

هل يمكن أن يقتصر طول الوسيطات العامة في كل قائمة [] على معظمها لتجنب هذه المشكلة ، تمامًا مثل الأنواع العامة المضمنة:

  • [N] ت
  • [] ت
  • الخريطة [K] T
  • تشان تي

يرجى ملاحظة أن الوسيطات الأخيرة في الأنواع العامة المضمنة ليست كلها محاطة بـ [] .
يشبه بناء جملة الإعلان العام: https://github.com/dotaheor/unify-Go-builtin-and-custom-generics#the-generic-statement-syntax

dotaheor لست متأكدًا مما تطلبه بالضبط ، ولكن من الواضح أنه من الضروري دعم الحجج متعددة الأنواع لنوع عام. على سبيل المثال ، https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#containers .

تضمين التغريدة
ما أعنيه هو أن كل معلمة نوع يتم تضمينها بواسطة [] ، لذلك يمكن الإعلان عن النوع الموجود في الارتباط الخاص بك على النحو التالي:

type Map[type K][type V] struct

عند استخدامه يكون مثل:

var m Map[string]int

يشير نوع الوسيطة غير المحاطة بـ [] إلى نهاية استخدام نوع عام.

أثناء التفكير في طلب المصفوفات # 39355 بالاقتران مع الأدوية العامة ، وجدت أن "المقارنة" يتم التعامل معها بشكل خاص في مسودة الأدوية العامة الحالية (على الأرجح بسبب عدم القدرة على سرد جميع الأنواع المماثلة في قائمة النوع بسهولة) كقيد نوع محدد مسبقًا .

سيكون من الرائع أن يتم تغيير مسودة الأدوية العامة لتعريف "أمر" / "قابل للترتيب" مشابه لكيفية تحديد "قابلة للمقارنة" مسبقًا. إنها علاقة شائعة الاستخدام ذات صلة بقيم من نفس النوع وهذا من شأنه أن يسمح بالامتدادات المستقبلية للغة go لتحديد الترتيب على أنواع أكثر (المصفوفات ، الهياكل ، الشرائح ، أنواع المجموع ، التعدادات المحددة ، ...) دون الوقوع في المضاعفات أنه لا يمكن إدراج جميع الأنواع المرتبة في قائمة الأنواع مثل "قابلة للمقارنة".

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

martisch أعتقد أن هذا سيحدث فقط بمجرد تمديد الأنواع المطلوبة. حاليًا ، يمكن أن يسرد constraints.Ordered جميع الأنواع (التي لا تعمل مع comparable ، بسبب المؤشرات ، التركيبات ، المصفوفات ، ... كونها قابلة للمقارنة ، لذلك يجب أن يكون ذلك سحريًا. لكن ordered يقتصر حاليًا على مجموعة محدودة من الأنواع الأساسية المضمنة) ويمكن للمستخدمين الاعتماد على ذلك. إذا قمنا بتوسيع الطلبات إلى المصفوفات (على سبيل المثال) ، فلا يزال بإمكاننا إضافة قيود سحرية جديدة ordered ثم تضمينها في constraints.Ordered . هذا يعني أن جميع مستخدمي constraints.Ordered سيستفيدون تلقائيًا من القيد الجديد. بالطبع ، لن يستفيد المستخدمون الذين يكتبون قائمة الأنواع الصريحة الخاصة بهم - ولكن الأمر نفسه إذا أضفنا ordered الآن ، للمستخدمين الذين لا يقومون بتضمين ذلك .

لذا ، IMO لا يوجد شيء ضائع في تأخير ذلك حتى يصبح مفيدًا في الواقع. لا ينبغي أن نضيف أي مجموعة قيود محتملة كمعرف مسبق التحديد - ناهيك عن أي مجموعة قيود مستقبلية محتملة :)

إذا قمنا بتوسيع الطلبات إلى المصفوفات (على سبيل المثال) فلا يزال بإمكاننا إضافة قيود سحرية جديدة ordered ثم تضمينها في constraints.Ordered .

@ ميروفيوس هذه نقطة جيدة لم أفكر فيها. هذا يسمح بتمديد constraints.Ordered في المستقبل بطريقة متسقة. إذا كان هناك أيضًا constraints.Comparable فإنه يتناسب بشكل جيد مع الهيكل العام.

martisch ، لاحظ أن ordered - على عكس comparable - ليس متماسكًا كنوع واجهة ما لم نحدد أيضًا ترتيبًا كليًا (عالميًا) بين الأنواع الملموسة ، أو نمنع الكود غير العام من استخدام < على المتغيرات من النوع ordered ، أو حظر استخدام comparable كنوع عام لواجهة وقت التشغيل.

خلاف ذلك ، تنهار انتقالية "الأدوات". ضع في اعتبارك جزء هذا البرنامج:

    var x constraints.Ordered = int(0)
    var y constraints.Ordered = string("0")
    fmt.Println(x < y)

ماذا يجب أن يخرج؟ (هل الإجابة بديهية أم عشوائية؟)

تضمين التغريدة
ماذا عن fun (<)(type T Ordered)(t1 T,t2 T) Bool?

لمقارنة أنواع حسابية من أنواع مختلفة:

إذا نفذت أي عملية حسابية S فقط Ordered(T) مقابل S<:T ، إذن:

//Isn't possible I think
interface SorT(S,T)
{ 
type S,T
}

fun (<)(type R SorT(S,T), S Ordered(R), T Ordered(R))(s S, t T) Bool

يجب أن تكون فريدة من نوعها.

بالنسبة لتعدد الأشكال في وقت التشغيل ، قد تحتاج إلى أن يكون Ordered قابلًا للتحديد.
أو:
لقد قمت بترتيب القسم في أنواع tuple ثم أعد كتابة (<) ليكون:

//but isn't supported that either
fun(<)(type R Ordered)(s R.0,t R.1)

مرحبا!
عندي سؤال.

هل هناك طريقة لجعل قيد الكتابة يمر فقط الأنواع العامة بمعامل نوع واحد؟
شيء يتجاوز Result(T) / Option(T) / إلخ ولكن ليس فقط T .
حاولت

type Box(type T) interface {
    Val() (T, bool)
}

لكنها تتطلب طريقة Val()

type Box(type T) interface{}

مشابه لـ interface{} ، أي Any

حاول أيضًا https://go2goplay.golang.org/p/lkbTI7yppmh -> فشل التجميع

type Box(type T) interface {
       type Box(T)
}

https://go2goplay.golang.org/p/5NsKWNa3E1k -> فشل التجميع

type Box(type T) interface{}

type Generic(type T) interface {
    type Box(T)
}

https://go2goplay.golang.org/p/CKzE2J-YOpD -> لا يعمل

type Box(type T) interface{}

type Generic(type T Box(T)) interface {}

هل هذا السلوك متوقع أم أنه مجرد كتابة فحص الخطأ؟

تنطبق قيود tdakkota على وسيطات الكتابة ، وتنطبق على الشكل الذي تم إنشاء مثيل له بالكامل من وسيطات النوع. لا توجد طريقة لكتابة قيد نوع يضع أي متطلبات على الشكل غير المحسّن لوسيطة النوع.

يرجى إلقاء نظرة على الأسئلة الشائعة المتضمنة في المسودة المحدثة

لماذا لا تستخدم صيغة Fمثل C ++ و Java؟
عند تحليل التعليمات البرمجية داخل دالة ، مثل v: = F، عند رؤية <، من الغموض ما إذا كنا نشاهد نوع مثيل أو تعبير باستخدام عامل التشغيل <. حل هذا يتطلب نظرة غير محدودة بشكل فعال. بشكل عام نحن نسعى جاهدين للحفاظ على كفاءة محلل Go.

تضمين التغريدة

كيف يتم التعامل مع القيمة الصفرية من النوع العام؟ بدون التعداد كيف يمكننا التعامل مع القيمة الاختيارية.
على سبيل المثال: الإصدار العام من vector ، ويعيد func المسمى First العنصر الأول إذا كان طوله> 0 وإلا صفر قيمة من النوع العام.
كيف نكتب مثل هذا الرمز؟ نظرًا لأننا لا نعرف أي نوع في المتجه ، إذا كان chan/slice/map ، فيمكننا return (nil, false) ، أو struct أو primitive type مثل string ، int ، bool كيف يتم التعامل معها؟

تضمين التغريدة

يجب أن يكون var zero T كافيًا

تضمين التغريدة

يجب أن يكون var zero T كافيًا

متغير سحري عالمي مثل nil ؟

تضمين التغريدة
يجب أن يكون var zero T كافيًا

متغير سحري عالمي مثل nil ؟

هناك اقتراح قيد المناقشة لهذا الموضوع - راجع الاقتراح: الانتقال 2: القيمة الصفرية العالمية مع نوع الاستدلال # 35966 .

يقوم بفحص عدة صيغ بديلة جديدة للتعبير (وليس جملة مثل var zero T ) والتي سترجع دائمًا القيمة الصفرية لنوع ما.

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

تبدو القيمة الصفرية مجدية حاليًا ، ولكن هل يمكن أن تأخذ مساحة على المكدس أو الكومة؟

تاريخيًا ، على ما أعتقد ، قام مترجم Go بتحسين هذه الأنواع من الحالات. أنا لست قلقة جدا.

يمكن تحديد قيمة نوع افتراضية في قوالب C ++. هل تم وضع بنية مماثلة في الاعتبار لمعلمات النوع العام؟ من المحتمل أن يجعل هذا من الممكن تعديل الأنواع الحالية دون كسر الكود الحالي.

على سبيل المثال ، ضع في اعتبارك نوع asn1.ObjectIdentifier الموجود وهو []int . تتمثل إحدى مشكلات هذا النوع في عدم توافقه مع مواصفات ASN.1 ، والتي تنص على أن كل معرف فرعي قد يكون عددًا صحيحًا من الطول التعسفي (على سبيل المثال ، *big.Int ). من المحتمل أن يتم تعديل ObjectIdentifier لقبول معامل عام ، لكن هذا من شأنه أن يكسر الكثير من التعليمات البرمجية الموجودة. إذا كانت هناك طريقة لتحديد أن int هي قيمة المعلمة الافتراضية ، فربما يجعل ذلك من الممكن تعديل الكود الحالي.

type SignedInteger interface {
    type int, int32, int64, *big.Int
}
type ObjectIdentifier(type T SignedInteger) []T
// type ObjectIdentifier(type T SignedInteger=int) []T  // `int` would be the default instantiation type.

// New code with generic awareness would compile in go2.
var oid1 ObjectIdentifier(int) = ObjectIdentifier(int){1, 2, 3}

// But existing code would fail to compile:
var oid1 ObjectIdentifier = ObjectIdentifier{1, 2, 3}

فقط للتوضيح ، ما ورد أعلاه asn1.ObjectIdentifier هو مجرد مثال. أنا لا أقول أن استخدام الأدوية الجنيسة هو الطريقة الوحيدة أو أفضل طريقة لحل مشكلة الامتثال لـ ASN.1.

علاوة على ذلك ، هل هناك أي خطط للسماح بحدود الواجهة المحدودة القابلة للتحديد ؟:

type Ordable(type T, S) interface {
    type S, type T
}

كيفية دعم شرط حيث على نوع المعلمة.
هل يمكننا كتابة هذا الرمز:

type Vector(type T) struct {
    vec []T
}

func (v Vector(T)) Sum() T where T: Summable {
      //
}

func (v Vector(T)) First()  (T, bool) {
     //
}

لا تعمل طريقة Sum إلا عند كتابة المعلمات T هي Summable ، وإلا لا يمكننا استدعاء Sum على Vector.

مرحبًا leaxoy

يمكنك فقط كتابة شيء مثل https://go2goplay.golang.org/p/pRznN30Qu8V

type Addable interface {
    type int, uint
}

type SummableVector(type T Addable) Vector(T)

func (v SummableVector(T)) Sum() T {
    var r T
    for _, i := range v.vec {
        r = r + i
    }
    return r
}

أعتقد أن عبارة where لا تبدو مثل Go ، وسيكون من الصعب تحليلها ، يجب أن تكون شيئًا مثل

type Vector(type T) struct {
    vec []T
}

func (v Vector(T Summable)) Sum() T {
      //
}

func (v Vector(T)) First()  (T, bool) {
     //
}

ولكن يبدو أنه أسلوب التخصص.

@ sebastien-rosset لم نأخذ في الاعتبار الأنواع الافتراضية لمعلمات النوع العام. لا تحتوي اللغة على قيم افتراضية لوسائط الدوال ، وليس من الواضح سبب اختلاف الأدوية الجنيسة. في رأيي ، القدرة على جعل الكود الحالي متوافقًا مع حزمة تضيف أدوية عامة ليست أولوية. إذا تمت إعادة كتابة الحزمة لاستخدام الأدوية الجنيسة ، فلا بأس من طلب تغيير الكود الحالي ، أو ببساطة إدخال الرمز العام باستخدام أسماء جديدة.

تضمين التغريدة

علاوة على ذلك ، هل هناك أي خطط للسماح بحدود الواجهة المحدودة القابلة للتحديد؟

أنا آسف ، لا أفهم السؤال.

أود تذكير الأشخاص بأن منشور المدونة (https://blog.golang.org/generics-next-step) يقترح إجراء مناقشة حول الأدوية الجنيسة في القائمة البريدية لـ golang-nuts ، وليس في أداة تعقب المشكلات. سأستمر في قراءة هذه المشكلة ، لكنها تحتوي على ما يقرب من 800 تعليق وهي غير عملية تمامًا ، إلى جانب الصعوبات الأخرى في أداة تعقب المشكلة مثل عدم وجود سلسلة للتعليقات. شكرا.

التعليقات: لقد استمعت إلى أحدث بودكاست Go Time ، ولا بد لي من القول إن الشرح من griesemer حول مشكلة الأقواس كانت المرة الأولى التي فهمتها حقًا ، أي ماذا يعني "lookahead on the parser" في الواقع من أجل Go؟ شكرا جزيلا على التفاصيل الإضافية هناك.

كما أنني أؤيد استخدام الأقواس المربعة. 😄

تضمين التغريدة

يقترح منشور المدونة أن المناقشة حول الأدوية الجنيسة تتم في القائمة البريدية لـ golang-nuts ، وليس في أداة تعقب المشكلات

في منشور مدونة حديث [1] ، يشير ddevault إلى أن مجموعة Google (حيث توجد تلك القائمة البريدية) تتطلب حساب Google. أنت بحاجة إلى واحد للنشر ، ويبدو أن بعض المجموعات تتطلب حسابًا لقراءته. لدي حساب على Google ، لذا فهذه ليست مشكلة بالنسبة لي (وأنا أيضًا لا أقول إنني أتفق مع كل شيء في منشور المدونة هذا) ، لكنني أوافق على أنه إذا أردنا أن يكون لدينا منتدى golang أكثر عدلاً ، و إذا أردنا تجنب غرفة الصدى ، فقد يكون من الأفضل عدم وجود هذا النوع من المتطلبات.

لم أكن أعرف هذا عن مجموعات Google ، وإذا كان هناك بعض الاستثناءات لـ golang-nuts ، فيرجى قبول اعتذاري وتجاهل ذلك. لما يستحق الأمر ، لقد تعلمت الكثير من قراءة هذا الموضوع ، كما أنني مقتنع تمامًا (بعد استخدام golang لأكثر من ست سنوات) أن الأدوية الجنسية هي النهج الخاطئ للغة. فقط رأيي الشخصي ، وأشكركم على تقديم اللغة التي أستمتع بها كثيرًا!

هتافات!

[1] https://drewdevault.com/2020/08/01/pkg-go-dev-sucks.html

يمكن استخدام purpleidea أي مجموعة Google كقائمة بريدية. يمكنك الانضمام والمشاركة دون أن يكون لديك حساب Google.

تضمين التغريدة

يمكن استخدام أي مجموعة Google كقائمة بريدية. يمكنك الانضمام والمشاركة دون أن يكون لديك حساب Google.

عندما أذهب الى:

https://groups.google.com/forum/#!forum/golang -nuts

في نافذة متصفح خاصة (لإخفاء حسابي على google الذي قمت بتسجيل الدخول إليه) ، والنقر فوق "موضوع جديد" يعيد توجيهي إلى صفحة تسجيل الدخول إلى google. كيف أستخدمه بدون حساب Google؟

purpleidea بكتابة بريد إلكتروني إلى [email protected] . إنها قائمة بريدية. تحتاج واجهة الويب فقط إلى حساب Google. وهو ما يبدو عادلاً - نظرًا لكونها قائمة بريدية ، فأنت بحاجة إلى عنوان بريد إلكتروني ومن الواضح أن المجموعات يمكنها فقط إرسال رسائل بريد إلكتروني من حساب gmail.

أعتقد أن معظم الناس لا يفهمون ما هي القائمة البريدية.

على أي حال ، يمكنك أيضًا استخدام أي مرآة قائمة بريدية عامة ، على سبيل المثال https://www.mail-archive.com/[email protected]/

كل هذا رائع ، لكنه لا يجعل الأمر أسهل عندما يرتبط الأشخاص بـ
سلاسل على مجموعات Google (والتي تحدث بشكل متكرر). إنه أمر لا يصدق
مزعج لمحاولة العثور على رسالة من المعرف في عنوان URL.

—صام

في الأحد 2 أغسطس 2020 الساعة 19:24 كتب أحمد و.
>
>

أعتقد أن معظم الناس لا يفهمون ما هي القائمة البريدية.

على أي حال ، يمكنك أيضًا استخدام أي مرآة قائمة بريدية عامة ، على سبيل المثال
https://www.mail-archive.com/[email protected]/

- أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/golang/go/issues/15292#issuecomment-667738419 ، أو
إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AAD5EPNQTEUF5SPT6GMM4JLR6XYUBANCNFSM4CA35RXQ
.

-
سام ويتيد

هذا ليس المكان المناسب لإجراء هذه المناقشة.

أي تحديثات على هذا؟ 🤔

@ Imperatorn كان هناك ، فقط لم تتم مناقشتها هنا. تقرر أن تكون الأقواس المربعة [ ] هي الصيغة المختارة وأن كلمة "type" لن تكون مطلوبة عند كتابة أنواع / وظائف عامة. يوجد أيضًا اسم مستعار جديد "أي" للواجهة الفارغة.

أحدث تصميم مسودة للأدوية هنا .
راجع أيضًا هذا التعليق لإعادة: المناقشات حول هذا الموضوع. شكرا.

أود تذكير الأشخاص بأن منشور المدونة (https://blog.golang.org/generics-next-step) يقترح إجراء مناقشة حول الأدوية الجنيسة في القائمة البريدية لـ golang-nuts ، وليس في أداة تعقب المشكلات. سأستمر في قراءة هذه المشكلة ، لكنها تحتوي على ما يقرب من 800 تعليق وهي غير عملية تمامًا ، إلى جانب الصعوبات الأخرى في أداة تعقب المشكلة مثل عدم وجود سلسلة للتعليقات. شكرا.

في هذا الصدد ، على الرغم من أنني أحترم رغبة فريق Go في نقل مثل هذه المناقشات من قضية لأسباب عملية ، يبدو أن هناك الكثير من أعضاء المجتمع على GitHub ليسوا على علاقة بالمسألة. أتساءل عما إذا كانت ميزة المناقشات الجديدة في GitHub ستكون مناسبة بشكل جيد؟ 🤔 لديها خيوط ، على ما يبدو.

toolbox يمكن أيضًا إجراء الحجة في الاتجاه الآخر - هناك أشخاص ليس لديهم حساب على Github (ويرفضون الحصول على حساب). لست مضطرًا أيضًا إلى الاشتراك في golang-nuts لتتمكن من النشر والمشاركة هناك.

Merovius إحدى الميزات التي أحبها حقًا بشأن مشكلات GitHub هي أنه يمكنني الاشتراك في الإشعارات الخاصة بالمشكلات التي أهتم بها فقط. لست متأكدًا من كيفية القيام بذلك مع مجموعات Google؟

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

toolbox يمكن أيضًا إجراء الحجة في الاتجاه الآخر - هناك أشخاص ليس لديهم حساب على Github (ويرفضون الحصول على حساب). لست مضطرًا أيضًا إلى الاشتراك في golang-nuts لتتمكن من النشر والمشاركة هناك.

لقد فهمت ما تقوله ، وهذا صحيح ، لكنك تفتقد العلامة. أنا لا أقول أنه يجب إخبار مستخدمي golang-nuts بالذهاب إلى GitHub ، (كما يحدث الآن في الاتجاه المعاكس) أنا أقول إنه سيكون من الجيد لمستخدمي GitHub أن يكون لديهم منتدى مناقشة.

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

أوافق على أن هذا خارج عن الموضوع بشكل كبير بالنسبة لهذه المشكلة ، وأعتذر عن إثارتها ، لكني آمل أن ترى المفارقة.

keeanMeroviustooolbox والناس في المستقبل.

لمعلوماتك: هناك مشكلة مفتوحة لهذا النوع من المناقشة ، راجع # 37469.

مرحبا،

بادئ ذي بدء ، شكرًا لك على Go. اللغة رائعة للغاية. من أكثر الأشياء المدهشة في Go ، بالنسبة لي ، سهولة القراءة. أنا جديد على اللغة ، لذلك ما زلت في المراحل الأولى من الاكتشاف ، لكن حتى الآن ، فقد ظهرت بشكل واضح ونقي ودقيق للغاية.

جزء واحد من التعليقات التي أود تقديمها هو أنه من خلال المسح الأولي لاقتراح الأدوية ، ليس [T Constraint] السهل بالنسبة لي تحليلها بسرعة ، على الأقل ليس سهلاً مثل مجموعة الأحرف المخصصة للأدوية . أفهم أن أسلوب C ++ F<T Constraint> غير ممكن بسبب طبيعة نموذج go متعدد العوائد. ستكون أي شخصيات غير أسكي عملاً روتينيًا مطلقًا ، لذا فأنا ممتن حقًا لأنك ألغيت هذه الفكرة.

يرجى النظر في استخدام مجموعة الأحرف. لست متأكدًا مما إذا كان من الممكن إساءة فهم العمليات التي تتم بطريقة أحاديات أو تعكير صفو مياه التحليل ، لكن في رأيي ، سيكون من الجيد استخدام F<<T Constraint>> . ومع ذلك ، فإن أي مجموعة رموز ستكون كافية. على الرغم من أنه قد يضيف بعض ضريبة مسح العين الأولية ، أعتقد أنه يمكن علاج ذلك بسهولة باستخدام وصلات الخطوط مثل FireCoda و Iosevka . لا يوجد الكثير الذي يمكن القيام به للتمييز بوضوح وسهولة عن الفرق بين Map[T Constraint] و map[string]T .

ليس لدي شك في أن الناس سوف يدربون عقولهم على التمييز بين التطبيقين [] بناءً على السياق. أنا فقط أظن أنه سيزيد من حدة منحنى التعلم.

شكرا على الملاحظة. لا يفوتك ما هو واضح ، ولكن يمكن تمييز map[T1]T2 و Map[T1 Constraint] لأن الأول ليس له أي قيود والأخير عليه قيد مطلوب.

تمت مناقشة بناء الجملة على نطاق واسع حول golang-nuts وأعتقد أنه تم تسويتها. يسعدنا سماع تعليقات تستند إلى بيانات فعلية مثل تحليل نقاط الغموض. بالنسبة للتعليقات التي تستند إلى المشاعر والتفضيلات ، أعتقد أن الوقت قد حان للاختلاف والالتزام.

شكرا لك مرة أخرى.

ianlancetaylor عادل بما فيه الكفاية. أنا متأكد من أنك سئمت من سماع nitpicks عليه :) لما يستحق ، قصدت بسهولة التمييز بين المسح الضوئي.

بغض النظر ، أتطلع إلى استخدامه. شكرا لك.

سيكون البديل العام لـ reflect.MakeFunc فوزًا كبيرًا في الأداء لأجهزة Go. لكني لا أرى طريقة لتحليل نوع دالة بالاقتراح الحالي.

@ Julio-Guerra لست متأكدًا مما تقصده بعبارة "تحلل نوع الوظيفة". يمكنك ، إلى حد ما ، وضع معاملات على الوسيطة وأنواع الإرجاع: https://go2goplay.golang.org/p/RwU11S4gC59

package main

import (
    "fmt"
)

func Call[In, Out any](f func(In) Out, v In) Out {
    return f(v)
}

func main() {
    triple := func(i int) int {
        return 3 * i
    }
    fmt.Println(Call(triple, 23))
}

هذا لا يعمل إلا إذا كان عدد كلاهما ثابتًا.

@ Julio-Guerra لست متأكدًا مما تقصده بعبارة "تحلل نوع الوظيفة". يمكنك ، إلى حد ما ، وضع معاملات على الوسيطة وأنواع الإرجاع: https://go2goplay.golang.org/p/RwU11S4gC59

في الواقع ، أنا أشير إلى ما فعلته ، لكنني معمم على أي معلمة دالة وقائمة نوع الإرجاع (على نحو مشابه لمصفوفة المعلمات وأنواع الإرجاع لـ reflect.MakeFunc). سيسمح ذلك بامتلاك أغلفة وظيفية معممة (بدلاً من استخدام توليد التعليمات البرمجية بأدوات).

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات