Julia: واجهات لأنواع الخلاصة

تم إنشاؤها على ٢٦ مايو ٢٠١٤  ·  171تعليقات  ·  مصدر: JuliaLang/julia

أعتقد أن طلب الميزة هذا لم يتم طرحه بعد على الرغم من أنه تمت مناقشته في مثال رقم 5.

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

ستعطينا الواجهات بشكل أساسي شيئين:

  • التوثيق الذاتي للواجهات في مكان واحد
  • رسائل خطأ أفضل

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

abstract MyType has print, size(::MyType,::Int), push!

سيكون الأمر رائعًا هنا إذا كان بإمكان المرء تحديد مستويات مختلفة من الحبيبات. تقول التصريحات print و push! فقط أنه يجب أن تكون هناك أية طرق بهذا الاسم (و MyType كمعامل أول) لكنها لا تحدد الأنواع. في المقابل ، تم كتابة إعلان size بالكامل. أعتقد أن هذا يمنح قدرًا كبيرًا من المرونة وبالنسبة لإعلان الواجهة غير المكتوب ، لا يزال بإمكان المرء تقديم رسائل خطأ محددة تمامًا.

كما قلت في رقم 5 ، فإن هذه الواجهات هي في الأساس ما تم التخطيط له في C ++ مثل Concept-light لـ C ++ 14 أو C ++ 17. وبعد أن قمت ببعض برمجة قوالب C ++ ، فأنا متأكد من أن بعض الصياغة في هذا المجال سيكون مفيدًا أيضًا لجوليا.

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

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

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

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

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

ال 171 كومينتر

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

ومع ذلك ، هناك شيء مفقود هنا. تواقيع الطرق (وليس أسمائها فقط) مهمة أيضًا للواجهة.

هذا ليس شيئًا سهل التنفيذ وسيكون هناك الكثير من المشاكل. ربما يكون هذا أحد أسباب عدم قبول _Concepts_ من قبل C ++ 11 ، وبعد ثلاث سنوات ، فقط إصدار محدود للغاية _lite_ يدخل في C ++ 14.

احتوت الطريقة size في المثال الخاص بي على التوقيع. علاوة على ذلك ، يأخذ @mustimplement من Base.graphics التوقيع في الحسبان.

يجب أن أضيف أن لدينا بالفعل جزءًا واحدًا من Concept-light وهو القدرة على تقييد نوع ليكون نوعًا فرعيًا من نوع ملخص معين. الواجهات هي الجزء الآخر.

هذا الماكرو رائع جدا. لقد قمت يدويًا بتعريف العناصر الاحتياطية المسببة للأخطاء ، وعملت جيدًا لتحديد الواجهات. على سبيل المثال ، يقوم MathProgBase من JuliaOpt بهذا ، وهو يعمل بشكل جيد. كنت أتجول مع حلال جديد (https://github.com/IainNZ/RationalSimplex.jl) وكان عليّ فقط الاستمرار في تنفيذ وظائف الواجهة حتى تتوقف عن إثارة الأخطاء لتشغيلها.

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

كيف يتعامل هذا مع المتغيرات المشتركة / المتناقضة؟

فمثلا،

abstract A has foo(::A, ::Array)

type B <: A 
    ...
end

type C <: A
    ...
end

# is it ok to let the arguments to have more general types?
foo(x::Union(B, C), y::AbstractArray) = ....

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

lindahua هذا مثال مثير للاهتمام. يجب أن تفكر في ذلك.

lindahua ربما يريد المرء أن @mustimplement لأنه يحدد تواقيع أكثر تحديدًا للطريقة.

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

من السهل جدًا تغيير طريقة طباعة MethodError ، عندما يكون لدينا بناء جملة وواجهة برمجة تطبيقات للتعبير عن المعلومات والوصول إليها.

الشيء الآخر الذي يمكن أن يحصلنا عليه هو وظيفة في base.Test للتحقق من أن نوعًا ما (جميع الأنواع؟) ينفذ بالكامل واجهات الأنواع الرئيسية. سيكون هذا اختبار وحدة أنيق حقًا.

شكراivarne. لذلك يمكن أن يبدو التنفيذ على النحو التالي:

  1. يحتوي المرء على قاموس عالمي مع أنواع مجردة كمفاتيح ووظائف (+ توقيعات اختيارية) كقيم.
  2. يحتاج المحلل اللغوي إلى التكيف لملء الأمر عند تحليل إعلان has .
  3. يحتاج MethodError للبحث عما إذا كانت الوظيفة الحالية جزءًا من القاموس العام.

سيكون معظم المنطق بعد ذلك في MethodError .

لقد جربت هذا قليلاً وباستخدام المضمون التالي https://gist.github.com/tknopp/ed53dc22b61062a2b283 يمكنني القيام به:

julia> abstract A
julia> addInterface(A,length)
julia> type B <: A end
julia> checkInterface(B)
ERROR: Interface not implemented! B has to implement length in order to be subtype of A ! in error at error.jl:22

عند تحديد length لم يحدث خطأ:

julia> import Base.length
julia> length(::B) = 10
length (generic function with 34 methods)
julia> checkInterface(B)
true

لا يعني ذلك أن هذا لا يأخذ التوقيع في الاعتبار حاليًا.

لقد قمت بتحديث الكود في الجوهر قليلاً بحيث يمكن أخذ توقيعات الوظيفة في الاعتبار. لا يزال الأمر صعبًا للغاية ولكن ما يلي يعمل الآن:

julia> abstract A
julia> type B <: A end

julia> addInterface(A,:size,(A,Int64))
1-element Array{(DataType,DataType),1}:
 (A,Int64)
julia> checkInterface(B)
ERROR: Interface not implemented! B has to implement size in order to be subtype of A !
in error at error.jl:22

julia> import Base.size
julia> size(::B, ::Integer) = 333
size (generic function with 47 methods)
julia> checkInterface(B)
true

julia> addInterface(A,:size,(A,Float64))
2-element Array{(DataType,DataType),1}:
 (A,Int64)
 (A,Float64)
julia> checkInterface(B)
ERROR: Interface not implemented! B has to implement size in order to be subtype of A !
 in error at error.jl:22
 in string at string.jl:30

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

رأيت للتو أن # 2248 يحتوي بالفعل على بعض المواد على الواجهات.

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


إليك نموذج بالحجم الطبيعي لبناء الجملة لإعلان الواجهة وتنفيذ تلك الواجهة:

interface Iterable{T,S}
    start :: Iterable --> S
    done  :: (Iterable,S) --> Bool
    next  :: (Iterable,S) --> (T,S)
end

implement UnitRange{T} <: Iterable{T,T}
    start(r::UnitRange) = oftype(r.start + 1, r.start)
    next(r::UnitRange, state::T) = (oftype(T,state), state + 1)
    done(r::UnitRange, state::T) = i == oftype(i,r.stop) + 1
end

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

ثانيًا ، هناك إعلان عن الواجهة Iterable{T,S} . هذا يجعل Iterable يشبه إلى حد ما وحدة ويشبه نوعًا ما مجردة. إنها تشبه الوحدة النمطية من حيث أنها تحتوي على ارتباطات بوظائف عامة تسمى Iterable.start ، Iterable.done و Iterable.next . إنه مثل النوع في ذلك Iterable و Iterable{T} و Iterable{T,S} يمكن استخدامها حيثما يمكن استخدام الأنواع المجردة - على وجه الخصوص ، في أسلوب الإرسال.

ثالثا، هناك من implement كتلة تحديد كيفية UnitRange تطبق Iterable اجهة. داخل implement كتلة، و Iterable.start ، Iterable.done و Iterable.next الوظائف المتاحة، كما لو أن المستخدم قد فعلت import Iterable: start, done, next ، مما يسمح لل إضافة طرق لهذه الوظائف. هذه الكتلة تشبه القالب بالطريقة التي تكون بها إعلانات الأنواع البارامترية - داخل الكتلة ، يعني UnitRange UnitRange محددًا ، وليس نوع المظلة.

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

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

julia> methods(sort!)
sort!(r::UnitRange{T<:Real}) at range.jl:498
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,::InsertionSortAlg,o::Ordering) at sort.jl:242
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::QuickSortAlg,o::Ordering) at sort.jl:259
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::MergeSortAlg,o::Ordering) at sort.jl:289
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::MergeSortAlg,o::Ordering,t) at sort.jl:289
sort!{T<:Union(Float64,Float32)}(v::AbstractArray{T<:Union(Float64,Float32),1},a::Algorithm,o::Union(ReverseOrdering{ForwardOrdering},ForwardOrdering)) at sort.jl:441
sort!{O<:Union(ReverseOrdering{ForwardOrdering},ForwardOrdering),T<:Union(Float64,Float32)}(v::Array{Int64,1},a::Algorithm,o::Perm{O<:Union(ReverseOrdering{ForwardOrdering},ForwardOrdering),Array{T<:Union(Float64,Float32),1}}) at sort.jl:442
sort!(v::AbstractArray{T,1},alg::Algorithm,order::Ordering) at sort.jl:329
sort!(v::AbstractArray{T,1}) at sort.jl:330
sort!{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32)}(A::CholmodSparse{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32),Int32}) at linalg/cholmod.jl:809
sort!{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32)}(A::CholmodSparse{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32),Int64}) at linalg/cholmod.jl:809

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

sort!(v::AbstractArray)

الباقي ضوضاء وينتمون إلى "الداخل". على وجه الخصوص ، فإن

sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,::InsertionSortAlg,o::Ordering)
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::QuickSortAlg,o::Ordering)
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::MergeSortAlg,o::Ordering)

أنواع الطرق هي ما تطبقه خوارزمية الفرز للربط بآلية الفرز العامة. حاليًا Sort.Algorithm هو نوع مجردة ، و InsertionSortAlg و QuickSortAlg و MergeSortAlg هي أنواع فرعية ملموسة منه. مع الواجهات ، يمكن أن يكون Sort.Algorithm واجهة بدلاً من ذلك وستقوم الخوارزميات المحددة بتنفيذها. شيء من هذا القبيل:

# module Sort
interface Algorithm
    sort! :: (AbstractVector, Int, Int, Algorithm, Ordering) --> AbstractVector
end
implement InsertionSortAlg <: Algorithm
    function sort!(v::AbstractVector, lo::Int, hi::Int, ::InsertionSortAlg, o::Ordering)
        <strong i="17">@inbounds</strong> for i = lo+1:hi
            j = i
            x = v[i]
            while j > lo
                if lt(o, x, v[j-1])
                    v[j] = v[j-1]
                    j -= 1
                    continue
                end
                break
            end
            v[j] = x
        end
        return v
    end
end

يمكن بعد ذلك تحقيق الفصل الذي نريده من خلال تحديد:

# module Sort
sort!(v::AbstractVector, alg::Algorithm, order::Ordering) =
    Algorithm.sort!(v,1,length(v),alg,order)

هذا قريب جدًا مما نقوم به حاليًا ، باستثناء أننا نسمي Algorithm.sort! بدلاً من sort! - وعند تنفيذ خوارزميات الفرز المختلفة ، فإن التعريف "الداخلي" هو طريقة Algorithm.sort! ليست الوظيفة sort! . هذا له تأثير فصل تنفيذ sort! عن واجهته الخارجية.

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

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

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

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

أرى "الواجهة" كتعليق توضيحي لأنواع مجردة. إذا وضع أحدهم has عليه ، فيمكنه تحديد واجهة ولكن لا يتعين على المرء ذلك.

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

type UnitRange{T} <: Iterable{T,T}
    start(r::UnitRange) = oftype(r.start + 1, r.start)
    next(r::UnitRange, state::T) = (oftype(T,state), state + 1)
    done(r::UnitRange, state::T) = i == oftype(i,r.stop) + 1
end

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

لكن مرة أخرى ، ربما يكون "مقاربتي الأقل توغلاً" قصير النظر للغاية. لا أعرف حقا.

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

StefanKarpinski من الرائع أن ترى أنك تفكر في هذا.

حزمة الرسوم البيانية هي التي تحتاج إلى نظام الواجهة أكثر من غيرها. سيكون من المثير للاهتمام أن نرى كيف يمكن لهذا النظام أن يعبر عن الواجهات الموضحة هنا: http://graphsjl-docs.readthedocs.org/en/latest/interface.html.

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

لكنني أفهم نوعًا ما أنه قد يرغب المرء في ترك تطبيق الواجهة "مفتوحًا". وقد يؤدي إعلان الوظيفة في النوع إلى تعقيد اللغة كثيرًا. ربما يكون النهج الذي قمت بتطبيقه في # 7025 كافياً. إما أن تضع verify_interface بعد إعلانات الوظيفة (أو في اختبار الوحدة) أو تأجيلها إلى MethodError .

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

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

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

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

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

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

لا ، هذا بالتأكيد لا يبدو تقييدًا جيدًا.

StefanKarpinski ذكرت أنك ستتمكن من الإرسال على واجهة. أيضًا في بناء الجملة implement تكمن الفكرة في أن نوعًا معينًا يقوم بتنفيذ الواجهة.

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

لنفترض أنني أستخدم مكتبة M:

module M

abstract A
abstract B

type A2 <: A end
type A3 <: A end
type B2 <: B end

function f(a::A2, b::B2)
    # do stuff
end

function f(a::A3, b::B2)
    # do stuff
end

export f, A, B, A2, A3, B2
end # module M

الآن أريد أن أكتب دالة عامة تأخذ A و B

using M

function userfunc(a::A, b::B, i::Int)
    res = f(a, b)
    res + i
end

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

الوحدات الأخرى التي ترغب في تقديم أنواع فرعية محددة من A و B يجب أن يُتوقع منها أن توفر تطبيقات f . لتجنب الانفجار الاندماجي للطرق المطلوبة ، أتوقع أن تحدد المكتبة f مقابل أنواع الملخصات:

module N

using M

type SpecialA <: A end
type SpecialB <: B end

function M.f(a::SpecialA, b::SpecialB)
    # do stuff
end

function M.f(a::A, b::SpecialB)
    # do stuff
end

function M.f(a::SpecialA, b::B)
    # do stuff
end

export SpecialA, SpecialB

end # module N

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

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

@ abe-egnor أعتقد أيضًا أن اتباع نهج أكثر انفتاحًا يبدو أكثر جدوى. يفتقر نموذجي الأولي # 7025 إلى شيئين أساسيين:
أ) صياغة أفضل لتعريف الواجهات
ب) تعريفات نوع حدودي

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

https://gist.github.com/abe-egnor/503661eb4cc0d66b4489 لديه أول طعنة لي في نوع التنفيذ الذي كنت أفكر فيه. باختصار ، الواجهة عبارة عن وظيفة من الأنواع إلى قائمة المهام التي تحدد الاسم وأنواع المعلمات للوظائف المطلوبة لتلك الواجهة. يقوم الماكرو @implement باستدعاء الوظيفة للأنواع المحددة ثم يقوم بتقسيم الأنواع في تعريفات الوظائف المحددة ، والتحقق من أن جميع الوظائف قد تم تعريفها.

نقاط جيدة:

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

نقاط سيئة:

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

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

<strong i="7">@interface</strong> stack(Container, Data) begin
  stack_push!(Container, Data)
end

<strong i="8">@implement</strong> stack{T}(Vector{T}, T) begin
  stack_push!(vec, x) = push!(vec, x)
end

في هذه الحالة ، يتم تمديد معلمات النوع إلى الطرق المحددة في الواجهة ، لذلك يتم توسيعها إلى stack_push!{T}(vec::Vector{T}, x::T) = push!(vec, x) ، وهو ما أعتقد أنه الشيء الصحيح تمامًا.

سأعيد العمل على تطبيقي الأولي للقيام بذلك عندما أحصل على الوقت ؛ ربما في حدود أسبوع.

لقد تصفحت الإنترنت قليلاً لأرى ما تفعله لغات البرمجة الأخرى بشأن الواجهات ، والميراث وما إلى ذلك ، وابتكرت بعض الأفكار. (في حالة اهتمام أي شخص هنا ، فإن الملاحظات التقريبية للغاية التي أخذتها https://gist.github.com/mauro3/e3e18833daf49cdf8f60)

باختصار ، ربما يمكن تنفيذ الواجهات من خلال:

  • السماح بميراث متعدد لأنواع مجردة ، و
  • السماح بالوظائف العامة كمجالات لأنواع مجردة.

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

القصة الطويلة:

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

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

ماذا عن تحويل الأنواع المجردة لجوليا إلى أكثر من فئة واجهة / سمة / نوع ، مع الاحتفاظ بجميع الأنواع في التسلسل الهرمي None<: ... <:Any ؟ قد يستلزم ذلك:
1) السماح بميراث متعدد لأنواع (مجردة) (الإصدار رقم 5)
2) السماح بربط الوظائف بأنواع مجردة (أي تحديد واجهة)
3) اسمح بتحديد تلك الواجهة ، لكل من النوع المجرد (أي التنفيذ الافتراضي) والأنواع الملموسة.

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

abstract Container  <: Iterable, Indexable, ...
end

abstract AbstractArray <: Container, Arithmetic, ...
    ...
end

abstract  Associative{K,V} <: Iterable, Indexable, Eq
    haskey :: (Associative, _) --> Bool
end

abstract Iterable{T,S}
    start :: Iterable --> S
    done  :: (Iterable,S) --> Bool
    next  :: (Iterable,S) --> (T,S)
end

abstract Indexable{A,I}
    getindex  :: (A,I) --> eltype(A)
    setindex! :: (A,I) --> A
    get! :: (A, I, eltype(A)) --> eltype(A)
    get :: (A, I, eltype(A)) --> eltype(A)
end

abstract Eq{A,B}
    == :: (A,B) --> Boolean
end
...

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

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

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

لقد وجدت أيضًا أثناء قراءة StefanKarpinski كتابة أن استخدام abstract بدلاً من interface قد يكون منطقيًا. ومع ذلك ، في هذه الحالة ، من المهم أن يرث abstract خاصية واحدة مهمة interface : يتم تحديد إمكانية تحديد نوع إلى implement و interface _after_. بعد ذلك يمكنني استخدام نوع typA من lib A مع خوارزمية algoB من lib B من خلال التصريح في الكود الخاص بي أن typeA ينفذ الواجهة المطلوبة بواسطة algoB (أعتقد أن هذا يعني أن الأنواع الملموسة لها نوع من الميراث المتعدد المفتوح).

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

آسف للجميع

------------------ 原始 邮件 ------------------
发件人: "جاكوب كوين" [email protected]؛
发送 时间: 2014 年 9 月 12 日 (星期五) 6:23
收件人: "JuliaLang / julia" [email protected]؛
抄送: "تنفيذ" [email protected] ؛
主题: Re: [جوليا] واجهات لأنواع الملخصات (# 6975)

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

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub.

implement آسف جدًا ؛ لست متأكدًا من كيفية إرسالنا لكم. إذا لم تكن تعرف بالفعل ، يمكنك إزالة نفسك من تلك الإشعارات باستخدام زر "إلغاء الاشتراك" الموجود على الجانب الأيمن من الشاشة.

لا ، أريد فقط أن أقول إنني لا أستطيع مساعدتك كثيرًا لقول ساري

------------------ 原始 邮件 ------------------
发件人: "pao" [email protected] ؛
发送 时间: 2014 年 9 月 13 (星期六) 9:50
收件人: "JuliaLang / julia" [email protected]؛
抄送: "تنفيذ" [email protected] ؛
主题: Re: [جوليا] واجهات لأنواع الملخصات (# 6975)

implement آسف جدًا ؛ لست متأكدًا من كيفية إرسالنا لكم. إذا لم تكن تعرف بالفعل ، يمكنك إزالة نفسك من تلك الإشعارات باستخدام زر "إلغاء الاشتراك" الموجود على الجانب الأيمن من الشاشة.

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub.

لا نتوقع منك! لقد كان حادثًا ، نظرًا لأننا نتحدث عن ماكرو Julia يحمل نفس اسم المستخدم الخاص بك. شكرا!

لقد رأيت للتو أن هناك بعض الميزات التي يحتمل أن تكون مثيرة للاهتمام (ربما تكون ذات صلة بهذه المشكلة) عملت عليها في Rust: http://blog.rust-lang.org/2014/09/15/Rust-1.0.html ، على وجه الخصوص: https : //github.com/rust-lang/rfcs/pull/195

بعد رؤية THTT ("Tim Holy Trait Trick") ، أعطيت واجهات / سمات مزيدًا من التفكير خلال الأسابيع القليلة الماضية. جئت ببعض الأفكار وتنفيذها: Traits.jl . أولاً ، (أعتقد) يجب النظر إلى السمات على أنها عقد يتضمن نوعًا واحدًا أو عدة أنواع . هذا يعني أن مجرد ربط وظائف واجهة ما بنوع تجريدي واحد ، كما اقترحت أنا وآخرون أعلاه ، لا يعمل (على الأقل ليس في الحالة العامة للسمة التي تتضمن عدة أنواع). وثانيًا ، يجب أن تكون الطرق قادرة على استخدام السمات للإرسال ، كما اقترح StefanKarpinski أعلاه.

قال Nuff ، إليك مثالاً باستخدام حزمة Traits.jl الخاصة بي:

<strong i="12">@traitdef</strong> Eq{X,Y} begin
    # note that anything is part of Eq as ==(::Any,::Any) is defined
    ==(X,Y) -> Bool
end

<strong i="13">@traitdef</strong> Cmp{X,Y} <: Eq{X,Y} begin
    isless(X,Y) -> Bool
end

يوضح هذا أن Eq و Cmp هي عقود بين النوعين X و Y . Cmp لديه Eq كصورة عمودية ، على سبيل المثال ، يجب الوفاء بكل من Eq و Cmp . في الجسم @traitdef ، تحدد تواقيع الوظيفة الطرق التي يجب تعريفها. أنواع الإرجاع لا تفعل شيئًا في الوقت الحالي. لا تحتاج الأنواع إلى تنفيذ سمة بشكل صريح ، فقط تنفيذ الوظائف سيفي بالغرض. يمكنني التحقق مما إذا كان ، على سبيل المثال ، Cmp{Int,Float64} سمة بالفعل:

julia> istrait(Cmp{Int,Float64})
true

julia> istrait(Cmp{Int,String})
false

تنفيذ السمات الصريح ليس في الحزمة حتى الآن ولكن يجب أن يكون واضحًا إلى حد ما لإضافته.

يمكن تعريف دالة باستخدام _trait-dispatch_ على هذا النحو

<strong i="31">@traitfn</strong> ft1{X,Y; Cmp{X,Y}}(x::X,y::Y) = x>y ? 5 : 6

هذا يصرح عن دالة ft1 تأخذ وسيطتين مع القيد الذي تحتاجه أنواعهم لتحقيق Cmp{X,Y} . يمكنني إضافة طريقة أخرى للإرسال على سمة أخرى:

<strong i="37">@traitdef</strong> MyT{X,Y} begin
    foobar(X,Y) -> Bool
end
# and implement it for a type:
type A
    a
end
foobar(a::A, b::A) = a.a==b.a

<strong i="38">@traitfn</strong> ft1{X,Y; MyT{X,Y}}(x::X,y::Y) = foobar(x,y) ? -99 : -999

يمكن الآن استدعاء وظائف السمات هذه تمامًا مثل الوظائف العادية:

julia> ft1(4,5)
6

julia> ft1(A(5), A(6))
-999

تعد إضافة نوع آخر إلى سمة لاحقًا أمرًا سهلاً (ولن يكون الأمر كذلك عند استخدام Unions لـ ft1):

julia> ft1("asdf", 5)
ERROR: TraitException("No matching trait found for function ft1")
 in _trait_type_ft1 at

julia> foobar(a::String, b::Int) = length(a)==b  # adds {String, Int} to MyTr
foobar (generic function with 2 methods)

julia> ft1("asdf", 5)
-999

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

باختصار ، يتحول إرسال السمات

<strong i="51">@traitfn</strong> f{X,Y; Trait1{X,Y}}(x::X,y::Y) = x+y

في شيء مثل هذا (مبسط قليلاً)

f(x,y) = _f(x,y, checkfn(x,y))
_f{X,Y}(x::X,y::Y,::Type{Trait1{X,Y}}) = x+y
# default
checkfn{T,S}(x::T,y::S) = error("Function f not implemented for type ($T,$S)")
# add types-tuples to Trait1 by modifying the checkfn function:
checkfn(::Int, ::Int) = Trait1{Int,Int}
f(1,2) # 3

في الحزمة ، تتم أتمتة توليد checkfn بواسطة stagedfuncitons. لكن راجع الملف التمهيدي لـ Traits.jl لمزيد من التفاصيل.

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

(تم التعديل في 27 أكتوبر لتعكس التغييرات الطفيفة في Traits.jl )

هل حزمة Traits.jl جاهزة للاستكشاف؟ يقول التمهيدي "تنفيذ واجهات مع traitimpl (لم يتم الانتهاء بعد ...)" - هل هذا عيب مهم؟

إنه جاهز للاستكشاف (بما في ذلك الأخطاء :-). يعني @traitimpl المفقود ذلك بدلاً من

<strong i="7">@traitimpl</strong> Cmp{T1, T2} begin
   isless(t1::T1, t2::T2) = t1.t < t2.f
end

أنت فقط تحدد الوظيفة (الوظائف) يدويًا

Base.isless(t1::T1, t2::T2) = t1.t < t2.f

لاثنين من الأنواع الخاصة بك T1 و T2 .

لقد أضفت الماكرو @traitimpl ، لذا فإن المثال أعلاه يعمل الآن. لقد قمت أيضًا بتحديث برنامج README بتفاصيل عن الاستخدام. وأضفت مثالا تنفيذ جزء منlindahua اجهة Graphs.jl:
https://github.com/mauro3/Traits.jl/blob/master/examples/ex_graphs.jl

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

أنا أيضا أجد هذا رائع جدا. هناك الكثير مما يعجبك في هذا النهج. عمل جيد.

: +1:

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

هذه واحدة من تلك الحزم التي تجعل المرء ينظر إلى الكود الخاص به في ضوء جديد. رائع جدا.

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

سأقوم بإعادة هيكلة الطرود الخاصة بي أيضًا :)

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

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

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

Array{X; Cmp{X}} # an array of comparables
myvar::Type{X; Cmp{X}} # just a variable which is comparable

أتفق مع نقطة mauro3 أعلاه ، والتي لها سمات (حسب تعريفه ، والتي أعتقد أنها جيدة جدًا) تعادل الأنواع المجردة التي

  • السماح بميراث متعدد ، و
  • السماح بالوظائف العامة كحقول

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

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

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

أليست الأنواع المجردة مجرد مثال "ممل" للسمات؟

إذا كان الأمر كذلك ، فهل من الممكن الاحتفاظ بالصيغة الحالية وتغيير معناه ببساطة للسمة (إعطاء الحرية المتعامدة وما إلى ذلك إذا أراد المستخدم ذلك)؟

_ أتساءل عما إذا كان هذا قد يكون قادرًا أيضًا على معالجة المثال Point{Float64} <: Pointy{Real} (لست متأكدًا مما إذا كان هناك رقم مشكلة)؟ _

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

يبدو أن هناك الكثير من العمل ، ولكن ربما يمكن تطويره ببطء دون أن يتعرض المجتمع للكثير من الانكسار. لذلك على الأقل حصلنا على ذلك ؛)

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

FWIW ، لقد وجدت مناقشة Simon Peyton-Jones للطبقات في الحديث أدناه مفيدة حقًا حول كيفية استخدام شيء مثل السمات بدلاً من التصنيف الفرعي: http://research.microsoft.com/en-us/um/people/simonpj/ أوراق / haskell-retrospective / ECOOP-July09.pdf

نعم ، علبة كاملة من الديدان!

johnmyleswhite ، شكرًا على الرابط ، ممتع جدًا. إليك رابط الفيديو الخاص به ، والذي يستحق المشاهدة لملء الفجوات. يبدو أن هذا العرض التقديمي يتطرق إلى الكثير من الأسئلة التي لدينا هنا. ومن المثير للاهتمام ، أن تطبيق فئات النوع يشبه إلى حد كبير ما هو موجود في Traits.jl (خدعة تيم ، السمات هي أنواع بيانات). https://www.haskell.org/haskellwiki/Multi-parameter_type_class من Haskell يشبه إلى حد كبير Traits.jl. أحد أسئلته في الحديث هو: "بمجرد أن نتبنى الأدوية الجنيسة بإخلاص ، هل ما زلنا بحاجة حقًا إلى تصنيف فرعي." (الأدوية الجنسية هي وظائف بارامترية متعددة الأشكال ، على ما أعتقد ، انظر ) وهو ما كان يتأمل فيهskariel و hayd أعلاه.

بالإشارة إلى skariel و hayd ، أعتقد أن سمات المعامل الفردي (كما في Traits.jl) قريبة جدًا من الأنواع المجردة بالفعل ، باستثناء أنه يمكن أن يكون لها تسلسل هرمي آخر ، أي وراثة متعددة.

لكن السمات متعددة المعلمات تبدو مختلفة بعض الشيء ، على الأقل كانت في ذهني. كما رأيتهم ، يبدو أن معلمات النوع لأنواع الملخصات تدور في الغالب حول الأنواع الأخرى الموجودة في النوع ، على سبيل المثال ، Associative{Int,String} تقول أن الأمر يحتوي على مفاتيح Int و String قيم Tr{Associative,Int,String}... وجود بعض "العقود" بين Associative و Int s و Strings . ولكن بعد ذلك ، ربما يجب قراءة Associative{Int,String} بهذه الطريقة أيضًا ، أي أن هناك طرقًا مثل getindex(::Associative, ::Int) -> String ، setindex!(::Associative, ::Int, ::String) ...

@ mauro3 الشيء المهم هو تمرير كائنات من النوع Associative كوسيطة لدالة ، حتى تتمكن بعد ذلك من إنشاء Associative{Int,String} نفسها:

function f(A::Associative)
  a = A{Int,String}()  # create new associative
  a[1] = "one"
  return a
end

يمكنك تسمية هذا على سبيل المثال f(Dict) .

eschnett ، آسف ، لا أفهم ما

@ mauro3 أعتقد أنني كنت أفكر بطريقة معقدة للغاية ؛ تجاهلني.

لقد قمت بتحديث Traits.jl بـ:

  • حل غموض السمات
  • الأنواع المرتبطة
  • باستخدام @doc للمساعدة
  • اختبار أفضل لطرق مواصفات السمات

راجع https://github.com/mauro3/Traits.jl/blob/master/NEWS.md للحصول على التفاصيل. نرحب بالتعليقات!

قام @ Rory-Finnegan بتجميع حزمة واجهة https://github.com/Rory-Finnegan/Interfaces.jl

لقد ناقشت هذا الأمر مؤخرًا مع mdcfrancis ونعتقد أن شيئًا مشابهًا لبروتوكولات Clojure سيكون بسيطًا وعمليًا. الميزات الأساسية هي (1) البروتوكولات هي نوع جديد من النوع ، (2) يمكنك تحديدها من خلال سرد بعض تواقيع الطريقة ، (3) الأنواع الأخرى تنفذها ضمنيًا فقط من خلال مطابقة تعريفات الطريقة. سوف تكتب على سبيل المثال

protocol Iterable
    start(::_)
    done(::_, state)
    next(::_, state)
end

ولدينا isa(Iterable, Protocol) و Protocol <: Type . وبطبيعة الحال ، يمكنك إرسال هذه. يمكنك التحقق مما إذا كان أحد الأنواع ينفذ بروتوكولًا باستخدام T <: Iterable .

فيما يلي قواعد التصنيف الفرعي:

دع P ، Q تكون أنواع البروتوكول
دع T يكون نوعًا غير بروتوكول

| الإدخال | نتيجة |
| --- | --- |
| P <: أي | صحيح |
| أسفل <: P | صحيح |
| (union، unionall، var) <: P | استخدام القاعدة العادية التعامل مع P كنوع أساسي |
| P <: (union، unionall، var) | استخدام القاعدة العادية |
| P <: P | صحيح |
| P <: س | طرق التحقق (س) <: الأساليب (ف) |
| P <: T | خطأ |
| T <: P | توجد طرق P مع استبدال T بـ _ |

آخر واحد هو الأكبر: لاختبار T <: P ، استبدلت T بـ _ في تعريف P وتحقق من method_exists لكل توقيع. بالطبع ، هذا يعني بحد ذاته أن التعريفات الاحتياطية التي تطرح "يجب عليك تنفيذ هذا" تصبح شيئًا سيئًا للغاية. نأمل أن يكون هذا أكثر من قضية تجميلية.

مشكلة أخرى هي أن هذا التعريف دائري إذا تم تعريف على سبيل المثال start(::Iterable) . مثل هذا التعريف لا معنى له حقا. يمكننا بطريقة ما منع هذا ، أو اكتشاف هذه الدورة أثناء فحص النوع الفرعي. لست متأكدًا بنسبة 100٪ من أن اكتشاف الدورة البسيط يعمل على إصلاحه ، لكن يبدو أنه معقول.

بالنسبة لنوع التقاطع لدينا:

| الإدخال | نتيجة |
| --- | --- |
| P ∩ (union، unionall، tvar) | استخدام القاعدة العادية |
| ف ∩ س | ص |
| P ∩ T | تي |

يوجد خياران لـ P ∩ Q:

  1. قم بالتقريب بإرجاع P أو Q (على سبيل المثال ، أيهما أولًا من ناحية المعجم). هذا صحيح فيما يتعلق بالاستدلال على الكتابة ، ولكنه قد يكون مزعجًا في مكان آخر.
  2. قم بإعادة بروتوكول خاص جديد يحتوي على اتحاد التواقيع في P و Q.
  3. أنواع التقاطع. ربما يقتصر على البروتوكولات فقط.

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

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

بعد # 13412 ، يمكن "ترميز" البروتوكول كـ UnionAll _ عبر اتحاد أنواع tuple (حيث يكون العنصر الأول من كل مجموعة داخلية هو نوع الوظيفة المعنية). هذه فائدة من ذلك التصميم لم تحدث لي من قبل. على سبيل المثال ، يبدو أن التصنيف الفرعي الهيكلي للبروتوكولات يسقط تلقائيًا.

بالطبع ، هذه البروتوكولات هي أسلوب "المعلمة الواحدة". أنا أحب بساطة هذا ، بالإضافة إلى أنني لست متأكدًا من كيفية التعامل مع مجموعات من الأنواع بأناقة مثل T <: Iterable .

كانت هناك بعض التعليقات حول هذه الفكرة في الماضي ، x-ref https://github.com/JuliaLang/julia/issues/5#issuecomment -37995516.

هل ندعم ، على سبيل المثال

protocol Iterable{T}
    start(::_)::T
    done(::_, state::T)
    next(::_, state::T)
end

رائع ، أنا معجب بهذا (خاصةً مع ملحقKeno )!

+1 هذا ما أريده بالضبط!

Keno يعد هذا بالتأكيد

يبدو أنه يمكنك استخدام قرن الحذاء في السمات (مثل O (1) الفهرسة الخطية لأنواع تشبه المصفوفة) في هذا المخطط. ستحدد طريقة وهمية مثل hassomeproperty(::T) = true (لكن _not_ hassomeproperty(::Any) = false ) وبعد ذلك

protocol MyProperty
hassomeproperty(::_)
end

هل يمكن أن يظهر _ عدة مرات بنفس الطريقة في تعريف البروتوكول ، مثل

protocol Comparable
  >(::_, ::_)
  =(::_, ::_0
end

يمكن أن تظهر عدة مرات بنفس الطريقة في تعريف البروتوكول

نعم. ما عليك سوى إسقاط نوع المرشح لكل مثيل _ .

JeffBezanson أتطلع حقًا إلى ذلك. وتجدر الإشارة بشكل خاص إلى "بُعد" البروتوكول. في ذلك يمكنني تنفيذ بروتوكول محدد / مخصص لنوع دون أن يكون لدى مؤلف النوع أي معرفة بوجود البروتوكول.

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

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

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

زوجان من التعليقات:

يُعرف اقتراح Keno (حقًا state في الأصل الخاص بـ Jeff) بالأنواع المرتبطة. لاحظ أنها مفيدة بدون أنواع الإرجاع أيضًا. الصدأ لديه إدخال يدوي لائق. أعتقد أنها فكرة جيدة ، على الرغم من أنها ليست ضرورية كما في Rust. لا أعتقد أنه يجب أن تكون معلمة للسمة على الرغم من ذلك: عند تحديد دالة إرسال على Iterable لن أعرف ما هو T .

في تجربتي ، method_exists غير قابل للاستخدام في شكله الحالي لهذا (# 8959). لكن من المفترض أن يتم إصلاح هذا في # 8974 (أو بهذا). لقد وجدت تواقيع أسلوب المطابقة مقابل سمات السمات هو الجزء الأصعب عند القيام بـ Traits.jl ، خاصة لحساب الدوال ذات المعلمات & vararg ( انظر ).

من المفترض أن يكون الميراث ممكنًا أيضًا؟

أود حقًا رؤية آلية تسمح بتعريف عمليات التنفيذ الافتراضية. الطريقة التقليدية هي أنه للمقارنة ، تحتاج فقط إلى تحديد اثنين من بين = ، < ، > ، <= ، >= . ربما هذا هو المكان الذي تكون فيه الدورة التي ذكرها جيف مفيدة بالفعل. بالاستمرار في المثال أعلاه ، فإن تحديد start(::Indexable) = 1 و done(i::Indexable,state)=length(i)==state سيجعل هذه الإعدادات الافتراضية. وبالتالي فإن العديد من الأنواع تحتاج فقط إلى تحديد next .

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

نعم ، يبدو أنه بإمكاننا السماح بسهولة protocol Foo <: Bar, Baz ، وببساطة نسخ التواقيع من Bar and Baz إلى Foo.

السمات متعددة المعلمات قوية بالتأكيد. أعتقد أنه من المثير للاهتمام التفكير في كيفية دمجهم مع التصنيف الفرعي. يمكن أن يكون لديك شيء مثل TypePair{A,B} <: Trait ، لكن هذا لا يبدو صحيحًا تمامًا.

أعتقد أن اقتراحك (من حيث الميزات) يشبه في الواقع Swift أكثر من Clojure.

يبدو من الغريب (وأعتقد أنه مصدر ارتباك في المستقبل) أن نمزج (الأنواع) الاسمية والهيكلية (البروتوكول) الفرعية (لكن أعتقد أن هذا أمر لا مفر منه).

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

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

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

protocol Iterable
    start(::_)
    done(::_, state)
    next(::_, state)
end

قد يبدو من الطبيعي لهذا تحديد الوظائف العامة start ، done و next وأسمائهم المؤهلة بالكامل هي Iterable.start ، Iterable.done و Iterable.next . سيطبق النوع Iterable لكن مع تنفيذ جميع الوظائف العامة في بروتوكول Iterable . لقد اقترحت شيئًا مشابهًا جدًا لهذا منذ بعض الوقت (لا يمكنني العثور عليه الآن) ، ولكن مع وجود الجانب الآخر أنه عندما تريد تنفيذ بروتوكول ، فإنك تفعل هذا:

implement T <: Iterable
    # in here `start`, `done` and `next` are automatically imported
    start(x::T) = something
    done(x::T, state) = whatever
    next(x::T, state) = etcetera, nextstate
end

هذا من شأنه أن يبطل "البُعد" الذي ذكره mdcfrancis ، إذا كنت أفهم ذلك ، ولكن مرة أخرى ، لا أرى حقًا فائدة التمكن من تنفيذ البروتوكول "عن طريق الخطأ". هل يمكنك توضيح سبب شعورك بأن هذا مفيد ،mdcfrancis؟ أعلم أن Go تصنع الكثير من هذا ، ولكن يبدو أن هذا يرجع إلى أن Go لا يمكنها كتابة البط ، وهو ما تستطيع جوليا القيام به. أظن أن الحصول على كتل implement سيقضي على جميع الاحتياجات تقريبًا لاستخدام import بدلاً من using ، والتي ستكون فائدة كبيرة.

اقترحت شيئًا مشابهًا جدًا لهذا منذ بعض الوقت (لا يمكنني العثور عليه الآن)

ربما https://github.com/JuliaLang/julia/issues/6975#issuecomment -44502467 وما قبله https://github.com/quinnj/Datetime.jl/issues/27#issuecomment -31305128؟ (تحرير: https://github.com/JuliaLang/julia/issues/6190#issuecomment-37932021.)

نعم ، هذا كل شيء.

StefanKarpinski تعليقات سريعة ،

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

إذا تم السماح بنوع من الوراثة على البروتوكولات ، فإن MySuperIterabe ،
يمكن أن يوسع Base.Iterable ، من أجل إعادة استخدام الأساليب الحالية.

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

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

هل يمكنك التوسع في نقطة MyModule.MySuperIterable؟ أنا لا أرى من أين يأتي الإسهاب الإضافي. يمكن أن يكون لديك شيء مثل هذا ، على سبيل المثال:

protocol Enumerable <: Iterable
    # inherits start, next and done; adds the following:
    length(::_) # => Integer
end

وهو ما قاله ivarne .

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

module Iterable

function start end
function done end
function next end

jeff_protocol the_protocol
    start(::_)
    done(::_, state)
    next(::_, state)
end

end

ثم في السياقات التي يتم فيها التعامل مع Iterable كنوع ، نستخدم Iterable.the_protocol .

يعجبني هذا المنظور لأن بروتوكولات jeff / mdcfrancis تبدو متعامدة جدًا مع كل شيء آخر هنا. الشعور الخفيف بعدم الحاجة إلى قول "X ينفذ البروتوكول Y" إلا إذا كنت تريد أن تشعر بـ "جوليان" بالنسبة لي.

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

ليس لدي ما أضيفه على أساس تقني ، ولكن كمثال على "البروتوكولات" المستخدمة في البرية في جوليا (نوعًا ما) سيكون JuMP هو الذي يحدد وظيفة حلال ، على سبيل المثال:

https://github.com/JuliaOpt/JuMP.jl/blob/master/src/solvers.jl#L223 -L246

        # If we already have an MPB model for the solver...
        if m.internalModelLoaded
            # ... and if the solver supports updating bounds/objective
            if applicable(MathProgBase.setvarLB!, m.internalModel, m.colLower) &&
               applicable(MathProgBase.setvarUB!, m.internalModel, m.colUpper) &&
               applicable(MathProgBase.setconstrLB!, m.internalModel, rowlb) &&
               applicable(MathProgBase.setconstrUB!, m.internalModel, rowub) &&
               applicable(MathProgBase.setobj!, m.internalModel, f) &&
               applicable(MathProgBase.setsense!, m.internalModel, m.objSense)
                MathProgBase.setvarLB!(m.internalModel, copy(m.colLower))
                MathProgBase.setvarUB!(m.internalModel, copy(m.colUpper))
                MathProgBase.setconstrLB!(m.internalModel, rowlb)
                MathProgBase.setconstrUB!(m.internalModel, rowub)
                MathProgBase.setobj!(m.internalModel, f)
                MathProgBase.setsense!(m.internalModel, m.objSense)
            else
                # The solver doesn't support changing bounds/objective
                # We need to build the model from scratch
                if !suppress_warnings
                    Base.warn_once("Solver does not appear to support hot-starts. Model will be built from scratch.")
                end
                m.internalModelLoaded = false
            end
        end

رائع ، هذا مفيد. هل يكفي أن يكون m.internalModel هو الشيء الذي ينفذ البروتوكول ، أم أن كلا الوسيطتين مهمان؟

نعم ، يكفي m.internalModel لتنفيذ البروتوكول. الحجج الأخرى في الغالب مجرد نواقل.

نعم ، يكفي m.internalModel لتنفيذ البروتوكول

من الطرق الجيدة للعثور على أمثلة على البروتوكولات في البرية البحث عن مكالمات applicable و method_exists .

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

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

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

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

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

هناك شيء واحد يبدو مفيدًا لأنواع الملخصات وهو معلماتها ، على سبيل المثال كتابة convert(AbstractArray{Int}, x) . إذا كان AbstractArray بروتوكولًا ، فلن يلزم بالضرورة ذكر نوع العنصر Int في تعريف البروتوكول. إنها معلومات إضافية حول النوع _ جانب_ من الطرق المطلوبة. لذلك ، سيظل AbstractArray{T} و AbstractArray{S} نوعين مختلفين ، على الرغم من تحديد نفس الأساليب ، لذلك أعدنا تقديم الكتابة الاسمية. لذلك يبدو أن استخدام معلمات النوع هذا يتطلب نوعًا اسميًا من الكتابة.

إذن هل ستعطينا 2. ميراثًا مجردًا متعددًا؟

إذن هل ستعطينا 2. ميراثًا مجردًا متعددًا؟

لا. ستكون طريقة لدمج الميزات أو دمجها ، لكن كل ميزة ستظل تتمتع بالخصائص التي تمتلكها الآن.

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

أليست معلمات النوع في النظام الحالي جزءًا بطريقة ما من الواجهة الضمنية؟ على سبيل المثال ، يعتمد هذا التعريف على ما يلي: ndims{T,n}(::AbstractArray{T,n}) = n والعديد من الوظائف المعرفة من قبل المستخدم تفعل ذلك أيضًا.

لذلك ، في بروتوكول جديد + نظام وراثة مجردة ، سيكون لدينا AbstractArray{T,N} و ProtoAbstractArray . الآن يجب أن يكون النوع الذي لم يكن اسميًا AbstractArray قادرًا على تحديد ما هي المعلمات T و N ، على الأرجح من خلال الترميز الثابت eltype و ndims . بعد ذلك ، يجب إعادة كتابة جميع الوظائف ذات المعلمات في AbstractArray s لاستخدام eltype و ndims بدلاً من المعلمات. لذلك ، ربما يكون من المنطقي أن يحمل البروتوكول المعلمات أيضًا ، لذلك قد تكون الأنواع المرتبطة مفيدة جدًا بعد كل شيء. (لاحظ أن أنواع الخرسانة ستظل بحاجة إلى معلمات.)

أيضا، وتجميع أنواع في البروتوكول باستخدامmalmaud الصورة خدعة: https://github.com/JuliaLang/julia/issues/6975#issuecomment -161056795 هو أقرب إلى الكتابة الاسمية: التجمع هو فقط بسبب اختيار أنواع و لا تشترك الأنواع في أي واجهة (قابلة للاستخدام). إذن ، ربما تتداخل الأنواع والبروتوكولات المجردة قليلاً؟

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

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

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

protocol PAbstractArray{T,N}
    size(_)
    getindex(_, i::Int)
    ...
end

type MyType1
    a::Array{Int,1}
    ...
end

impl MyType for PAbstractArray{Int,1}
    size(_) = size(_.a)
    getindex(_, i::Int) = getindex(_.a,i)
    ...
end

# an implicit definition could look like:
associatedT(::Type{PAbstractArray}, :T, ::Type{MyType}) = Int
associatedT(::Type{PAbstractArray}, :N, ::Type{MyType}) = 1
size(mt::MyType) = size(mt.a)
getindex(mt::MyType, i::Int) = getindex(mt.a,i)


# parameterized type
type MyType2{TT, N, T}
    a::Array{T, N}
    ...
end

impl MyType2{TT,N,T} for PAbstractArray{T,N}
    size(_) = size(_.a)
    getindex(_, i::Int) = getindex(_.a,i)
    ...
end

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

protocol PAbstractArray{eltype,ndims}
    size(_)
    getindex(_, i::Int)
    ...
end

protocol Indexable{eltype}
    getindex(_, i::Int)
end

هل لدينا PAbstractArray{Int,1} <: Indexable{Int} ؟ أعتقد أن هذا يمكن أن يعمل بشكل جيد للغاية إذا كانت المعلمات مطابقة بالاسم. ربما يمكننا أيضًا أتمتة التعريف الذي يجعل eltype(x) يُرجع المعلمة eltype من نوع x .

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

لذلك يبدو أنه مع مثل هذه الآلية ، لم نعد بحاجة لأنواع مجردة. يمكن أن يصبح AbstractArray{T,N} بروتوكولًا. ثم نحصل تلقائيًا على وراثة متعددة (من البروتوكولات). أيضًا ، استحالة الوراثة من الأنواع الملموسة (وهي شكوى نسمعها أحيانًا من الوافدين الجدد) أمر واضح ، حيث سيتم دعم وراثة البروتوكول فقط.

جانباً: سيكون من الرائع حقًا أن تكون قادرًا على التعبير عن السمة Callable . يجب أن تبدو كما يلي:

protocol Callable
    ::TupleCons{_, Bottom}
end

حيث يتطابق TupleCons بشكل منفصل مع العنصر الأول في المجموعة ، وبقية العناصر. الفكرة هي أن هذا يتطابق طالما أن جدول الطريقة _ غير فارغ (الجزء السفلي هو نوع فرعي من كل نوع من أنواع المجموعات الوسيطة). في الواقع ، قد نرغب في إنشاء بنية Tuple{a,b} لـ TupleCons{a, TupleCons{b, EmptyTuple}} (انظر أيضًا # 11242).

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

jakebolewski هل يمكنك التفكير في مثال؟ من الواضح أنهما لن يكونا نفس الشيء تمامًا ؛ أود أن أقول إن السؤال هو أكثر ما إذا كان بإمكاننا تدليك واحد بحيث يمكننا التغلب عليه دون الحصول على كليهما.

ربما أفتقد النقطة ، ولكن كيف يمكن للبروتوكولات ترميز أنواع مجردة معقدة بشكل معتدل مع قيود ، مثل:

typealias BigMatrix ∃T, T <: Union{BigInt,BigFloat} AbstractArray{T,2}

دون الحاجة إلى تعداد كل الاحتمالات اسميًا؟

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

يمكنني تخيل ما يلي (بطبيعة الحال ، توسيع التصميم إلى حدوده العملية):

BigMatrix = ∃T, T<:Union{BigInt, BigFloat} protocol { eltype = T, ndims = 2 }

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

AbstractArray = ∃T ∃N protocol { eltype=T, ndims=N }

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

أدركت أيضًا أن هذا يمكن أن يوفر فتحة هروب من مشاكل الغموض: التقاطع بين نوعين يكون فارغًا إذا كان لديهما قيم متضاربة لبعض المعلمات. لذلك إذا أردنا نوع Number لا لبس فيه فقد يكون لدينا

protocol Number
    super = Number
    +(_, _)
    ...
end

هذا يشاهد super كمجرد خاصية أخرى من النوع.

يعجبني بناء جملة البروتوكول المقترح ، لكن لدي بعض الملاحظات.

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

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

protocol Foo{bar}
    ...
end

protocol Bar{foo<:Foo}
   ...
end

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

(ب)

هل لدينا PAbstractArray {Int، 1} <: Indexable {Int}؟ أعتقد أن هذا يمكن أن يعمل بشكل جيد للغاية إذا كانت المعلمات مطابقة بالاسم.

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

module SomeBigLibrary
  # Assuming required definitions

  protocol Baz{el1type}
    Base.foo(_, i::el1type) # say `convert`
    baz(_)
  end
end

module SomeOtherLibrary
  # Assuming required definitions

  protocol Bar{el2type}
    Base.foo(_, i::el2type)
    bar(_)
  end
end

module My
  # Assuming required definitions

  protocol Protocol{el_type} # What do I put here to get both subtypes correctly!
    Base.foo(_, i::el_type)
    SomeBigLibrary.baz(_)
    SomeOtherLibrary.bar(_)
  end
end

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

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

أعتقد أن هذه هي المشكلة التي وصفتها في المثال super = Number . عندما نعلن عن bitstype Int16 <: Signed (سؤالي الآخر هو حتى كيف يتم تطبيق Number أو Signed كبروتوكولات مع خصائص النوع الخاصة بهم على النوع الملموس؟) هل يرفق ذلك خصائص النوع من البروتوكول Signed ( super = Signed ) يميزه على أنه مختلف عن الأنواع المحددة بواسطة البروتوكول Unsigned ؟ لأن هذا حل غريب من وجهة نظري ، وليس فقط لأنني أجد معلمات النوع المسماة غريبة. إذا كان هناك بروتوكولان يتطابقان تمامًا باستثناء النوع الذي وضعوه في super ، فكيف يختلفان على أي حال؟ وإذا كان الاختلاف في السلوكيات بين مجموعات فرعية من النوع الأكبر (البروتوكول) ، فهل نحن في الحقيقة نعيد اختراع الغرض من الأنواع المجردة؟

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

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

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

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

أعتقد أن هذا يعمل على إصلاح مشكلة Signed مقابل Unsigned أعلاه:

  • حدد بروتوكولين ، كلاهما موروث من IntegerProtocol (يرث أي بنية بروتوكول ، NumberAddingProtocol ، IntegerSteppingProtocol ، إلخ) أحدهما من AbstractSignedInteger والآخر من AbstractUnsignedInteger ).
  • ثم يضمن المستخدم من النوع Signed كلاً من الوظيفة (من البروتوكول) والسلوك (من التسلسل الهرمي المجرد).
  • لا يمكن استخدام النوع الملموس AbstractSignedInteger بدون البروتوكولات _ بأي حال_.
  • ولكن المثير للاهتمام (وكميزة مستقبلية سبق ذكرها أعلاه) أنه يمكننا في النهاية إنشاء القدرة على حل الميزات المفقودة ، إذا كان هناك فقط IntegerSteppingProtocol (وهو أمر تافه وهو في الأساس مجرد اسم مستعار لوظيفة واحدة) بالنظر إلى AbstractUnsignedInteger يمكننا محاولة حل المشكلة لـ Signed خلال تنفيذ البروتوكولات الأخرى من حيث ذلك. ربما حتى مع شيء مثل convert .

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

تحرير: (و) مثال على النحو (بما في ذلك الجزء (أ) ).

تحرير 2 : تصحيح بعض الأخطاء ( :< بدلاً من <: ) ، إصلاح خيار ضعيف ( Foo بدلاً من ::Foo )

protocol {T<: Number}(Foo <: AbstractFoo; Bar <: AbstractBar) # Abstract inheritance
    IterableProtocol(::Foo) # Explicit protocol inheritance.

    # Implicit protocol inheritance.
    start(::Bar)
    next(::Bar, state) # These states should really share an anonymous internal type
    done(::Bar, state)

    # Custom method for protocol involving both participants, defines Foo / Bar relationship.
    set(::Foo, ::Bar, v::T)

    # Custom method only on Bar
    bar(::Bar)
end

# Protocols both Foo{T} and Bar{T}.

أرى مشاكل في بناء الجملة على النحو التالي:

  • أنواع داخلية مجهولة للبروتوكول (مثل متغيرات الحالة).
  • أنواع الإرجاع.
  • من الصعب تنفيذ الدلالات بكفاءة.

يحدد نوع الملخص _ ما هو الكيان. يعرف _Protocol_ ما يفعله الكيان. داخل حزمة واحدة ، يمكن تبادل هذين المفهومين: الكيان _ هو _ ما _ يفعل _. و "النوع المجرد" أكثر مباشرة. ومع ذلك ، هناك فرق بين الحزمتين: فأنت لا تطلب ما "هو" عميلك ، لكنك تطلب ما "يفعله" عميلك. هنا ، "النوع المجرد" لا يعطي أي معلومات عنه.

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

@ ميسون بيالي

لست متأكدًا من سبب ضرورة مطابقة المعلمات بالاسم

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

protocol Collection{T}
    eltype = T
end

ثم أي شيء مع خاصية تسمى eltype هو نوع فرعي من Collection . لا يهم ترتيب هذه "المعلمات" وموقعها.

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

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

"لديها أنواع تعلن نيتها في تنفيذ فئة مجردة والتحقق من الامتثال"

نعم ، أنا ضد جعل هذا النهج هو الميزة الأساسية.

إذا سمحنا للبروتوكولات أن ترث بروتوكولات متعددة ... والعديد من الأنواع المجردة

هل هذا يعني قول على سبيل المثال "T هو نوع فرعي من البروتوكول P إذا كان يحتوي على طرق x ، y ، z ، ويعلن أنه نوع فرعي من AbstractArray"؟ أعتقد أن هذا النوع من "البروتوكول + نوع الملخص" مشابه جدًا لما ستحصل عليه من خلال اقتراح الملكية الخاص بي super = T . من المسلم به ، في الإصدار الخاص بي ، لم أتوصل بعد إلى كيفية ربطهم بمقدمة تسلسل هرمي كما لدينا الآن (على سبيل المثال Integer <: Real <: Number ).

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

protocol {T :< Number}(Foo :< AbstractFoo; Bar :< AbstractBar) # Abstract inheritance
    IterableProtocol(Foo) # Explicit protocol inheritance.

    # Implicit protocol inheritance.
    start(Bar)
...

أنا لا أفهم هذا النحو.

  • هل هذا البروتوكول له اسم؟
  • ماذا تعني الأشياء الموجودة داخل { } و ( ) بالضبط؟
  • كيف تستخدم هذا البروتوكول؟ هل يمكنك إرسالها؟ إذا كان الأمر كذلك ، فماذا يعني تعريف f(x::ThisProtocol)=... ، بالنظر إلى أن البروتوكول يتعلق بأنواع متعددة؟

ثم أي شيء مع خاصية تسمى eltype هو نوع فرعي من المجموعة. لا يهم ترتيب هذه "المعلمات" وموقعها.

آها ، كان هناك سوء فهمي ، وهذا منطقي أكثر. وهي القدرة على التخصيص:

el1type = el_type
el2type = el_type

لحل مشكلتي كمثال.

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

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

من المسلم به ، في نسختي ، لم أتوصل بعد إلى كيفية ربطهم بمقدمة تسلسل هرمي كما لدينا الآن (على سبيل المثال ، عدد صحيح <: حقيقي <: رقم).

أعتقد أنه يمكنك استخدام super (على سبيل المثال مع Integer مثل Real ) ثم جعل super خاصًا والعمل كنوع مسمى أو إضافة طريقة لإضافة رمز دقة من النوع المخصص (ala python) وإنشاء قاعدة افتراضية للمعلمة super .

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

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

هل هذا البروتوكول له اسم؟

بروتوكولين لهما اسمان ( Foo ، و Bar ) يأتيان من كتلة واحدة ، ولكن بعد ذلك اعتدت على استخدام وحدات الماكرو لتوسيع تعريفات متعددة من هذا القبيل. كان هذا الجزء من تركيبتي محاولة لحل الجزء (أ) . إذا تجاهلت ذلك ، فقد يكون السطر الأول ببساطة هو protocol Foo{T <: Number, Bar <: AbstractBar} <: AbstractFoo (مع تعريف آخر منفصل للبروتوكول Bar ). أيضًا ، ستكون كل من Number و AbstractBar و AbstractFoo اختيارية ، كما هو الحال في تعريفات الأنواع العادية ،

ماذا تعني العناصر الموجودة بداخل {} و () بالضبط؟

{} هو قسم تعريف النوع المعياري القياسي. السماح باستخدام Foo{Float64} لوصف نوع يطبق بروتوكول Foo باستخدام Float64 على سبيل المثال. () هو في الأساس قائمة ربط متغيرة لهيكل البروتوكول (لذلك يمكن وصف عدة بروتوكولات في وقت واحد). من المحتمل أن يكون ارتباكك خطأي لأنني أخطأت في كتابة :< بدلاً من <: في الأصل. قد يكون من المفيد أيضًا تبديلها للاحتفاظ بالهيكل <<name>> <<parametric>> <<bindings>> ، حيث يمكن أن يكون <<name>> أحيانًا قائمة من الارتباطات.

كيف تستخدم هذا البروتوكول؟ هل يمكنك إرسالها؟ إذا كان الأمر كذلك ، فماذا يعني تعريف f(x::ThisProtocol)=... ، بالنظر إلى أن البروتوكول يتعلق بأنواع متعددة؟

يبدو أن مثال الإرسال الخاص بك صحيحًا لأنه من الناحية التركيبية في رأيي ، فعليك التفكير في التعريفات التالية:

protocol FooProtocol # Single protocol definition shortcut
    foo(::FooProtocol) # I changed my syntax here, protocol names inside the protocol block should referenced as types
end

abstract FooAbstract

# This next line could use better syntax, like a type alias with an Intersection or something.
protocol Foo <: FooAbstract
    FooProtocol(::Foo)
end

type Bar <: FooAbstract
  a
end

type Baz
  b
end

type Bax <: FooAbstract
  c
end

f(f::Any) = ... # def (0)

foo(x::Bar) = ... # def (1a)
foo(x::Baz) = ... # def (1b)

f(x::FooProtocol) = ... # def (2); Least specific type (structural)

f(Bar(...)) # Would call def (2)
f(Baz(...)) # Would call def (2)
f(Bax(...)) # Would call def (0)

f(x::FooAbstract) = ... # def (3); Named type, more specific than structural

f(Bar(...)) # Would call def (3)
f(Baz(...)) # Would call def (2)
f(Bax(...)) # Would call def (3)

f(x::Foo) = ... # def (4); Named structural type, more specific than equivalent named type

f(Bar(...)) # Would call def (4)
f(Baz(...)) # Would call def (2)
f(Bax(...)) # Would call def (3)

تستخدم البروتوكولات بشكل فعال النوع الأعلى المسمى (أي) ما لم يتم إعطاء نوع مجردة أكثر تحديدًا للتحقق من البنية. في الواقع ، قد يكون من المفيد السماح بشيء مثل typealias Foo Intersect{FooProtocol, Foo} (_Edit: كان Intersect هو الاسم الخاطئ ، وربما كان الانضمام بدلاً من ذلك كان Intersect محقًا في المرة الأولى _) بدلاً من استخدام بنية البروتوكول للقيام بذلك.

آه عظيم ، هذا منطقي أكثر بالنسبة لي الآن! يعد تحديد بروتوكولات متعددة معًا في نفس الكتلة أمرًا مثيرًا للاهتمام ؛ سوف أفكر أكثر في ذلك.

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

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

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

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

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

تحرير: إزالة مجموعة من الأشياء غير المجدية.

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

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

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

سيؤدي هذا إلى إضافة نوع جديد مع قواعد التصنيف الفرعي الخاصة به (https://github.com/JuliaLang/julia/issues/6975#issuecomment-160857877). للوهلة الأولى يبدو أنها متوافقة مع بقية النظام ويمكن توصيلها فقط.

هذه البروتوكولات هي إلى حد كبير إصدار "معلمة واحدة" لسمات @ mauro3 .

Join الخاص بك هو بالضبط تقاطع أنواع البروتوكول.

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

تعديل:

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

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

ماذا او ما؟

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

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

لما؟

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

مجرد بروتوكولات

(1) الدعوة

يمكن تمديد البروتوكول ليصبح بروتوكولًا (أكثر تفصيلاً).
يمكن تقليص البروتوكول ليصبح بروتوكولًا (أقل تفصيلاً).
يمكن تحقيق البروتوكول كواجهة مطابقة [في البرنامج].
قد يتم الاستعلام عن بروتوكول لتحديد توافق الواجهة.

(2) يقترح

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

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

(3) التأمل

يمكن أن يكون البروتوكول نوعًا مميزًا من الوحدات النمطية. سوف يخدم صادراتها
كمقارنة أولية وعند تحديد ما إذا كانت بعض الواجهة تتوافق.
يمكن الإعلان عن استخدام أي بروتوكول محدد أنواع ووظائف
@abstract ، @type ، @immutable و @function لدعم التجريد الفطري.

[pao: قم بالتبديل إلى علامات الاقتباس ، على الرغم من ملاحظة أن الحصان قد غادر الحظيرة بالفعل عندما تفعل هذا بعد الحقيقة ...]

(عليك اقتباس @mentions !)

شكرا - إصلاحه

يوم الأربعاء ، 16 ديسمبر ، 2015 الساعة 3:01 صباحًا ، كتب Mauro [email protected] :

(تحتاج إلى اقتباس @ الإشارات!)

-
قم بالرد على هذا البريد الإلكتروني مباشرة أو قم بعرضه على GitHub
https://github.com/JuliaLang/julia/issues/6975#issuecomment -165026727.

آسف ، كان يجب أن أكون أكثر وضوحًا: اقتباس الشفرة باستخدام `` وليس "

إصلاح إصلاح الاقتباس.

شكرا - عفوا عن جهلي السابق

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

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

abstract Iterable
    start(::_)
    done(::_, state)
    next(::_, state)
end

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

الهدف الأساسي لآلية الواجهة الواضحة هو IMHO للحصول على رسائل خطأ أفضل وإجراء اختبار تحقق أفضل.

إذن نوع إعلان مثل:

type Foo <: Iterable
  ...
end

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

يسهل حل جميع هذه الأسئلة عن طريق إنشاء نوع جديد (أو إنشاء صيغة نوع جديدة).

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

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

    1. كما هو مطبق في # 7025 ، استخدم طريقة verify_interface التي يمكن استدعاؤها بعد كل تعريفات الوظائف أو في اختبار الوحدة

    2. لا يمكن التحقق من الواجهة على الإطلاق وتأجيلها إلى رسالة خطأ محسّنة في "MethodError". في الواقع ، يعد هذا احتياطيًا رائعًا لـ 1.

    3. تحقق من جميع الواجهات إما في نهاية وحدة وقت الترجمة أو في نهاية مرحلة تحميل الوحدة. في الوقت الحالي من الممكن أيضًا أن يكون لديك:

function a()
  b()
end

function b()
end

وبالتالي ، لا أعتقد أن تعريفات الوظائف داخل الكتلة ستكون مطلوبة هنا.

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

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

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

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

حسنًا ، افهم هذا. https://github.com/JuliaLang/IterativeSolvers.jl/issues/2 هي مشكلة مماثلة ، حيث يكون الحل أساسًا هو استخدام كتابة البط. إذا كان لدينا شيء يحل هذه المشكلة بأناقة ، فسيكون هذا رائعًا حقًا. لكن هذا شيء يجب دعمه على مستوى الإرسال. إذا فهمت فكرة البروتوكول أعلاه بشكل صحيح ، فيمكن للمرء أن يضع نوعًا مجردًا أو بروتوكولًا كنوع التعليق التوضيحي في الوظيفة. سيكون من الجيد هنا دمج هذين المفهومين بأداة واحدة قوية بما فيه الكفاية.

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

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

JeffreySarnoff +1

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

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

ولكن من المهم أيضًا وجود بروتوكولات مرتبطة ضمنيًا ، كما هو الحال في https://github.com/JuliaLang/julia/issues/6975#issuecomment -168499775

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

abs(x::Unsigned) == x
signbit(x::Unsigned) == false
-abs(x::Signed) <= 0

هذا الاختلاف في السلوك المرئي خارجيًا بين Signed و Unsigned هو ما يجعل هذا التمييز مفيدًا.

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

لا يوجد سبب يمنع استخدام البروتوكولات لتجميع الأنواع ببساطة ، أي دون الحاجة إلى أي طرق محددة (ومن الممكن مع التصميم "الحالي" باستخدام الحيلة: https://github.com/JuliaLang/julia/issues/ 6975 # issuecomment-161056795). (لاحظ أيضًا أن هذا لا يتعارض مع البروتوكولات المعرفة ضمنيًا.)

بالنظر إلى المثال (Un)signed : ماذا أفعل إذا كان لدي نوع Signed ولكن لسبب ما يجب أن يكون أيضًا نوعًا فرعيًا من نوع مجردة آخر؟ هذا لن يكون ممكنا

eschnett : الأنواع المجردة ، في الوقت الحالي ، ليس لها علاقة بتنفيذ أنواعها الفرعية. على الرغم من أن هذا قد تمت مناقشته: # 4935.

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

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

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

على سبيل المثال ، مثل المجموعات التي تحتوي على عناصر مجهولة:

DiscriminatedUnion{Int16, UInt32, Float64}

أو بعناصر مسماة:

discriminated_union MyType
    i::Int16
    u::UInt32
    f::Float64
end

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

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

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

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

بالنظر إلى المثال الموقع (Un): ماذا أفعل إذا كان لدي نوع موقّع ولكن لسبب ما يجب أن يكون أيضًا نوعًا فرعيًا من نوع مجردة آخر؟ هذا لن يكون ممكنا

الوراثة المجردة المتعددة.


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

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

JeffBezanson هل

نعم أعتقد ذلك؛ أعني "الأنواع المرتبطة" بالمعنى التقني لبروتوكول يحدد زوجًا من قيم المفاتيح حيث تكون "القيمة" نوعًا ، بنفس الطريقة التي تحدد بها البروتوكولات الطرق. على سبيل المثال ، "type Foo يتبع بروتوكول الحاوية إذا كان يحتوي على eltype " أو "يتبع type Foo بروتوكول Matrix إذا كانت المعلمة ndims هي 2".

أنواع مجردة اسمية بينما البروتوكولات هيكلية و
أنواع الملخصات نوعية بينما البروتوكولات فعالة و
الأنواع المجردة (ذات الوراثة المتعددة) تنسق أثناء إجراء البروتوكولات

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

@ mason-bially: إذن يجب أن نضيف الميراث المتعدد أيضًا؟ سيؤدي هذا إلى ترك مشكلة أنه لا يمكن إضافة الأنواع الفائقة بعد إنشاء النوع (ما لم يكن ذلك مسموحًا به أيضًا).

JeffBezanson : لا شيء يمنعنا من السماح ببروتوكولات اسمية بحتة.

@ mauro3 لماذا يجب

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

  • حل عام
  • لا تدهور الأداء
  • سهولة الاستخدام (وكذلك سهلة الفهم!)

نظرًا لأن ما تم اقتراحه مبدئيًا في # 6975 يختلف تمامًا عن فكرة البروتوكول التي تمت مناقشتها لاحقًا ، فقد يكون من الجيد الحصول على نوع JEP يصف الشكل الذي يمكن أن تبدو عليه البروتوكولات.

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

لقد بدأت في استخدام هذا كتحقق من وقت التشغيل في DSL ونحن نحدد المكان الذي يجب أن أتأكد فيه من أن النوع المقدم هو مكرر للتواريخ.

وهو يدعم حاليًا الوراثة المتعددة للأنواع الفائقة ، ولا يتم استخدام اسم الحقل الفائق في وقت التشغيل ويمكن أن يكون أي رمز صالح. يمكنك توفير أنواع أخرى لـ _super Tuple.

https://github.com/mdcfrancis/tc.jl/blob/master/test/runtests.jl

فقط أشير هنا إلى أنني قمت بمتابعة مناقشة من JuliaCon حول بناء الجملة المحتمل على السمات على https://github.com/JuliaLang/julia/issues/5#issuecomment -230645040

لدى Guy Steele بعض الأفكار الجيدة عن السمات بلغة إرسال متعددة (Fortress) ، راجع خطابه الرئيسي JuliaCon 2016: https://youtu.be/EZD3Scuv02g .

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

حالة استخدام AD جديدة لمجمع Tensorflow مترجم للبروتوكولات:
https://gist.github.com/rxwei/30ba75ce092ab3b0dce4bde1fc2c9f1d
قد يكون timholy و Keno مهتمين بهذا. يحتوي على محتوى جديد تمامًا

أعتقد أن هذا العرض يستحق الاهتمام عند استكشاف مساحة التصميم لهذه المشكلة.

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

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

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

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

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

_that's the_: التذكرة:

بصرف النظر عن تجنب كسر التغييرات ، هل سيكون التخلص من الأنواع المجردة وإدخال واجهات ضمنية على غرار golang أمرًا ممكنًا في جوليا؟

لا ، لن يحدث ذلك.

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

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

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

  1. تعتبر "الواجهات" رخيصة وخفيفة الوزن ، فقط مجموعة من الوظائف بسلوك محدد لمجموعة من الأنواع (نعم ، الأنواع هي المستوى الصحيح من الدقة - بالنسبة لـ x::T ، يجب أن تكون T كافية لتحديد ما إذا كان x بتنفيذ الواجهة). لذلك إذا كان أحدهم يقوم بتعريف حزمة ذات سلوك قابل للتوسيع ، فمن المنطقي حقًا توثيق الواجهة.

  2. لا تحتاج الواجهات

  3. يتطلب إعادة التوجيه / التركيب واجهات ضمنيًا. "كيفية جعل غلاف يرث جميع أساليب الوالد" هو سؤال يطرأ كثيرًا ، لكنه ليس السؤال الصحيح. الحل العملي هو أن يكون لديك واجهة أساسية وتنفيذ ذلك فقط للغلاف.

  4. الصفات رخيصة ويجب استخدامها بحرية. Base.IndexStyle مثالًا أساسيًا ممتازًا.

قد يستفيد ما يلي من التوضيح لأنني لست متأكدًا من أفضل الممارسات:

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

  2. ما هو أفضل مكان لتوثيق الواجهة في سلسلة docstring؟ أود أن أقول وظيفة الاستعلام أعلاه.

  1. نعم ، الأنواع هي المستوى الصحيح من التفاصيل

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

  1. لا تحتاج الواجهات

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

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

ما هو الهدف من التصنيف الفرعي الاسمي إذا كنت تستخدمه بشكل أساسي كواجهات مجردة؟

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

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

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

مجرد فكرة ، أثناء استخدام MacroTools s forward :

من المزعج أحيانًا إعادة توجيه الكثير من الأساليب

<strong i="9">@forward</strong> Foo.x a b c d ...

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

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

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

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

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

ومع ذلك ، ما أود أن أراه هو

  1. توثيق المزيد والمزيد من الواجهات (في سلسلة docstring) ،

  2. مجموعات الاختبار لاكتشاف الأخطاء الواضحة للواجهات الناضجة للأنواع المحددة حديثًا (على سبيل المثال ، يتم تنفيذ الكثير من T <: AbstractArray eltype(::T) وليس eltype(::Type{T}) .

tpapp أمر منطقي بالنسبة لي الآن ، شكرًا.

تضمين التغريدة السمات ليست أنواعًا رمزية (أليس كذلك؟) ، ومع ذلك ، يمكن استخدامها للإرسال.

نقطتي هي في الأساس النقطة التي قدمها tknopp و @ mauro3 هنا: https://discourse.julialang.org/t/why-does-julia-not-support-multiple-traits/5278/43؟

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

الشيء الذي يبدو أن الناس يريدونه هو تجنب تلك الطبقة من المراوغة ولكن يبدو أنها مجرد راحة وليست ضرورة.

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

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

مجرد نشر هذا هنا يجعله ممتعًا: يبدو أنه تم قبول المفاهيم بالتأكيد وستكون جزءًا من C ++ 20. الاشياء!

https://herbsutter.com/2019/02/23/trip-report-winter-iso-c-standards-meeting-kona/
https://en.cppreference.com/w/cpp/language/constraints

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

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

import Base: Generator
<strong i="6">@require</strong> getindex(AbstractArray, Vararg{Int})
function getindex(container::Generator, index...)
    iterator = container.iter
    if <strong i="7">@works</strong> getindex(iterator, index...)
        container.f(getindex(iterator, index...))
    else
        <strong i="8">@interfaceerror</strong> getindex(iterator, index...)
    end
end
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

sbromberger picture sbromberger  ·  3تعليقات

iamed2 picture iamed2  ·  3تعليقات

omus picture omus  ·  3تعليقات

StefanKarpinski picture StefanKarpinski  ·  3تعليقات

felixrehren picture felixrehren  ·  3تعليقات