Julia: بناء جملة بديل لـ `` map (func، x) ''

تم إنشاؤها على ٢٣ سبتمبر ٢٠١٤  ·  283تعليقات  ·  مصدر: JuliaLang/julia

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

breaking speculative

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

بمجرد أن يدمج عضو في الثلاثي (ستيفان ، جيف ، فيال) # 15032 (والذي أعتقد أنه جاهز للدمج) ، سأغلق هذا وأقدم مشكلة في خريطة الطريق لتوضيح التغييرات المقترحة المتبقية: إصلاح حساب نوع البث ، وإيقاف @vectorize ، حوّل .op إلى سكر البث ، أضف "انصهار البث" على مستوى بناء الجملة ، وأخيراً اندمج مع التعيين الموضعي. ربما لن يصل الاثنان الأخيران إلى 0.5.

ال 283 كومينتر

+1

أو func.(args...) كسكر نحوي لـ

broadcast(func, args...)

لكن ربما أنا الوحيد الذي يفضل ذلك؟
في كلتا الحالتين ، +1.

: -1: إذا كان هناك أي شيء ، أعتقد أن اقتراح ستيفان الآخر لـ f[...] له تشابه جميل مع الفهم.

مثل ihnorton ، لست مغرمًا جدًا بهذه الفكرة. على وجه الخصوص ، لا أحب عدم التناسق لوجود كل من a .+ b و sin.(a) .

ربما لا نحتاج إلى بناء جملة خاص. مع # 1470 ، يمكننا فعل شيء مثل

call(f::Callable,x::AbstractArray) = applicable(f,x) ? apply(f,x) : map(f,x)

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

quinnj يلخص هذا السطر أعظم مخاوفي بشأن السماح بالحمل الزائد للمكالمات. لن أتمكن من النوم لأيام.

لست متأكدًا بعد من أنه ممكن من الناحية التركيبية ، ولكن ماذا عن .sin(x) ؟ هل هذا أقرب إلى a .+ b ؟

أعتقد أن [] أصبح مثقلًا جدًا ولن يعمل لهذا الغرض. على سبيل المثال ، من المحتمل أن نكون قادرين على كتابة Int(x) ، لكن Int[x] يبني مصفوفة وبالتالي لا يمكن أن يعني map .

سأكون على متن الطائرة مع .sin(x) .

سنضطر إلى استعادة بعض بناء الجملة لذلك ، ولكن إذا كان Int(x) هو الإصدار القياسي ، فإن Int[x] معقول عن طريق القياس لإنشاء متجه لنوع العنصر Int . في رأيي ، فإن الفرصة لجعل بناء الجملة أكثر تماسكًا هي في الواقع أحد أكثر الجوانب جاذبية لاقتراح f[v] .

كيف يجعل بناء جملة f[v] لـ map بناء الجملة أكثر تماسكًا؟ أنا لا أفهم. map له "شكل" مختلف عن صيغة مُنشئ المصفوفة الحالية T[...] . ماذا عن Vector{Int}[...] ؟ ألا يعمل هذا؟

لول ، آسف على الخوفJeffBezanson! هاها ، التحميل الزائد للمكالمات أمر مخيف بعض الشيء ، بين الحين والآخر ، أفكر في أنواع تشويش الشفرة التي يمكنك القيام بها في جوليا وباستخدام call ، يمكنك القيام ببعض الأشياء الشائكة.

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

: -1 :. حفظ حرفين مقارنة باستخدام وظائف ذات ترتيب أعلى لا أعتقد أنها تستحق تكلفة سهولة القراءة. هل يمكنك تخيل ملف به .func() / func.() و func() متناثرة في كل مكان؟

يبدو من المحتمل أننا سنزيل بناء الجملة a.(b) على أي حال ، على الأقل.

واو ، تحدث عن إثارة عش النحل! لقد غيرت الاسم ليعكس المناقشة بشكل أفضل.

يمكننا أيضًا إعادة تسمية الوسيطة 2 map إلى zipWith :)

إذا كانت بعض القواعد ضرورية حقًا ، فماذا عن [f <- b] أو تورية أخرى على الفهم _ داخل_ الأقواس؟

( JeffBezanson ، أنت تخشى أن يكتب شخص ما CJOS أو Moose.jl :) ... إذا حصلنا على هذه الميزة ، فقط ضعها في قسم Don't do stupid stuff: I won't optimize that من الدليل)

تشير كتابة Int[...] حاليًا إلى أنك تنشئ مصفوفة من نوع العنصر Int . ولكن إذا كان Int(x) يعني تحويل x إلى Int من خلال تطبيق Int كدالة ، فيمكنك أيضًا اعتبار Int[...] يعني "تطبيق Int لكل شيء في ... "، يا والذي يحدث بالمناسبة لإنتاج قيم من النوع Int . لذا فإن كتابة Int[v] ستكون مساوية لـ [ Int(x) for x in v ] و Int[ f(x) for x in v ] ستعادل [ Int(f(x)) for x in v ] . بالطبع ، فقد فقدت بعضًا من فائدة كتابة Int[ f(x) for x in v ] في المقام الأول - أي أنه يمكننا أن نعرف بشكل ثابت أن نوع العنصر هو Int - ولكن إذا فرضت ذلك Int(x) يجب أن ينتج Int (ليس قيدًا غير معقول) ، ثم يمكننا استرداد هذه الخاصية.

يبدو لي المزيد من الاتجاهية / الجحيم الضمني للقط. ماذا سيفعل Int[x, y] ؟ أو ما هو أسوأ ، Vector{Int}[x] ؟

أنا لا أقول إنها أفضل فكرة على الإطلاق أو حتى أدافع عنها - أنا فقط أشير إلى أنها لا تتعارض تمامًا مع الاستخدام الحالي ، والذي يعد بحد ذاته نوعًا من الاختراق. إذا تمكنا من جعل الاستخدام الحالي جزءًا من نمط أكثر تماسكًا ، فسيكون ذلك بمثابة فوز. لست متأكدًا مما قد يعنيه f[v,w] - الخيارات الواضحة هي [ f(x,y) for x in v, y in w ] أو map(f,v,w) ولكن لا يزال هناك المزيد من الخيارات.

أشعر أن a.(b) نادرًا ما يستخدم. شغّل اختبارًا سريعًا ولا يُستخدم إلا في 54 من ملفات مصدر جوليا التي يبلغ عددها 4000 جوليا فقط: https://gist.github.com/jakebolewski/104458397f2e97a3d57d.

أعتقد أنه يتعارض تمامًا. T[x] له "الشكل" T --> Array{T} ، بينما map له الشكل Array{T} --> Array{S} . هذه إلى حد كبير غير متوافقة.

للقيام بذلك ، أعتقد أنه سيتعين علينا التخلي عن T[x,y,z] كمُنشئ لـ Vector{T} . يمكن اعتبار فهرسة المصفوفة القديمة البسيطة ، A[I] حيث I متجهًا ، على أنها map(i->A[i], I) . يشبه "تطبيق" المصفوفة تطبيق دالة (بالطبع تستخدم matlab نفس الصيغة لها). بهذا المعنى ، فإن بناء الجملة يعمل حقًا ، لكننا سنفقد بناء الجملة المكتوب المتجه في هذه العملية.

أشعر نوعًا ما بأن بناء الجملة هنا يصرف الانتباه عن التغيير الأكثر أهمية: جعل map سريعًا.

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

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

موافق تماما.

أتفق مع JeffBezanson على أن f[x] لا يمكن التوفيق بينها إلى حد كبير مع إنشاءات المصفوفة المكتوبة الحالية Int[x, y] إلخ.

سبب آخر لتفضيل .sin على sin. هو السماح أخيرًا باستخدام مثل Base.(+) للوصول إلى الوظيفة + في Base (مرة واحدة a.(b) تمت إزالة

عندما تحدد وحدة نمطية وظيفتها sin (أو أيا كان) ونريد استخدام هذه الوظيفة على متجه ، فهل نقوم بعمل Module..sin(v) ؟ Module.(.sin(v)) ؟ Module.(.sin)(v) ؟ .Module.sin(v) ؟

لا يبدو أي من هذه الخيارات جيدًا بعد الآن.

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

خذ على سبيل المثال إسهاب map(x->func(x,other,args), container) ، أو سلسلة عملية تصفية لجعلها أسوأ filter(x->func2(x[1]) == val, map(x->func1(x,other,args), container)) .

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

IIRC ، في Haskell ، يمكن كتابة ما سبق filter ((==val) . func2 . fst) $ map (func1 other args) container مع تغيير طفيف في ترتيب الوسيطات إلى func1 .

في علم الدردار ، يتم تعريف .func بواسطة x->x.func وهذا مفيد جدًا ، راجع سجلات elm . يجب أخذ هذا في الاعتبار قبل أخذ بناء الجملة هذا لـ map .

أحب ذلك.

على الرغم من أن الوصول إلى المجال ليس بالأمر المهم في جوليا كما هو الحال في العديد من اللغات.

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

يبدو أن f.(x) هو الحل الأقل إشكالية ، إذا لم يكن لعدم التماثل مع .+ . لكن الاحتفاظ بالارتباط الرمزي لـ . بـ "عملية العناصر" فكرة جيدة IMHO.

إذا كان من الممكن إهمال إنشاء المصفوفة المكتوبة الحالية ، فيمكن ترجمة func[v...] إلى map(func, v...) ، ويمكن بعد ذلك كتابة المصفوفات الصغيرة T[[a1, ..., an]] (بدلاً من T[a1, ..., an] الحالي

أجد أيضًا sin∘v هادئًا طبيعيًا (عندما يُرى مصفوفة v تطبيقًا من الفهارس إلى القيم المضمنة) ، أو ببساطة sin*v أو v*[sin]' ( الذي يتطلب تحديد *(x, f::Callable) ) إلخ.

بالعودة إلى هذه المشكلة بعقل جديد ، أدركت أن f.(x) يمكن اعتباره تركيبًا طبيعيًا تمامًا. بدلاً من قراءتها كـ f. و ( ، يمكنك قراءتها كـ f و .( . يُعد .( مجازًا نسخة من عامل الاتصال للوظيفة ( ، وهو ما يتوافق تمامًا مع .+ والأصدقاء.

فكرة .( أن أكون عامل مكالمات وظيفية تجعلني حزينًا للغاية.

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

بالنسبة لي ، ( ليس جزءًا من دلالات اللغة على الإطلاق: إنه مجرد جزء من بناء الجملة. لذلك لا أريد أن أضطر إلى اختراع طريقة لـ .( و ( لبدء الاختلاف. هل الأول يولد multicall Expr بدلاً من call Expr ؟

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

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

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

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

نحتاج فقط إلى اختيار الحل الأكثر فائدة. تمت مناقشة الخيارات المحتملة هنا: https://github.com/JuliaLang/julia/issues/8389#issuecomment -55953120 (والتعليقات التالية). كما قال JeffBezanson إن السلوك الحالي لـ map معقول. معيار مثير للاهتمام هو أن تكون قادرًا على استبدال @vectorize_2arg .

نقطتي هي أن التعايش بين sin.(x) و x .+ y أمر محرج. أفضل الحصول على .sin(x) -> map(sin, x) و x .+ y -> map(+, x, y) .

يستخدم $ .+ broadcast .

بعض الأفكار الأخرى من منطلق اليأس المطلق:

  1. النقطتان الزائدتان ، sin:x . لا يعمم جيدًا على الحجج المتعددة.
  2. sin.[x] --- هذه البنية متاحة حاليًا بلا معنى.
  3. sin@x - ليس متاحًا ، لكن ربما يكون ممكنًا

أنا حقًا لست مقتنعًا بأننا بحاجة إلى هذا.

وأنا كذلك. أعتقد أن f.(x) هو الخيار الأفضل هنا ، لكني لا أحبه.

ولكن بدون هذا كيف يمكننا تجنب إنشاء جميع أنواع الوظائف الموجهة ، وخاصة أشياء مثل int() ؟ هذا ما دفعني لبدء هذه المناقشة في https://github.com/JuliaLang/julia/issues/8389.

يجب أن نشجع الناس على استخدام map(func, x) . لا يتعلق الأمر بالكتابة ويصبح واضحًا على الفور لأي شخص قادم من لغة أخرى.

وبالطبع تأكد من أنه سريع.

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

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

تستخدم الكثير من اللغات الأخرى map لسنوات.

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

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

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

لنتخيل ماذا يحدث إذا أزلنا دوال الرياضيات الموجهة الشائعة الاستخدام:

  1. أنا شخصياً لا أمانع في كتابة map(exp, x) بدلاً من exp(x) ، على الرغم من أن الأخير أقصر وأنظف قليلاً. ومع ذلك ، يوجد اختلاف كبير في الأداء. تكون الوظيفة الموجهة أسرع بنحو 5 أضعاف من الخريطة الموجودة على جهازي.
  2. عندما تتعامل مع التعبيرات المركبة ، تكون المشكلة أكثر إثارة للاهتمام. فكر في تعبير مركب: exp(0.5 * abs2(x - y)) ، ثم لدينا
# baseline: the shortest way
exp(0.5 * abs2(x - y))    # ... takes 0.03762 sec (for 10^6 elements)

# using map (very cumbersome for compound expressions)
map(exp, 0.5 * map(abs2, x - y))   # ... takes 0.1304 sec (about 3.5x slower)

# using anonymous function (shorter for compound expressions)
map((u, v) -> 0.5 * exp(abs2(u - v)), x, y)   # ... takes 0.2228 sec (even slower, about 6x baseline)

# using array comprehension (we have to deal with two array arguments)

# method 1:  using zip to combine the arguments (readability not bad)
[0.5 * exp(abs2(u - v)) for (u, v) in zip(x, y)]  # ... takes 0.140 sec, comparable to using map

# method 2:  using index, resulting in a slightly longer statement
[0.5 * exp(abs2(x[i] - y[i])) for i = 1:length(x)]  # ... takes 0.016 sec, 2x faster than baseline 

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

-1 لإزالة الإصدارات الموجهة. في الواقع ، تقدم مكتبات مثل VML و Yeppp أداءً أعلى بكثير للإصدارات الموجهة ونحتاج إلى معرفة كيفية الاستفادة منها.

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

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

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

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

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

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

المقارنة بين y = exp(x) و

for i = 1:length(x)
    y[i] = exp(x[i])
end

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

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

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

نحن بالتأكيد لا نريد نسخًا متجهية من كل شيء ، ولكن استخدام الخريطة في كل مرة في vectorize سيكون أمرًا مزعجًا للأسباب التي ذكرتها Dahua للتو.

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

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

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

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

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

يؤدي هذا إلى التسوية map ، والتي أشعر أنها أقرب إلى المثالية ، لكنني ما زلت أتفق مع lindahua على أنها ليست مقتضبة بما فيه الكفاية.

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

أجد كيف تفعل ماثيماتيكا ذلك برمزها المختصر جذابًا حقًا. الترميز المختصر لتطبيق دالة على وسيطة في Mathematica هو @ ، لذلك يمكنك Apply الوظيفة f للمتجه على النحو التالي: f @ vector . الترميز المختصر ذي الصلة لتعيين دالة هو /@ ، لذا يمكنك تعيين f للمتجه على النحو التالي: f /@ vector . هذا له العديد من الخصائص الجذابة. كلتا العقارب القصيرة مقتضبة. تؤكد حقيقة استخدام كلاهما للرمز @ على وجود علاقة بين ما يفعلونه ، لكن الخريطة / لا تزال تجعلها مميزة بصريًا لتوضيحها عندما تقوم بالتخطيط ومتى ليست كذلك. هذا لا يعني أن جوليا يجب أن تنسخ بشكل أعمى تدوينات ماثيماتيكا ، فقط أن التدوين الجيد لرسم الخرائط له قيمة لا تصدق

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

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

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

قد يكون أحد الحلول مكتبة من وظائف الرياضيات المتجهية. ستكون هذه تطبيقات sin ، cos ، وما إلى ذلك المتوفرة لـ LLVM للتضمين. يمكن أن تقوم LLVM بعد ذلك بتوجيه هذه الحلقة ، ونأمل أن تؤدي إلى رمز فعال للغاية. يبدو أن Yeppp لديها نواة الحلقة الصحيحة ، ولكن لا يبدو أنها تعرضها للتضمين.

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

اجمع ذلك مع نقطة eschnett وستحصل على:

  • تعمل وظائف Vectorized فقط إذا كنت تقيد نفسك بالوظائف الموجودة في المكتبة القياسية
  • تعمل وظائف Vectorized فقط إذا كنت تقيد نفسك بأنواع الحاويات في المكتبة القياسية

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

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

هذا ما يدور في ذهني ، مستوحى من تعليقات eschnett :

# The <strong i="11">@vec</strong> macro tags the function that follows as vectorizable
<strong i="12">@vec</strong> abs2(x::Real) = x * x
<strong i="13">@vec</strong> function exp(x::Real) 
   # ... internal implementation ...
end

exp(2.0)  # simply calls the function

x = rand(100);
exp(x)    # maps exp to x, as exp is tagged as vectorizable

exp(abs2(x))  # maps v -> exp(abs2(v)), as this is applying a chain of vectorizable functions

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

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

شكرًا ، @ lindahua : توضيحك يساعد كثيرًا.

هل يختلف @vec عن إعلان دالة @pure ؟

يشير @vec إلى أنه يمكن تعيين الوظيفة بطريقة حكيمة.

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

تعذر إعادة إنشاء خاصية vec sum من pure و associative على + مع معرفة كيفية reduce / foldl / foldr العمل عند إعطاء وظائف pure و associative ؟ من الواضح أن كل هذا افتراض افتراضي ، ولكن إذا كانت جوليا ستدخل في سمات الأنواع ، يمكنني أن أتخيل تحسينًا كبيرًا لحالة الفن للتوجيه من خلال الدخول في سمات للوظائف.

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

فلماذا لا تضيف عامل تشغيل مقتضب map ؟ هنا سأختار $ بشكل تعسفي. وهذا من شأنه أن يجعل مثالlindahua ينطلق من

exp(0.5 * abs2(x - y))

ل

exp $ (0.5 * abs2 $ (x-y))

الآن ، إذا كان لدينا دعم مشابه لـ Haskell فقط لمشغلي infix المعرفة من قبل المستخدم ، فسيكون هذا فقط تغيير سطر واحد ($) = map . :)

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

  • foo. (x) - تشبه بصريًا وصول الأعضاء من النوع القياسي
  • foo [x] - هل أقوم بالوصول إلى العضو x-th في مصفوفة foo أم استدعاء الخريطة هنا؟
  • .foo (x) - لديه مشاكل كما أشار kmsquire

وأشعر أن الحل @vec قريب جدًا من @vectorize الذي نحاول تجنبه في المقام الأول. بالتأكيد ، سيكون من الجيد امتلاك بعض التعليقات التوضيحية ala # 8297 ويمكن أن تساعد مستقبلاً مترجمًا أكثر ذكاءً في التعرف على فرص دمج الدفق هذه وتحسينها وفقًا لذلك. لكني لا أحب فكرة فرضها.

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

(x, y) -> exp(0.5 * abs2(x - y)) $ x, y

أتساءل عما إذا كان من الممكن استعارة الفكرة من Trait.jl الجديد الرائع في سياق تعيين دالة قابلة للتوجيه. بالطبع ، في هذه الحالة ، نحن ننظر إلى _المواقف_ الفردية من Function من النوع الذي يكون متجهًا أم لا ، بدلاً من نوع جوليا الذي له سمة معينة.

الآن ، إذا كان لدينا فقط دعم يشبه Haskell لمشغلي infix المعرفة من قبل المستخدم

6582 # 6929 لا يكفي؟

هناك نقطة في هذه المناقشة حول توجيه التعبيرات بأكملها بأقل عدد ممكن من المصفوفات المؤقتة. المستخدمون الذين يريدون بناء جملة متجه لن يريدوا مجرد متجه exp(x) ؛ يريدون كتابة تعبيرات مثل

y =  √π exp(-x^2) * sin(k*x) + im * log(x-1)

وجعلها موجهة بطريقة سحرية

لن يكون من الضروري وضع علامة على الوظائف على أنها "قابلة للتوجيه". هذه بالأحرى خاصية لكيفية تنفيذ الوظائف ومتاحة لجوليا. إذا تم تنفيذها على سبيل المثال في لغة C ، فيجب تجميعها إلى LLVM bytecode (وليس ملفات الكائن) بحيث يظل بإمكان مُحسِّن LLVM الوصول إليها. كما أن تنفيذها في جوليا سينجح أيضًا.

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

لسوء الحظ ، ستعتمد مثل هذه التطبيقات على الأجهزة ، أي قد يتعين على المرء اختيار خوارزميات مختلفة أو تطبيقات مختلفة اعتمادًا على تعليمات الأجهزة الفعالة. لقد فعلت ذلك في الماضي (https://bitbucket.org/eschnett/vecmathlib/wiki/Home) في C ++ ، ومع جمهور مستهدف مختلف قليلاً (العمليات القائمة على الاستنسل التي يتم توجيهها يدويًا بدلاً من التوجيه التلقائي مترجم).

هنا في Julia ، ستكون الأمور أسهل لأننا (أ) نعلم أن المترجم سيكون LLVM ، و (ب) يمكننا تنفيذ ذلك في Julia بدلاً من C ++ (وحدات الماكرو مقابل القوالب).

هناك شيء آخر يجب مراعاته: إذا تخلى المرء عن أجزاء من معيار IEEE ، فيمكنه تحسين السرعة بشكل كبير. يعرف العديد من المستخدمين أن الأرقام التي لم تتم تسويتها ، على سبيل المثال ، ليست مهمة ، أو أن الإدخال سيكون دائمًا أقل من sqrt(max(Double)) ، إلخ. السؤال هو ما إذا كان يجب تقديم مسارات سريعة لهذه الحالات. أعلم أنني سأكون مهتمًا جدًا بهذا ، ولكن قد يفضل الآخرون النتائج القابلة للتكرار تمامًا بدلاً من ذلك.

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

هل من المخيف جدًا استخدام أقواس كاملة العرض حول وسيطة الوظيفة ~

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

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

يمكن أن تكون فكرة OTOHjohnmyleswhite المتمثلة في استخدام السمات لتوصيل الخصائص الرياضية للوظيفة حلاً رائعًا. (اقتراح lindahua هو ميزة اقترحتها في مكان ما منذ فترة ، ولكن حل استخدام السمات قد يكون أفضل.)

الآن ، إذا كان لدينا فقط دعم يشبه Haskell لمشغلي infix المعرفة من قبل المستخدم

6582 # 6929 لا يكفي؟

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

julia> ($) = map
julia> sin $ (0.5 * (abs2 $ (x-y)))

لا أعرف ما إذا كان هذا هو الخيار الأفضل لـ map لكن استخدام $ مقابل xor يبدو حقًا مضيعة. لا يتم استخدام Bitwise xor كثيرًا. map هو الطريق الأكثر أهمية.

تعتبر النقطة الواردة أعلاه من jiahao نقطة جيدة جدًا: الوظائف الموجهة الفردية مثل exp هي في الواقع نوع من الاختراق للحصول على تعبيرات متجهة مثل exp(-x^2) . إن بناء الجملة الذي يفعل شيئًا مثل @devec سيكون ذا قيمة حقيقية: ستحصل على أداء منقسم بالإضافة إلى عمومية عدم الحاجة إلى تحديد الوظائف بشكل فردي على أنها متجهية.

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

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

العصف الذهني: ماذا عن وضع علامة على الحجج التي تريد رسم خريطة لها حتى تدعم تعيين متعدد الوسائط:

a = split("the quick brown")
b = split("fox deer bear")
c = split("jumped over the lazy")
d = split("dog cat")
e = string(a, " ", b., " ", c, " ", d.) # -> 3x2 Vector{String} of the combinations   
# e[1,1]: """["the","quick", "brown"] fox ["jumped","over","the","lazy"] dog"""

لست متأكدًا مما إذا كان .b أو b. هو الأفضل لإظهار أنك تريد ذلك التعيين. أحب إرجاع نتيجة 3x2 متعددة الأبعاد في هذه الحالة لأنها تمثل شكل أداة ping map .

جلين

هنا https://github.com/eschnett/Vecmathlib.jl عبارة عن ريبو مع عينة
تنفيذ exp ، مكتوب بطريقة يمكن تحسينها بواسطة LLVM.
هذا التنفيذ أسرع بنحو ضعف سرعة exp القياسي
التنفيذ على نظامي. (ربما) لم تصل إلى سرعة Yeppp بعد ،
ربما لأن LLVM لا تقوم بفك حلقة SIMD المعنية مثل
بقوة مثل Yeppp. (لقد قارنت التعليمات المفككة.)

كتابة دالة vectorizable exp ليست سهلة. يبدو استخدامه كالتالي:

function kernel_vexp2{T}(ni::Int, nj::Int, x::Array{T,1}, y::Array{T,1})
    for j in 1:nj
        <strong i="16">@simd</strong> for i in 1:ni
            <strong i="17">@inbounds</strong> y[i] += vexp2(x[i])
        end
    end
end

حيث توجد الحلقة j ووسيطات الدالة فقط من أجل
أغراض قياس الأداء.

هل هناك ماكرو @unroll لـ Julia؟

-ريك

في الأحد ، 2 نوفمبر 2014 الساعة 8:26 مساءً ، كتب Tim Holy [email protected] :

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

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

إريك شنيتر [email protected]
http://www.perimeterinstitute.ca/personal/eschnetter/

الدوال الموجهة الفردية مثل exp هي في الواقع نوع من الاختراق للحصول على تعبيرات متجهية مثل exp(-x^2)

بناء الجملة الأساسي لرفع التعبيرات الكاملة من المجال القياسي سيكون ممتعًا للغاية. التوجيه هو مثال واحد فقط (حيث يكون المجال الهدف هو النواقل) ؛ هناك حالة استخدام أخرى مثيرة للاهتمام وهي الرفع إلى مجال المصفوفة (# 5840) حيث تختلف الدلالات تمامًا. في مجال المصفوفة ، سيكون من المفيد أيضًا استكشاف كيفية عمل الإرسال على التعبيرات المختلفة ، لأنه في الحالة العامة قد ترغب في Schur-Parlett وخوارزميات أخرى أكثر تخصصًا إذا كنت تريد شيئًا أبسط مثل sqrtm . (وباستخدام البنية الذكية ، يمكنك التخلص من وظائف *m بالكامل - expm ، logm ، sqrtm ، ...)

هل هناك ماكرو @unroll لـ Julia؟

باستخدام Base.Cartesian
nexpr 4 d -> (y [i + d] = exp (x [i + d])

(انظر http://docs.julialang.org/en/latest/devdocs/cartesian/ إذا كانت لديك أسئلة.)

jiahao يبدو أن تعميم هذا على وظائف المصفوفة يمثل تحديًا مثيرًا للاهتمام ، لكن معرفتي بذلك قريبة من الصفر. هل لديك أي أفكار حول كيفية عملها؟ كيف يمكن التعبير عن ذلك مع التوجيه؟ كيف سيسمح بناء الجملة بإحداث فرق بين تطبيق exp element-wise على متجه / مصفوفة ، وحساب المصفوفة الأسية؟

timholy : شكرا! لم أفكر في استخدام الديكارتية لإلغاء التسجيل.

لسوء الحظ ، لم يعد الكود الذي تم إنتاجه بواسطة @nexprs (أو عن طريق الفتح اليدوي) متجهًا بعد الآن. (هذا هو LLVM 3.3 ، ربما يكون LLVM 3.5 أفضل.)

رد: فتح ، راجع أيضًا مشاركة toivoh على مستخدمي جوليا . قد يكون من المفيد أيضًا إعطاء # 6271 فرصة.

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

X = randn(10,10)
c = 0.7
lift(x->exp(c*x^2)*sin(x), X)

الذي من شأنه بعد ذلك

  1. تحديد المجالات المصدر والهدف للرفع من X من النوع Matrix{Float64} وبها عناصر (معلمة النوع) Float64 (وبالتالي تحديد Float64 => Matrix{Float64} lift ضمنًا) ، ومن بعد
  2. استدع matrixfunc(x->exp(c*x^2)*sin(x), X) لحساب ما يعادل expm(c*X^2)*sinm(X) ، لكن مع تجنب مضاعفة المصفوفة.

في بعض الكودات الأخرى ، يمكن أن يكون $ X Vector{Int} وسيكون الرفع الضمني من Int إلى Vector{Int} ، ثم lift(x->exp(c*x^2)*sin(x), X) يمكن حينها اتصل بـ map(x->exp(c*x^2)*sin(x), X) .

يمكن للمرء أن يتخيل أيضًا طرقًا أخرى تحدد المجالات المصدر والهدف بشكل صريح ، على سبيل المثال lift(Number=>Matrix, x->exp(c*x^2)*sin(x), X) .

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

eschnett : هل تستخدم نفس معنى التوجيه مثل الآخرين؟ يبدو أنك تتحدث عن SIMD ، وما إلى ذلك ، وهذا ليس ما أفهمه nalimilan .

حق. هناك نوعان من المفاهيم المختلفة للاتجاه هنا. صفقات واحدة
مع الحصول على SIMD لحلقات داخلية ضيقة (توجيه المعالج). الرئيسية
القضية التي تتم مناقشتها هنا هي بناء الجملة / دلالات بطريقة أو بأخرى "تلقائيًا"
القدرة على استدعاء دالة وسيطة واحدة (أو متعددة) في مجموعة.

يوم الثلاثاء 4 نوفمبر 2014 الساعة 7:04 مساءً ، جون مايلز وايت [email protected]
كتب:

eschnett https://github.com/eschnett : هل تستخدم نفس المعنى
الاتجاهية مثل الآخرين؟ يبدو أنك تتحدث عن SIMD ، وما إلى ذلك ، والتي
ليس ما أفهمه nalimilan https://github.com/nalimilan ل
يعني.

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

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

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

هناك مستويان من القضايا هنا: (1) النحو والدلالات ، (2) التنفيذ.

تدور مشكلة التركيب اللغوي والدلالات حول كيفية قيام المستخدم بالتعبير عن نية تعيين حسابات معينة بطريقة عنصر الحكمة / البث لمصفوفات معينة. حاليًا ، تدعم Julia طريقتين: استخدام الوظائف الموجهة والسماح للمستخدمين بكتابة الحلقة بشكل صريح (أحيانًا بمساعدة وحدات الماكرو). لا توجد طريقة مثالية. بينما تسمح الدالات الموجهة للفرد بكتابة تعبيرات موجزة جدًا مثل exp(0.5 * (x - y).^2) ، إلا أنها تواجه مشكلتين: (1) من الصعب رسم خط فيما يتعلق بالوظائف التي يجب أن توفر إصدارًا متجهًا وأيها غير متجه ، مما يؤدي غالبًا إلى في مناقشات لا تنتهي من جانب المطور والارتباك من جانب المستخدم (غالبًا ما يتعين عليك البحث عن المستند لمعرفة ما إذا كانت بعض الوظائف متجهة). (2) يجعل من الصعب دمج الحلقات عبر حدود الوظيفة. في هذه المرحلة وربما عدة أشهر / سنوات قادمة ، ربما لن يتمكن المترجم من أداء مهام معقدة مثل النظر في وظائف متعددة معًا ، وتحديد تدفق البيانات المشتركة ، وإنتاج مسار رمز محسن عبر حدود الوظيفة.

استخدام الدالة map يعالج المشكلة (1) هنا. ومع ذلك ، لا يزال هذا لا يوفر أي مساعدة في حل المشكلة (2) - باستخدام الدوال ، إما دالة متجهية محددة أو دالة عامة map ، دائمًا ما يؤدي إلى إنشاء حد دالة يعيق اندماج الحلقات ، مما يؤدي إلى أمر بالغ الأهمية في الحساب عالي الأداء. يؤدي استخدام وظيفة الخريطة أيضًا إلى الإسهاب ، على سبيل المثال ، يصبح التعبير أعلاه الآن عبارة أطول مثل map(exp, 0.5 * map(abs2, x - y)) . قد تتخيل بشكل معقول أن هذه المشكلة ستتفاقم بتعبيرات أكثر تعقيدًا.

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

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

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

ملخص lindahua جيد IMHO.

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

  • يجب أيضًا تطبيق نمط دمج استدعاءات الوظائف المتداخلة في حلقة واحدة على المشغلين ، بحيث لا يؤدي A .* B .+ C إلى إنشاء مؤقتين ، ولكن واحد فقط للنتيجة.
  • يجب أيضًا التعامل مع مجموعة الوظائف ذات العناصر الحكيمة والتخفيضات ، بحيث يتم تطبيق التخفيض سريعًا بعد حساب قيمة كل عنصر. عادةً ، سيسمح هذا بالتخلص من sumabs2(A) ، واستبداله بترميز قياسي مثل sum(abs$(A)$^2) (أو sum(abs.(A).^2) ).
  • أخيرًا ، يجب دعم أنماط التكرار غير القياسية للمصفوفات غير القياسية ، لذلك بالنسبة للمصفوفات المتفرقة A .* B يحتاج فقط إلى معالجة الإدخالات غير الصفرية ، وإرجاع مصفوفة متفرقة. قد يكون هذا مفيدًا أيضًا إذا كنت تريد تطبيق دالة عنصر الحكمة على Set ، أو Dict ، أو حتى Range .

يمكن أن تعمل النقطتان الأخيرتان بجعل دوال العناصر ترجع نوعًا خاصًا AbstractArray ، دعنا نقول LazyArray ، والتي من شأنها أن تحسب عناصرها بسرعة (على غرار Transpose اكتب من https://github.com/JuliaLang/julia/issues/4774#issuecomment-59422003). ولكن بدلاً من الوصول إلى عناصره بسذاجة من خلال تجاوزها باستخدام الفهارس الخطية من 1 إلى length(A) ، يمكن استخدام بروتوكول المكرر. سيختار المكرر لنوع معين تلقائيًا ما إذا كان التكرار على مستوى الصفوف أو العمود هو الأكثر كفاءة ، اعتمادًا على تخطيط تخزين النوع. وبالنسبة للمصفوفات المتفرقة ، سيسمح بتخطي أي مدخلات (يجب أن يكون للمصفوفات الأصلية والنتيجة بنية مشتركة ، راجع https://github.com/JuliaLang/julia/issues/7010، https: // github. كوم / JuliaLang / جوليا / قضايا / 7157).

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

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

سيدعم هذا النظام بأكمله أيضًا العمليات الموضعية بشكل مباشر تمامًا.

كنت أفكر قليلاً في معالجة بناء الجملة وفكرت في .= لتطبيق عمليات العناصر على المصفوفة.
لذا يجب كتابة مثال nalimilan sum(abs.(A).^2)) في خطوتين:

A = [1,2,3,4]
a .= abs(A)^2
result = sum(a)

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

بالطبع ، لا شيء غير الأداء والألفة يمنع أي شخص من كتابة map((x) -> abs(x)^2, A) الآن كما ذكرنا.

بدلاً من ذلك ، يمكن أن يعمل إحاطة التعبير المراد تعيينه بـ .() .
لا أعرف مدى صعوبة القيام بذلك ولكن .sin(x) و .(x + sin(x)) عندئذٍ يرسم التعبير إما داخل الأقواس أو الوظيفة التي تلي . .
سيسمح هذا بعد ذلك بالتخفيضات مثل مثالnalimilan حيث يمكن كتابة sum(.(abs(A)^2)) في سطر واحد.

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

لقد جربت فكرة LazyArray التي كشفت عنها في تعليقي الأخير: https://gist.github.com/nalimilan/e737bc8b3b10288abdad

لا يحتوي إثبات المفهوم هذا على أي سكر نحوي ، ولكن سيتم ترجمة (a ./ 2).^2 إلى ما هو مكتوب في الجوهر كـ LazyArray(LazyArray(a, /, (2,)), ^, (2,)) . يعمل النظام بشكل جيد ، ولكنه يحتاج إلى مزيد من التحسين ليكون قادرًا على المنافسة عن بعد مع الحلقات فيما يتعلق بالأداء. يبدو أن المشكلة (المتوقعة) هي أن استدعاء الوظيفة في السطر 12 لم يتم تحسينه (تحدث جميع التخصيصات تقريبًا هناك) ، حتى في الإصدار الذي لا يُسمح فيه بالوسيطات الإضافية. أعتقد أنني بحاجة إلى تحديد LazyArray على الوظيفة التي تستدعيها ، لكنني لم أفهم كيف يمكنني القيام بذلك ، ناهيك عن التعامل مع الحجج أيضًا. أيه أفكار؟

أي اقتراحات حول كيفية تحسين أداء LazyArray ؟

nalimilan لقد جربت نهجًا مشابهًا قبل عام ، باستخدام أنواع functor في NumericFuns لتحديد معلمات أنواع التعبير الكسول. لقد جربت مجموعة متنوعة من الحيل ، لكن لم يحالفني الحظ في سد فجوة الأداء.

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

lindahua @inline لا يحدث أي فرق في التوقيت ، وهو أمر منطقي بالنسبة لي لأن getindex(::LazyArray, ...) متخصص لتوقيع LazyArray ، والذي لا يحدد الوظيفة التي يجب يدعى يسمى. سأحتاج إلى شيء مثل LazyArray{T1, N, T2, F} ، مع F الوظيفة التي يجب استدعاؤها ، بحيث عند تجميع getindex تُعرف المكالمة. هل هناك طريقة لفعل ذلك؟

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

قد تفكر في استخدام NumericFuns و F يمكن أن يكون من النوع الممتع.

داهوا

لقد احتجت إلى وظائف حيث أعرف نوع الإرجاع للتوزيع
الحوسبة ، حيث أقوم بإنشاء مراجع للنتيجة قبل النتيجة (و
وبالتالي نوعه) معروف. لقد نفذت شيئًا مشابهًا جدًا بنفسي ، و
ربما ينبغي أن تتحول إلى استخدام ما تسميه "المنفذين". (أنا لا أحب ال
اسم "functor" ، لأنها عادة ما تكون شيئًا آخر <
http://en.wikipedia.org/wiki/Functor> ، لكني أعتقد أن C ++ تسبب في تشويش المياه
هنا.)

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

-ريك

يوم الخميس ، 20 تشرين الثاني (نوفمبر) 2014 الساعة 10:35 صباحًا ، Dahua Lin [email protected]
كتب:

يمكنك التفكير في استخدام NumericFuns ويمكن أن يكون F من النوع الممتع.

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

إريك شنيتر [email protected]
http://www.perimeterinstitute.ca/personal/eschnetter/

lindahua لقد حاولت استخدام المسامير ، والواقع أن الأداء أكثر منطقية:
https://gist.github.com/nalimilan/d345e1c080984ed4c89a

With functions:
# elapsed time: 3.235718017 seconds (1192272000 bytes allocated, 32.20% gc time)

With functors:
# elapsed time: 0.220926698 seconds (80406656 bytes allocated, 26.89% gc time)

Loop:
# elapsed time: 0.07613788 seconds (80187556 bytes allocated, 45.31% gc time) 

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

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

# With sqrt()
julia> test_lazy!(newa, a);
julia> <strong i="8">@time</strong> for i in 1:1000 test_lazy!(newa, a) end
elapsed time: 0.151761874 seconds (232000 bytes allocated)

julia> test_loop_dense!(newa, a);
julia> <strong i="9">@time</strong> for i in 1:1000 test_loop_dense!(newa, a) end
elapsed time: 0.121304952 seconds (0 bytes allocated)

# With exp()
julia> test_lazy!(newa, a);
julia> <strong i="10">@time</strong> for i in 1:1000 test_lazy!(newa, a) end
elapsed time: 0.289050295 seconds (232000 bytes allocated)

julia> test_loop_dense!(newa, a);
julia> <strong i="11">@time</strong> for i in 1:1000 test_loop_dense!(newa, a) end
elapsed time: 0.191016958 seconds (0 bytes allocated)

لذا أود معرفة سبب عدم إجراء التحسينات باستخدام LazyArray . التجميع الذي تم إنشاؤه طويل جدًا للعمليات البسيطة. على سبيل المثال ، لـ x/2 + 3 :

julia> a1 = LazyArray(a, Divide(), (2.0,));

julia> a2 = LazyArray(a1,  Add(), (3.0,));

julia> <strong i="17">@code_native</strong> a2[1]
    .text
Filename: none
Source line: 1
    push    RBP
    mov RBP, RSP
Source line: 1
    mov RAX, QWORD PTR [RDI + 8]
    mov RCX, QWORD PTR [RAX + 8]
    lea RDX, QWORD PTR [RSI - 1]
    cmp RDX, QWORD PTR [RCX + 16]
    jae L64
    mov RCX, QWORD PTR [RCX + 8]
    movsd   XMM0, QWORD PTR [RCX + 8*RSI - 8]
    mov RAX, QWORD PTR [RAX + 24]
    mov RAX, QWORD PTR [RAX + 16]
    divsd   XMM0, QWORD PTR [RAX + 8]
    mov RAX, QWORD PTR [RDI + 24]
    mov RAX, QWORD PTR [RAX + 16]
    addsd   XMM0, QWORD PTR [RAX + 8]
    pop RBP
    ret
L64:    movabs  RAX, jl_bounds_exception
    mov RDI, QWORD PTR [RAX]
    movabs  RAX, jl_throw_with_superfluous_argument
    mov ESI, 1
    call    RAX

على عكس ما يعادله:

julia> fun(x) = x/2.0 + 3.0
fun (generic function with 1 method)

julia> <strong i="21">@code_native</strong> fun(a1[1])
    .text
Filename: none
Source line: 1
    push    RBP
    mov RBP, RSP
    movabs  RAX, 139856006157040
Source line: 1
    mulsd   XMM0, QWORD PTR [RAX]
    movabs  RAX, 139856006157048
    addsd   XMM0, QWORD PTR [RAX]
    pop RBP
    ret

الجزء حتى jae L64 هو فحص لحدود المصفوفة. قد يساعد استخدام @inbounds (إذا كان ذلك مناسبًا).

الجزء أدناه ، حيث يبدأ سطرين متتاليين بـ mov RAX, ... ، هو اتجاه مزدوج ، أي الوصول إلى مؤشر إلى مؤشر (أو مصفوفة من المصفوفات ، أو مؤشر إلى مصفوفة ، إلخ). قد يكون لهذا علاقة بالتمثيل الداخلي لـ LazyArray - ربما استخدام العناصر الثابتة (أو تمثيل العناصر الثابتة بشكل مختلف بواسطة Julia) قد يساعد هنا.

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

أيضًا: ماذا يحدث إذا قمت بفك هذا ليس من REPL ولكن من داخل وظيفة؟

لا يسعنا أيضًا إلا أن نلاحظ أن الإصدار الأول يحمل ملفًا فعليًا
القسمة بينما الثانية حولت x / 2 إلى الضرب.

شكرا على التعليقات.

eschnett LazyArray هو بالفعل ثابت ، وأنا أستخدم @inbounds في الحلقات. بعد تشغيل الجوهر على https://gist.github.com/nalimilan/d345e1c080984ed4c89a ، يمكنك التحقق مما يقدمه هذا في حلقة باستخدام هذا:

function test_lazy!(newa, a)
    a1 = LazyArray(a, Divide(), (2.0,))
    a2 = LazyArray(a1, Add(), (3.0,))
    collect!(newa, a2)
    newa
end
<strong i="11">@code_native</strong> test_lazy!(newa, a); 

لذلك ربما كل ما أحتاجه هو أن أكون قادرًا على إجبار التضمين؟ في محاولاتي ، لا تؤدي إضافة @inline إلى getindex إلى تغيير التوقيت.

toivoh ما الذي يمكن أن يفسر أنه في الحالة الأخيرة لا يتم تبسيط القسمة؟

لقد واصلت تجربة الإصدار المكون من وسيطتين (يسمى LazyArray2 ). تحول لعملية بسيطة مثل x .+ y ، إنه في الواقع أسرع استخدام LazyArray2 من .+ الحالي ، كما أنه قريب جدًا من الحلقات الصريحة (هذه مخصصة لـ 1000 مكالمة ، راجع https://gist.github.com/nalimilan/d345e1c080984ed4c89a):

# With LazyArray2, filling existing array
elapsed time: 0.028212517 seconds (56000 bytes allocated)

# With explicit loop, filling existing array
elapsed time: 0.013500379 seconds (0 bytes allocated)

# With LazyArray2, allocating a new array before filling it
elapsed time: 0.098324278 seconds (80104000 bytes allocated, 74.16% gc time)

# Using .+ (thus allocating a new array)
elapsed time: 0.078337337 seconds (80712000 bytes allocated, 52.46% gc time)

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

يبدو أيضًا تنافسيًا حقًا لتحقيق عمليات مشتركة مثل حساب مجموع الفروق التربيعية على طول أحد أبعاد المصفوفة ، أي sum((x .- y).^2, 1) (انظر مرة أخرى الجوهر):

# With LazyArray2 and LazyArray (no array allocated except the result)
elapsed time: 0.022895754 seconds (1272000 bytes allocated)

# With explicit loop (no array allocated except the result)
elapsed time: 0.020376307 seconds (896000 bytes allocated)

# With element-wise operators (temporary copies allocated)
elapsed time: 0.331359085 seconds (160872000 bytes allocated, 50.20% gc time)

تضمين التغريدة
يبدو أن أسلوبك مع LazyArrays مشابه لطريقة عمل Haskell للانصهار البخاري [1 ، 2]. ربما يمكننا تطبيق الأفكار من تلك المنطقة؟

[1] http://citeseer.ist.psu.edu/viewdoc/summary؟doi=10.1.1.104.7401
[2] http://citeseer.ist.psu.edu/viewdoc/summary؟doi=10.1.1.421.8551

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

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

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

بقدر ما أستطيع أن أقول ، لا تزال الأقواس المتعرجة متاحة في بناء جملة المكالمة. ماذا لو أصبح هذا func{x} ؟ ربما قليلا من الإسراف؟

فيما يتعلق بموضوع التوجيه السريع (بمعنى SIMD) ، هل هناك أي طريقة يمكننا بها محاكاة الطريقة التي يقوم بها Eigen؟

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

1) أضف بناء جملة جديدًا f.(x) أو f$(x) أو أيًا كان من شأنه إنشاء LazyArray calling f() على كل عنصر x .

2) قم بتعميم بناء الجملة هذا باتباع كيفية عمل broadcast حاليًا ، بحيث يؤدي ، على سبيل المثال ، f.(x, y, ...) أو f$(x, y, ...) إلى إنشاء LazyArray ، لكن توسيع الأبعاد المفردة x ، y ، ... لمنحهم حجمًا مشتركًا. هذا بالطبع سيتم القيام به بسرعة من خلال الحسابات على المؤشرات بحيث لا يتم تخصيص المصفوفات الموسعة بالفعل.

3) اجعل .+ ، .- ، .* ، ./ ، .^ ، إلخ ، استخدم LazyArray بدلاً من broadcast .

4) تقديم عامل تعيين جديد .= أو $= والذي من شأنه أن يحول (استدعاء collect ) LazyArray إلى مصفوفة حقيقية (من نوع يعتمد على ذلك عن طريق قواعد الترويج ، ونوع عنصر اعتمادًا على نوع عنصر المدخلات وعلى الوظيفة التي يتم استدعاؤها).

5) ربما استبدل broadcast باستدعاء LazyArray و collect أيون من النتائج في مصفوفة حقيقية.

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

y .= sqrt.(x .+ 2)
y .=  √π exp.(-x .^ 2) .* sin.(k .* x) .+ im * log.(x .- 1)
sum((x .- y).^2, 1)

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

هذا حقا يبدو وكأنه ميزة مهمة. السلوك الحالي لمشغلي العناصر هو فخ للمستخدمين الجدد ، حيث أن التركيب لطيف وقصير ولكن الأداء عادة ما يكون سيئًا للغاية ، ويبدو أنه أسوأ من Matlab. خلال الأسبوع الماضي فقط ، واجهت العديد من سلاسل الرسائل حول مستخدمي جوليا مشكلات في الأداء والتي من شأنها التخلص من مثل هذا التصميم:
https://groups.google.com/d/msg/julia-users/t0KvvESb9fA/6_ZAp2ujLpMJ
https://groups.google.com/d/msg/julia-users/DL8ZsK6vLjw/w19Zf1lVmHMJ
https://groups.google.com/d/msg/julia-users/YGmDUZGOGgo/LmsorgEfXHgJ

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

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

x .|> x->exp(-x ^ 2) * sin(k * x) + im * log(x - 1)

الذي يتطلب وظائف أساسية فقط و map ( .|> ).

هذه مقارنة مثيرة للاهتمام:

y .=  √π exp.(-x .^ 2) .* sin.(k .* x) .+ im * log.(x .- 1)
y =  [√π exp(-x[i]^ 2) .* sin(k * x[i]) .+ im * log(x[i] - 1) for i = 1:length(x)]

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

لا يحافظ الفهم أحادي الأبعاد على الشكل ، ولكن الآن لدينا for i in eachindex(x) يمكن أن يتغير أيضًا.

إحدى مشكلات الفهم هي أنها لا تدعم DataArrays.

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

على شبكة الإنترنت ، ذهبوا بعيدًا بهذه الفكرة: يمكنك تنفيذ أشجار التعبير هذه بالتوازي على وحدات معالجة مركزية متعددة (عن طريق إضافة. AsParallel ()) ، أو يمكنك تشغيلها على مجموعة كبيرة باستخدام DryadLINQ ، أو حتى على http: / /research.microsoft.com/en-us/projects/accelerator/ (ربما لم يتكامل الأخير بشكل كامل مع LINQ ، ولكنه قريب من الروح إذا تذكرته بشكل صحيح) ، أو بالطبع يمكن ترجمته إلى SQL إذا كان كانت البيانات بهذا الشكل وأنت تستخدم فقط العوامل التي يمكن ترجمتها إلى عبارات SQL.

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

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

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

يمكن للمرء أن يتخيل تكييف صيغة الفهم ، شيء مثل y = [sqrt(x + 2) over x] . ولكن كما لاحظ johnmyleswhite يجب أن يدعموا DataArrays ، ولكن أيضًا المصفوفات المتفرقة وأي نوع مصفوفة جديد. إذن هذه مرة أخرى حالة خلط بناء الجملة والميزات.

بشكل أكثر جوهرية ، أعتقد أن سمتين يقدمهما اقتراحي على البدائل هما:
1) دعم التخصيص الموضعي بدون تخصيص باستخدام y[:] = sqrt.(x .+ 2) .
2) دعم التخفيضات بدون تخصيص مثل sum((x .- y).^2, 1) .

هل يمكن توفير حلول أخرى لذلك (بغض النظر عن المسائل النحوية)؟

davidanthoff شكرًا ، بالنظر إليها الآن (أعتقد أنه يمكن صنع LazyArray لدعم الحوسبة المتوازية أيضًا).

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

mbauman نعم ، تشترك المولدات والمصفوفات البطيئة في العديد من الخصائص. فكرة استخدام الأقواس لتجميع المولد / المصفوفة البطيئة ، وعدم إضافتها للحفاظ على الكائن الكسول تبدو رائعة. لذا فيما يتعلق بأمثلةي أعلاه ، سيكون المرء قادرًا على كتابة كل من 1) y[:] = sqrt(x + 2) over x و sum((x - y)^2 over (x, y), 1) (على الرغم من أنني أجد ذلك طبيعيًا حتى بالنسبة للوافدين الجدد ، فلنترك إصدار over لـ جلسة الزفاف مع التركيز على الأساسيات أولاً).

تعجبني فكرة f(x) over x . يمكننا حتى استخدام f(x) for x لتجنب كلمة رئيسية جديدة. في الواقع [f(x) for x=x] يعمل بالفعل. سنحتاج بعد ذلك إلى تكوين فهمات تعادل map ، حتى يتمكنوا من العمل مع غير المصفوفات. سيكون Array هو الخيار الافتراضي فقط.

يجب أن أعترف أنني أتيت أيضًا إلى فكرة over . أحد الاختلافات بين over كخريطة و for في قائمة الاستيعاب هو ما يحدث في حالة التكرارات المتعددة: ينتج عن [f(x, y) for x=x, y=y] مصفوفة. بالنسبة لحالة الخريطة ، ما زلت تريد بشكل عام متجهًا ، أي أن [f(x, y) over x, y] سيكون مساويًا لـ [f(x, y) for (x,y) = zip(x, y)]] . لهذا السبب ، ما زلت أعتقد أن إدخال كلمة رئيسية إضافية over يستحق كل هذا العناء ، لأنه نظرًا لإثارة هذه المشكلة ، فإن map ing عبر نواقل متعددة أمر شائع جدًا ويجب أن يكون مقتضبًا.

مرحبًا ، لقد أقنعت جيف بالنحو! ؛-)

هذا ينتمي بجانب # 4470 ، لذا أضف 0.4 مشروعًا في الوقت الحالي.

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

  • يعمل مع أنواع بيانات مختلفة ، مثل DataArrays ، وليس فقط المصفوفات الأصلية ؛
  • هي أسرع حلقة مكتوبة يدويًا.

قد يكون من الممكن القيام بذلك باستخدام مضمنة ، ولكن كن حذرًا حقًا للتأكد من تضمين الأعمال.

ماذا عن النهج المختلف: استخدام الماكرو حسب نوع البيانات المستنتج. إذا تمكنا من استنتاج أن بنية البيانات هي DataArray ، فإننا نستخدم خريطة الماكرو التي توفرها مكتبة DataArrays. إذا كانت SomeKindOfStream ، فإننا نستخدم مكتبة البث المتوفرة. إذا لم نتمكن من استنتاج النوع ، فإننا نستخدم فقط التنفيذ العام المُرسَل ديناميكيًا.

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

إذا كان ما أكتبه غير واضح ، فأنا أعني أن شيئًا مثل [EXPR for i in collection if COND] يمكن ترجمته إلى eval(collection_mapfilter_macro(:(i), :(EXPR), :(COND))) ، حيث يتم اختيار collection_mapfilter_macro بناءً على نوع المجموعة المستنتج.

لا ، لا نريد القيام بأشياء من هذا القبيل. إذا حددت DataArray map (أو ما يعادله) ، فيجب دائمًا استدعاء تعريفها لـ DataArrays بغض النظر عما يمكن استنتاجه.

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

1) دعم التخصيص الموضعي بدون تخصيص باستخدام y[:] = sqrt.(x .+ 2)
2) دعم التخفيضات بدون تخصيص مثل sum((x .- y).^2, 1)

y = √π exp (-x ^ 2) * sin (k * x) + im * log (x-1)

بالنظر إلى هذه الأمثلة الثلاثة من الآخرين ، أعتقد أنه مع بناء الجملة for ، سينتهي الأمر بشيء مثل هذا:
1) y[:] = [ sqrt(x + 2) for x ])
2) sum([ (x-y)^2 for x,y ], 1)
و
y = [ √π exp(-x^2) * sin(k*x) + im * log(x-1) for x,k ]

أنا أحب هذا كثيرا جدا! حقيقة أنه ينشئ مصفوفة مؤقتة هو أمر واضح تمامًا ولا يزال مقروءًا ومختصرًا.

لكن السؤال البسيط ، هل يمكن أن يكون لدى x[:] = [ ... for x ] بعض السحر لإحداث تحول في المصفوفة دون تخصيص واحدة مؤقتة؟
لست متأكدًا مما إذا كان هذا سيوفر الكثير من الفوائد ولكن يمكنني أن أتخيل أنه سيساعد في المصفوفات الكبيرة.
أستطيع أن أصدق أنه قد يكون غلاية مختلفة تمامًا من الأسماك والتي يجب مناقشتها في مكان آخر.

@ Mike43110 يمكن كتابة $ x[:] = [ ... for x ] x[:] = (... for x) ، يقوم RHS بإنشاء مولد ، والذي سيتم تجميعه عنصرًا تلو الآخر لملء x ، دون تخصيص نسخة. كانت هذه هي الفكرة LazyArray تجربتي أعلاه.

سيكون بناء الجملة [f <- y] رائعًا إذا تم دمجه مع بناء جملة Int[f <- y] لخريطة تعرف نوع مخرجاتها ولا تحتاج إلى الإقحام من f(y[1]) ما ستكون عليه العناصر الأخرى.

على وجه الخصوص ، نظرًا لأن هذا يوفر واجهة بديهية لـ mapslices أيضًا ، فإن [f <- rows(A)] حيث يقوم rows(A) (أو columns(A) أو slices(A, dims) ) بإرجاع Slice لذلك يمكن استخدام الإرسال:

map(f, slice::Slice) = mapslices(f, slice.A, slice.dims)

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

f(x[:,j]) .* g(x[i,:])

من الصعب مطابقة إيجاز ذلك. التفجير من أسلوب الفهم سيء للغاية:

[f(x[m,j])*g(x[i,n]) for m=1:size(x,1), n=1:size(x,2)]

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

[f(x[m,j]) for m=1:size(x,1)] .* [g(x[i,n]) for _=1, n=1:size(x,2)]

ولكن لفترة أطول.

يبدو أن هذا النوع من الأمثلة يجادل بشأن "النقاط" ، حيث يمكن أن ينتج عن ذلك f.(x[:,j]) .* g.(x[i,:]) .

JeffBezanson لست متأكدا ما هي النية من تعليقك. هل اقترح أي شخص التخلص من بناء جملة .* ؟

رقم؛ أركز على f و g هنا. هذا مثال حيث لا يمكنك فقط إضافة over x في نهاية السطر.

حسنًا ، فهمت ، فاتني نهاية التعليق. في الواقع ، نسخة النقاط أجمل في هذه الحالة.

على الرغم من أنه مع طرق عرض المصفوفة ، سيكون هناك AFAICT فعال بشكل معقول وليس بديلاً قبيحًا:
[ f(y) * g(z) for y in x[:,j], z in x[i,:] ]

هل يمكن حل المثال أعلاه عن طريق التداخل فوق الكلمات الرئيسية؟

f(x)*g(y) over x,y

يتم تفسيره على أنه

[f(x)*g(y) for (x,y) = zip(x,y)]

بينما

f(x)*g(y) over x over y

يصبح

[f(x)*g(y) for x=x, y=y]

بعد ذلك ، سيكون المثال المحدد أعلاه شيئًا مثل

f(x[:,n])*g(x[m,:]) over x[:,n] over x[m,:]

تحرير: في الماضي ، هذا ليس موجزًا ​​كما اعتقدت.

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

f(x[:i,n]) * g(x[m,:i]) over i

يعطي ما يعادل f.(x[:,n] .* g.(x[m,:]) . تعني الصيغة الجديدة x[:i,n] أنه يتم تقديم i محليًا كمكرر فوق مؤشرات الحاوية x[:,n] . لا أعرف ما إذا كان هذا ممكنًا للتنفيذ. لكن يبدو (للوهلة الأولى) أنه ليس قبيحًا ولا مرهقًا ، ويعطي التركيب نفسه حدودًا للمكرر ، وهي 1: الطول (x [: ، n]). فيما يتعلق بالكلمات الرئيسية ، يمكن أن تشير كلمة "over" إلى أنه سيتم استخدام النطاق بالكامل ، بينما يمكن استخدام "for" إذا كان المستخدم يرغب في تحديد نطاق فرعي من 1: length (x [:، n]):

f(x[:i,n]) * g(x[m,:i]) for i in 1:length(x[:,n])-1 .

davidagold ، :i يعني بالفعل الرمز i .

آه نعم ، نقطة جيدة. حسنًا ، طالما أن النقاط هي لعبة عادلة فماذا عنها

f(x[.i,n]) * g(x[m,.i]) over i

حيث تشير النقطة إلى أنه يتم تقديم i محليًا كمكرر فوق 1: length (x [:، n). أفترض من حيث الجوهر أن هذا يحول تدوين النقطة من تعديل الوظائف إلى تعديل المصفوفات ، أو بالأحرى مؤشراتهم. سيوفر هذا واحدًا من "زحف النقطة" لاحظ جيف:

[ f(g(e^(x[m,.i]))) * p(e^(f(y[.i,n]))) over i ]

في مقابل

f.(g.(e.^(x[m,:]))) .* p.(e.^(f.(y[:,n])))

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

[ f(g(e^(x[m,.i]))) * p(e^(f(y[.i,n]))) ] ]

تتمثل إحدى الميزات المحتملة لبناء جملة الاستيعاب في أنه يمكن أن يسمح بمجموعة أوسع من أنماط العمليات من حيث العناصر. على سبيل المثال ، إذا فهم المحلل اللغوي أن الفهرسة باستخدام i في x[m, .i] هي طول معياري ضمنيًا (x [m ،:]) ، فيمكن للمرء أن يكتب

[ f(x[.i]) * g(y[.j]) over i, j=-i ]

لمضاعفة عناصر x مقابل عناصر y بالترتيب المعاكس ، أي العنصر الأول x مقابل العنصر الأخير y ، إلخ. يمكن للمرء أن يكتب

[ f(x[.i]) * g(y[.j]) over i, j=i+1 ]

لمضاعفة العنصر i th لـ x في العنصر i+1 th من y (حيث يتم ضرب العنصر الأخير x بواسطة العنصر الأول y نظرًا لفهم الفهرسة في هذا السياق على أنها طول النموذج (x)). وإذا تبدل p::Permutation (1، ...، length (x)) يمكن للمرء أن يكتب

[ f(x[.i]) * g(y[.j]) over i, j=p(i) ]

لمضاعفة العنصر i th لـ x في p(i) th العنصر y .

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

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

unvectorized_sum(a, b, c, d) = a + b + c + d
vectorized_sum = @super_vectorize(unvectorized_sum)

a = [1, 2, 3, 4]
b = [1, 2, 3]
c = [1, 2]
d = 1

A = [1, 2, 3, 4]
B = [1, 2, 3, 1]
C = [1, 2, 1, 2]
D = [1, 1, 1, 1]

vectorized_sum(a, b, c, d) = vectorized_sum(A, B, C, D) = [4, 7, 8, 8]

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

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

سيتم وضع ما إذا كان سيتم استخدام @super_vectorize في يد المستخدم أم لا. سيكون من الممكن أيضًا إعطاء تحذيرات في حالات مختلفة. على سبيل المثال ، في R ،

c(1, 2, 3) + c(1, 2)
[1] 2 4 4
Warning message:
In c(1, 2, 3) + c(1, 2) :
  longer object length is not a multiple of shorter object length

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

تم تضمين كل من @vectorize_1arg و @vectorize_2arg بالفعل في Base ، والخيارات التي يقدمانها للمستخدم تبدو محدودة إلى حد ما.

لكن هذه المشكلة تركز على تصميم نظام لإزالة @vectorize_1arg و @vectorize_2arg من Base. هدفنا هو إزالة الدوال الموجهة من اللغة واستبدالها بتجريد أفضل.

على سبيل المثال ، يمكن كتابة إعادة التدوير كـ

[ A[i] + B[mod1(i,length(B))] for i in eachindex(A) ]

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

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

سيكون f(x[:,j]) .* g(x[i,:]) حينئذٍ f(x[a:,j]) * g(x[i,b:]) for a, b وهو ليس أسوأ كثيرًا.

بالرغم من ذلك ، قد تكون النقطتان المتعددة غريبة بعض الشيء.

f(x[:,:,j]) .* g(x[i,:,:]) -> f(x[a:,a:,j]) * g(x[i,b:,b:]) for a, b كان فكرتي المبدئية في هذا الشأن.

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

using DataFrames

a = [1, 2, 3]
b = 1
c = [1 2]
d = <strong i="6">@data</strong> [NA, 2, 3]

# coerce an array to a certain size using recycling
coerce_to_size = function(argument, dimension_extents...)

  # number of repmats needed, initialized to 1
  dimension_ratios = [dimension_extents...]

  for dimension in 1:ndims(argument)

    dimension_ratios[dimension] = 
      ceil(dimension_extents[dimension] / size(argument, dimension))
  end

  # repmat array to at least desired size
  if typeof(argument) <: AbstractArray
    rep_to_size = repmat(argument, dimension_ratios...)
  else
    rep_to_size = 
      fill(argument, dimension_ratios...)
  end

  # cut down array to exactly desired size
  dimension_ranges = [1:i for i in dimension_extents]
  dimension_ranges = tuple(dimension_ranges...)

  rep_to_size = getindex(rep_to_size, dimension_ranges...)  

end

recycle = function(argument_list...)

  # largest dimension in arguments
  max_dimension = maximum([ndims(i) for i in argument_list])
  # initialize dimension extents to 1
  dimension_extents = [1 for i in 1:max_dimension]

  # loop through argument and dimension
  for argument_index in 1:length(argument_list)
    for dimension in 1:ndims(argument_list[argument_index])
      # find the largest size for each dimension
      dimension_extents[dimension] = maximum([
        size(argument_list[argument_index], dimension),
        dimension_extents[dimension]
      ])
    end
  end

  expand_arguments = 
    [coerce_to_size(argument, dimension_extents...) 
     for argument in argument_list]
end

recycle(a, b, c, d)

mapply = function(FUN, argument_list...)
  argument_list = recycle(argument_list...)
  FUN(argument_list...)
end

mapply(+, a, b, c, d)

من الواضح أن هذا ليس الرمز الأكثر أناقة أو سرعة (أنا مهاجر حديث من فئة R). لست متأكدًا من كيفية الانتقال من هنا إلى vectorize الماكرو.

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

special_array = [1 2; 3 5]
special_array.dims = (10, 10, 10, 10)
special_array[4, 1, 9, 7] = 3

تحرير 4: الأشياء التي أتساءل عنها موجودة لأنه كان من الصعب كتابتها: تعميم أبعاد n لـ hcat و vcat؟ طريقة لملء مصفوفة ذات أبعاد n (مطابقة لحجم مصفوفة معينة) بقوائم أو مجموعات من مؤشرات كل موضع معين؟ N- الأبعاد تعميم repmat؟

[pao: تمييز بناء الجملة]

أنت لا تريد حقًا تحديد الوظائف باستخدام بناء جملة foo = function(x,y,z) ... end في Julia ، على الرغم من أنها تعمل. يؤدي ذلك إلى إنشاء ارتباط غير ثابت للاسم بوظيفة مجهولة. في جوليا ، القاعدة هي استخدام الوظائف العامة وتكون ارتباطات الوظائف ثابتة تلقائيًا. وإلا ستحصل على أداء رهيب.

لا أرى سبب ضرورة repmat هنا. المصفوفات المملوءة بفهرس كل موضع هي أيضًا علامة تحذير: لا ينبغي أن يكون من الضروري استخدام جزء كبير من الذاكرة لتمثيل القليل من المعلومات. أعتقد أن مثل هذه التقنيات مفيدة حقًا فقط في اللغات التي يحتاج فيها كل شيء إلى "توجيه". يبدو لي أن النهج الصحيح هو مجرد تشغيل حلقة حيث يتم تحويل بعض الفهارس ، كما هو الحال في https://github.com/JuliaLang/julia/issues/8450#issuecomment -111898906.

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

function non_zero_mod(big::Number, little::Number)
  result = big % little
  result == 0 ? little : result
end

function mod_select(array, index...)
  # just return singletons
  if !(typeof(array) <: AbstractArray) return array end
  # find a new index with moded values
  transformed_index = 
      [non_zero_mod( index[i], size(array, i) )
       for i in 1:ndims(array)]
  # return value at moded index
  array[transformed_index...]
end

function mod_value_list(argument_list, index...)
  [mod_select(argument, index...) for argument in argument_list]
end

mapply = function(FUN, argument_list...)

  # largest dimension in arguments
  max_dimension = maximum([ndims(i) for i in argument_list])
  # initialize dimension extents to 1
  dimension_extents = [1 for i in 1:max_dimension]

  # loop through argument and dimension
  for argument_index in 1:length(argument_list)
    for dimension in 1:ndims(argument_list[argument_index])
      # find the largest size for each dimension
      dimension_extents[dimension] = maximum([
        size(argument_list[argument_index], dimension),
        dimension_extents[dimension]
      ])
    end
  end

  # more needed here
  # apply function over arguments using mod_value_list on arguments at each position
end

في الحديث ، ذكر JeffBezanson الصيغة sin(x) over x ، فلماذا لا يوجد شيء مثل:
sin(over x) ؟ (أو استخدم بعض الأحرف بدلاً من استخدام over ككلمة رئيسية)

بمجرد حل هذه المشكلة ، يمكننا أيضًا حل المشكلة رقم 11872

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

كما أن لها ميزة "التوافق" مع بنية فهم المصفوفة الموجودة:

result = [g(f(.i), h(.j)) over i, j]

ضد.

result = [g(f(_i), h(_j)) for _i in eachindex(i), _j in eachindex(j)]

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

over ، range ، و window لديهم بعض الأعمال الفنية السابقة في مساحة OLAP كمعدلات للتكرار ، وهذا يبدو متسقًا.

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

ربما يكون $ متسقًا ، فقم بتدريس قيم التكرار i ، j في التعبير؟

result = [g(f($i), h($j)) over i, j]

من أجل التوجيه التلقائي للتعبير ، لا يمكننا taint أحد المتجهات في التعبير وجعل نظام الكتابة يرفع التعبير في مساحة المتجه؟

أقوم بما يشبه عمليات السلاسل الزمنية حيث يسمح لي تعبير جوليا بالفعل بالكتابة

ts_a = GetTS( ... )
ts_b = GetTS( ... ) 
factors = [ 1,  2, 3 ]

ts_x = ts_a * 2 + sin( ts_a * factors ) + ts_b 

والتي عندما يتم ملاحظتها تنتج سلسلة زمنية من المتجهات.

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

في الأساس ، أود أن أكون قادرًا على تحديد شيء مثل ما يلي ...

abstract TS{K}
function {F}{K}( x::TS{K}, y::TS{K} ) = tsjoin( F, x, y ) 
# tsjoin is a time series iteration operator

ومن ثم تكون قادرًا على التخصص في عمليات محددة

function mean{K}(x::TS{K}) = ... # my hand rolled form

مرحبًا JeffBezanson ،

إذا فهمت بشكل صحيح ، أود أن أقترح حلاً لتعليقك JuliaCon 2015 فيما يتعلق بالتعليق الوارد أعلاه:
"[...] وإخبار كتّاب المكتبة بوضع vectorize على جميع الوظائف المناسبة أمر سخيف ؛ يجب أن تكون قادرًا فقط على كتابة دالة ، وإذا أراد شخص ما حسابها لكل عنصر يستخدمه في الخريطة."
(لكنني لن أتطرق إلى القضية الأساسية الأخرى "[..] لا يوجد سبب مقنع حقًا لماذا يجب تعيين الخطيئة ، exp وما إلى ذلك بشكل ضمني على المصفوفات.")

في Julia v0.40 ، تمكنت من الحصول على حل أفضل إلى حد ما (في رأيي) من vectrorize :

abstract Vectorizable{Fn}
#Could easily have added extra argument to Vectorizable, but want to show inheritance case:
abstract Vectorizable2Arg{Fn} <: Vectorizable{Fn}

call{F}(::Type{Vectorizable2Arg{F}}, x1, x2) = eval(:($F($x1,$x2)))
function call{F,T1,T2}(fn::Type{Vectorizable2Arg{F}}, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

#Function in need of vectorizing:
function _myadd(x::Number, y::Number)
    return x+y+1
end

#"Register" the function as a Vectorizable 2-argument (alternative to @vectorize):
typealias myadd Vectorizable2Arg{:_myadd}

<strong i="13">@show</strong> myadd(5,6)
<strong i="14">@show</strong> myadd(collect(1:10),collect(21:30.0)) #Type stable!

هذا معقول إلى حد ما ، لكنه يشبه إلى حد ما حل vectorize . لكي تكون Vectorization أنيقة ، أقترح أن تدعم Julia ما يلي:

abstract Vectorizable <: Function
abstract Vectorizable2Arg <: Vectorizable

function call{T1,T2}(fn::Vectorizable2Arg, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

#Note: by default, functions would normally be <: Function:
function myadd(x::Number, y::Number) <: Vectorizable2Arg
    return x+y+1
end

هذا هو! إن وجود دالة موروثة من دالة Vectorizable سيجعلها قابلة للتوجيه.

آمل أن يكون هذا على غرار ما كنت تبحث عنه.

يعتبر،

ماجستير

في حالة عدم وجود وراثة متعددة ، كيف ترث الوظيفة من Vectorizable ومن شيء آخر؟ وكيف تربط معلومات الوراثة لطرق معينة بمعلومات الوراثة لوظيفة عامة؟

@ ma-laforge يمكنك فعل ذلك بالفعل --- حدد نوعًا myadd <: Vectorizable2Arg ، ثم قم بتنفيذ call مقابل myadd على Number .

شكرا لذلكJeffBezanson!

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

abstract Vectorizable
#Could easily have parameterized Vectorizable, but want to show inheritance case:
abstract Vectorizable2Arg <: Vectorizable

function call{T1,T2}(fn::Vectorizable2Arg, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

#SECTION F: Function in need of vectorizing:
immutable MyAddType <: Vectorizable2Arg; end
const myadd = MyAddType()
function call(::MyAddType, x::Number, y::Number)
    return x+y+1
end

<strong i="7">@show</strong> myadd(5,6)
<strong i="8">@show</strong> myadd(collect(1:10),collect(21:30.0)) #Type stable

الآن ، الشيء الوحيد المفقود هو طريقة لـ "نوع فرعي" لأي دالة ، بحيث يمكن استبدال القسم F بأكمله بالصيغة الأكثر أناقة:

function myadd(x::Number, y::Number) <: Vectorizable2Arg
    return x+y+1
end

ملاحظة: لقد صنعت النوع "MyAddType" ، واسم الوظيفة في كائن مفرد "myadd" لأنني وجدت بناء الجملة الناتج أجمل مما لو كان الشخص يستخدم Type{Vectorizable2Arg} في توقيع المكالمة:

function call{T1,T2}(fn::Type{Vectorizable2Arg}, v1::Vector{T1}, v2::Vector{T2})

للأسف ، من خلال إجابتك ، يبدو أن هذا لن يكون حلاً مناسبًا لـ "سخافة" الماكرو vectorize .

يعتبر،

ماجستير

johnmyleswhite :

أود الرد على تعليقك ، لكن لا أعتقد أنني أفهم. يمكنك توضيح؟

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

بعد قولي هذا: اقتراحي هو أن الوظائف المحددة داخل Base يجب أن تستخدم Base.Vectorizable <: Function (أو شيء مشابه) من أجل إنشاء خوارزميات متجهة تلقائيًا.

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

myfunction(x::MyType, y::MyType) <: Base.Vectorizable

بالطبع ، سيتعين عليهم تقديم نسختهم الخاصة من promote_type(::Type{MyType},::Type{MyType}) - إذا لم يكن الخيار الافتراضي هو إرجاع MyType بالفعل.

إذا كانت خوارزمية التوجيه الافتراضية غير كافية ، فلا يوجد ما يمنع المستخدم من تنفيذ الهرمية الخاصة به:

MyVectorizable{nargs} <: Function
call(fn::MyVectorizable{2}, x, y) = ...

myfunction(x::MyType, y:MyType) <: MyVectorizable{2}

ماجستير

@ ma-laforge ، آسف لكونك غير واضح. ما يقلقني هو أن أي تسلسل هرمي سيفتقر دائمًا إلى المعلومات المهمة لأن جوليا لديها وراثة واحدة ، مما يتطلب منك الالتزام بنوع والد واحد لكل وظيفة. إذا كنت تستخدم شيئًا مثل myfunction(x::MyType, y::MyType) <: Base.Vectorizable ، فلن تستفيد وظيفتك من قيام شخص آخر بتعريف مفهوم مثل Base.NullableLiftable الذي يولد تلقائيًا وظائف العناصر الفارغة.

يبدو أن هذا لن يكون مشكلة في السمات (راجع https://github.com/JuliaLang/julia/pull/13222). يرتبط أيضًا بالاحتمال الجديد للإعلان عن الأساليب على أنها نقية (https://github.com/JuliaLang/julia/pull/13555) ، مما قد يعني تلقائيًا أن هذه الطريقة قابلة للتوجيه (على الأقل بالنسبة لطرق الوسيطة المفردة).

johnmyleswhite ،

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

لا أعرف الكثير عن NullableLiftables (لا يبدو أن لدي ذلك في روايتي من Julia). ومع ذلك ، بافتراض أنه يرث من Base.Function (وهو أمر غير ممكن أيضًا في إصداري من Julia):

NullableLiftable <: Function

يمكن لوحدتك بعد ذلك تنفيذ نوع فرعي _جديد_ قابل للتوجيه (مرة واحدة فقط):

abstract VectorizableNullableLiftable <: NullableLiftable

function call{T1,T2}(fn::VectorizableNullableLiftable, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

لذلك ، من الآن فصاعدًا ، أي شخص يقوم بتعريف دالة <: VectorizableNullableLiftable سوف يتم تطبيق رمز التوجيه الخاص بك تلقائيًا!

function mycooladdon(scalar1, scalar2) <: VectorizableNullableLiftable
...

أنا أفهم أن وجود أكثر من نوع Vectorizable لا يزال يمثل نوعًا ما من الألم (وقليلًا من الأناقة) ... ولكن على الأقل سيزيل أحد التكرارات المزعجة (1) في Julia (الاضطرار إلى التسجيل _كل_ المضافة حديثًا) تعمل باستدعاءvectorize_Xarg).

(1) هذا على افتراض أن جوليا تدعم التوريث على الوظائف (على سبيل المثال: myfunction(...)<: Vectorizable ) - وهو ما لا يبدو عليه ، في الإصدار 4.0.0. الحل الذي عملت به في Julia 0.4.0 هو مجرد اختراق ... لا يزال يتعين عليك تسجيل وظيفتك ... ليس أفضل بكثير من استدعاءvectorize_Xarg

ماجستير

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

راجع للشغل ، مع التغيير الذي أعمل عليه في فرع jb / function ، ستتمكن من القيام بـ function f(x) <: T (على الرغم من ذلك ، بوضوح ، فقط للتعريف الأول f ).

حسنًا ، أعتقد أنني أفهم بشكل أفضل ما تبحث عنه ... وهذا ليس ما اقترحته. أعتقد أن هذا قد يكون جزءًا من المشكلات التي واجهتهاjohnmyleswhite مع اقتراحاتي أيضًا ...

... ولكن إذا فهمت الآن ماهية المشكلة ، فسيظهر الحل أبسط بالنسبة لي:

function call{T1,T2}(fn::Function, v1::Vector{T1}, v2::Vector{T2})
    RT = promote_type(T1,T2) #For type stability!
    return RT[fn(v1[i],v2[i]) for i in 1:length(v1)]
end

myadd(x::Number, y::Number) = x+y+1

نظرًا لأن myadd من النوع Function ، يجب أن يتم حصره بواسطة الوظيفة call ... وهي تقوم بذلك:

call(myadd,collect(1:10),collect(21:30.0)) #No problem

لكن call لا يرسل الوظائف تلقائيًا ، لسبب ما (لست متأكدًا من السبب):

myadd(collect(1:10),collect(21:30.0)) #Hmm... Julia v0.4.0 does not dispatch this to call...

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

شيء غريب لاحظته: تقوم جوليا بالفعل بتوجيه الوظائف تلقائيًا إذا لم يتم كتابتها:

myadd(x,y) = x+y+1 #This gets vectorized automatically, for some reason

الطاقة المتجددة: راجع للشغل ...:
بارد! أتساءل ما الأشياء الرائعة التي سأتمكن من القيام بها من خلال تصنيف الوظائف الفرعي :).

تقوم جوليا بالفعل بتوجيه الوظائف تلقائيًا إذا لم يتم كتابتها

يبدو بهذه الطريقة لأن عامل التشغيل + المستخدم داخل الدالة متجه.

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

أشارككم تحفظاتكم. لا يمكنك أن يكون لديك تعريف منطقي يقول "إليك كيفية استدعاء أي وظيفة" ، لأن كل وظيفة في حد ذاتها تقول ما يجب القيام به عندما يتم استدعاؤها --- هذه هي الوظيفة!

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

جوليا> ($) = الخريطة
جوليا> sin $ (0.5 * (abs2 $ (xy)))

binarybana ماذا عن / \mapsto ؟

julia> x, y = rand(3), rand(3);

julia> ↦ = map    # \mapsto<TAB>
map (generic function with 39 methods)

julia> sin ↦ (0.5 * (abs2 ↦ (x-y)))
3-element Array{Float64,1}:
 0.271196
 0.0927406
 0.0632608

هناك أيضا:

FWIW ، أفترض في البداية على الأقل أن \mapsto كان بناء جملة بديل لللامدا ، حيث أنه شائع الاستخدام في الرياضيات ، وبشكل أساسي (في تجسد ASCII ، -> ) في جوليا أيضًا . أعتقد أن هذا سيكون محيرًا إلى حد ما.

بالحديث عن الرياضيات ... في نظرية النموذج ، رأيت map يتم التعبير عنه من خلال تطبيق دالة على tuple بدون أقواس. بمعنى ، إذا كان \bar{a}=(a_1, \dots, a_n) ، فإن f(\bar{a}) هو f(a_1, \dots, a_n) (أي بشكل أساسي apply ) و f\bar{a} هو (f(a_1), \dots, f(a_n)) (على سبيل المثال ، map ). بناء جملة مفيد لتعريف التشابهات ، وما إلى ذلك ، ولكن ليس كل ما يمكن نقله بسهولة إلى لغة برمجة: -}

ماذا عن أي من البدائل الأخرى مثل \Mapsto ، هل تخلط بينه وبين => (زوج)؟ أعتقد أن كلا الرمزين يمكن تمييزهما جنبًا إلى جنب:

  • ->

هناك الكثير من الرموز التي تبدو متشابهة ، فما سبب وجود الكثير منها إذا استخدمنا فقط الرموز التي تبدو مختلفة تمامًا أو هي ASCII خالصًا؟

أعتقد أن هذا سيكون محيرًا إلى حد ما.

أعتقد أن التوثيق والخبرة يحل هذا ، هل توافق؟

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

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

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

@ Ismael-VC المشكلة في اقتراحك هي أنك تحتاج إلى استدعاء map مرتين ، بينما الحل المثالي لن يتضمن أي مصفوفات مؤقتة ويقلل إلى map((a, b) -> sin(0.5 * abs2(a-b)), x, y) . بالإضافة إلى ذلك ، لا يعد تكرار مرتين أمرًا رائعًا سواء من الناحية المرئية أو للكتابة (حيث سيكون من الجيد الحصول على مكافئ ASCII).

قد يكره مستخدمو R هذه الفكرة ، ولكن إذا انتقلنا نحو إهمال تحليل الحالة الخاصة الحالية للماكرو ~ (ستحتاج الحزم مثل GLM و DataFrames إلى التغيير إلى تحليل الماكرو الخاص بصيغتهم DSL ، المرجع https: / /github.com/JuliaStats/GLM.jl/issues/116) ، من شأنه تحرير السلعة النادرة لمشغل infix ascii.

يمكن تعريف a ~ b في الأساس على أنه map(a, b) ، وربما يمكن تعريف a .~ b على أنه broadcast(a, b) ؟ إذا تم تحليلها كمشغل infix تقليدي ، فإن ماكرو DSL مثل محاكاة واجهة صيغة R سيكون مجانيًا لتنفيذ تفسيرهم الخاص للمشغل داخل وحدات الماكرو ، كما يفعل JuMP مع <= و == .

ربما لا يكون هذا هو أجمل اقتراح ، ولكن لا توجد اختصارات في Mathematica إذا أفرطت في استخدامها ... المفضل لدي هو .#&/@

: +1: لغلاف أقل خصوصية ومزيد من العمومية والاتساق ، تبدو المعاني التي تقترحها لـ ~ و .~ رائعة بالنسبة لي.

+1 توني.

tkelman لكي نكون واضحين ، كيف تكتب على سبيل المثال sin(0.5 * abs2(a-b)) بطريقة متجهية بالكامل؟

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

لذلك لن يؤدي ذلك إلى حل هذه المشكلة حقًا. : - /

حتى الآن ، يعتبر بناء الجملة sin(0.5 * abs2(a-b)) over (a, b) (أو متغير ، ربما باستخدام عامل infix) هو الأكثر جاذبية.

عنوان هذه المشكلة هو "بناء جملة بديل لـ map(func, x) ". لا يؤدي استخدام عامل تشغيل infix إلى حل اندماج الخريطة / الحلقة للتخلص من المؤقتات ، لكنني أعتقد أن هذا قد يكون مشكلة أوسع ، ومتصلة ، ولكنها منفصلة تقنيًا عن البنية.

نعم ، أتفق مع tkelman ، النقطة المهمة هي أن يكون لديك بناء جملة بديل لـ map ، ولهذا السبب اقترحت استخدام \mapsto ، . ما يذكره nalimilan يبدو أنه أوسع وأكثر ملاءمة لقضية أخرى IMHO ، How to fully vecotrize expressions ؟

<rambling>
هذه القضية مستمرة منذ أكثر من عام الآن (ويمكن أن تستمر إلى أجل غير مسمى مثل العديد من القضايا الأخرى الآن)! ولكن يمكن أن يكون لدينا Alternative syntax for map(func, x) الآن . من بين 450 مساهمًا في جوليان ، تمكن 41 فقط من العثور على هذه المشكلة و / أو على استعداد لمشاركة الرأي (هذا كثير بالنسبة لمشكلة جيثب ولكن من الواضح أنه ليس كافيًا في هذه الحالة) ، بشكل عام لا توجد اقتراحات مختلفة كثيرًا (هذه ليست مجرد اختلافات بسيطة لنفس المفهوم).

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

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

يغطي map(func, x) أيضًا أشياء مثل map(v -> sin(0.5 * abs2(v)), x) ، وهذا ما ناقشه هذا الموضوع. دعونا لا ننقل هذا إلى موضوع آخر ، لأنه سيجعل من الصعب تذكر جميع المقترحات التي تمت مناقشتها أعلاه.

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

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

@ Ismael-VC هذا جيد ، لا تتردد في إجراء استطلاع. في الحقيقة لقد أجريت بعض استطلاعات الرأي حول رسومات الشعار المبتكرة حول قضايا في الماضي (على سبيل المثال http://doodle.com/poll/s8734pcue8yxv6t4). في تجربتي ، يصوت نفس الأشخاص أو أقل في استطلاعات الرأي مقارنة بالمناقشة في مواضيع. يكون منطقيًا لقضايا محددة للغاية ، سطحية / نحوية في كثير من الأحيان. ولكن كيف سيولد الاستطلاع أفكارًا جديدة في حين أن كل ما يمكنك فعله هو الاختيار من الخيارات الحالية؟

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

لقد حاولت البحث عن تدوين رياضي موجود لهذا ، لكنك تميل إلى رؤية تعليقات تفيد بأن العملية غير مهمة جدًا بحيث لا يكون لها رمز! في حالة الدوال التعسفية في سياق رياضي يمكنني تقريبًا تصديق ذلك. ومع ذلك ، يبدو أن أقرب شيء هو تدوين منتج Hadamard ، والذي يحتوي على بعض التعميمات: https://en.wikipedia.org/wiki/Hadamard_product_ (المصفوفات) #Analogous_Operations

هذا يترك لنا sin∘x كما اقترح rfourquet . لا يبدو مفيدًا للغاية ، لأنه يتطلب unicode وغير معروف على نطاق واسع على أي حال.

nalimilan ، أعتقد أنك ستفعل sin(0.5 * abs2(a-b)) ~ (a,b) والتي ستترجم إلى map((a,b)->sin(0.5 * abs2(a-b)), (a,b)) . لست متأكدًا مما إذا كان هذا صحيحًا تمامًا ، لكنني أعتقد أنه سينجح.

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

quinnj نعم ، هذا هو أساسًا بناء الجملة over المقترح أعلاه ، باستثناء عامل التشغيل.

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

johnmyleswhite يوافق ، يبدأ في الظهور مثل DSL المعروف أيضًا باسم Linq

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

julia> (|>)(x::Function, y...) = map(x, y... )
|> (generic function with 8 methods)

julia> (|>)(x::Function, y::Function) = (z...)->x(y(z...))
|> (generic function with 8 methods)

julia> sin |> cos |> [ 1,2,3 ]
3-element Array{Float64,1}:
  0.514395
 -0.404239
 -0.836022

julia> x,y = rand(3), rand(3)
([0.8883630054185454,0.32542923024720194,0.6022157767415313],    [0.35274912207468145,0.2331784754319688,0.9262490059844113])

julia> sin |> ( 0.5 *( abs( x - y ) ) )
3-element Array{Float64,1}:
 0.264617
 0.046109
 0.161309

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

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

JeffBezanson يكمن جمال استخدام \circ في أنك إذا فسرت عائلة مفهرسة كدالة من مجموعة الفهرس (وهي "التنفيذ" الرياضي القياسي) ، فعندئذٍ تعيين _is_ ببساطة تكوين. إذن ، (sin ∘ x)(i)=sin(x(i)) ، أو بالأحرى sin(x[i]) .

استخدام الأنبوب ، كما ذكر mdcfrancis ، سيكون في الأساس مجرد تكوين "ترتيب مخطط" ، والذي يتم غالبًا باستخدام فاصلة منقوطة (ربما دهنية) في الرياضيات (أو خاصة تطبيقات CS لنظرية الفئة) - ولكن لدينا بالفعل الأنبوب عامل بالطبع.

إذا لم يكن أي من عوامل التكوين هذه على ما يرام ، فيمكن للمرء استخدام البعض الآخر. على سبيل المثال ، يستخدم بعض المؤلفين على الأقل \cdot المتواضع لتكوين السهم / التشكل المجرد ، حيث إنه في الأساس "مضاعفة" مجموعة Groupoid (أكثر أو أقل) من الأسهم.

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

لذلك يمكن أن يحصل المرء على sin . x … لكن أعتقد أن هذا سيكون مربكًا: -}

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

لنجربها من زاوية مختلفة ، لا تطلق النار علي.

إذا حددنا .. بواسطة collect(..(A,B)) == ((a[1],..., a[n]), (b[1], ...,b[n])) == zip(A,B) ، فعند استخدام T[x,y,z] = [T(x), T(y), T(z)] رسميًا ، فإنه يحمل ذلك

map(f,A,B) = [f(a[1],b[1]), ..., f(a[n],b[n])] = f[zip(A,B)...] = f[..(A,B)]

هذا يحفز بناء جملة واحد على الأقل للخريطة التي لا تتداخل مع بناء الجملة لبناء المصفوفة. مع :: أو table يؤدي الامتداد f[::(A,B)] = [f(a[i], b[j]) for i in 1:n, j in 1:n] على الأقل إلى حالة استخدام ثانية مثيرة للاهتمام.

فكر جيدًا في السؤال الذي تريد طرحه.

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

إيجاد حل لم يجده أحد بالفعل

nalimilan لا أحد بيننا ، وهذا هو. :ابتسامة:

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

JeffBezanson يسعدني أن أسمع أنك أجريت استطلاعات الرأي بالفعل ، استمر في ذلك!

  • كيف كنت تروج لاستطلاعات الرأي الخاصة بك؟
  • من برنامج الاستطلاع / الاستطلاع الذي قمت بتقييمه حتى الآن kwiksurveys.com ، اسمح للمستخدمين بإضافة آرائهم الخاصة بدلاً من _لا أحد من هذه الخيارات متاح لي_.

لا يبدو sin∘x مفيدًا جدًا ، لأنه يتطلب Unicode وغير معروف على نطاق واسع على أي حال.

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

له سابقة ، فالمشكلة هي أنه Unicode؟ لماذا ا؟ متى سنبدأ في استخدام بقية Unicode غير المعروفة على نطاق واسع بعد ذلك؟ أبدا؟

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

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

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

يكمن جمال استخدام \circ في أنك إذا فسرت عائلة مفهرسة كدالة من مجموعة الفهرس (وهي "التنفيذ" الرياضي القياسي) ، فإن تعيين _is_ مجرد تكوين.

لست متأكدًا من أنني اشتريت هذا.

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

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

mlhetland |> قريب جدًا من -> ويعمل اليوم - كما أنه يتمتع "بميزة" كونه رابطًا صحيحًا.

x = parse( "sin |> cos |> [1,2]" )
:((sin |> cos) |> [1,2])

تضمين التغريدة لكن هذا يقلب تفسير التكوين الذي أوجزته رأساً على عقب. وهذا يعني أن sin∘x سيكون معادلاً لـ x |> sin ، أليس كذلك؟

ملاحظة: ربما ضاعت في "الجبر" ، ولكن فقط السماح بالوظائف في بناء المصفوفة المكتوبة T[x,y,z] مثل أن f[x,y,z] هو [f(x),f(y),f(z)] يعطي مباشرة

map(f,A) == f[A...]

والتي يمكن قراءتها تمامًا ويمكن معاملتها على أنها بناء جملة ..

هذا ذكي. لكني أظن أنه إذا تمكنا من إنجاحها ، فإن sin[x...] سيخسر حقًا الإسهاب إلى sin(x) أو sin~x إلخ.

ماذا عن بناء الجملة [sin xs] ؟

يشبه هذا في بناء الجملة فهم المصفوفة [sin(x) for x in xs] .

mlhetland sin |> x === خريطة (sin، x)

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

mdcfrancis نعم ، فهمت أن هذا ما تطمح إليه. الذي يعكس الأشياء (مثل tkelman يكرر) wrt. تفسير التكوين الذي أوجزته.

أعتقد أن دمج المتجهات والتسلسل سيكون رائعًا. أتساءل عما إذا كانت الكلمات ستكون أوضح العوامل.
شيء مثل:

[1, 2] mapall
  +([2, 3]) map
  ^(2, _) chain
  { a = _ + 1
    b = _ - 1
    [a..., b...] } chain
  sum chain
  [ _, 2, 3] chain
  reduce(+, _)

يمكن دمج عدة خرائط متتالية تلقائيًا في خريطة واحدة لتحسين الأداء. لاحظ أيضًا أنني أفترض أن الخريطة ستحتوي على نوع من ميزة البث التلقائي. استبدال [1 ، 2] بـ _ في البداية يمكن بدلاً من ذلك بناء دالة مجهولة. ملاحظة أنا أستفيد من قواعد Magrittr الخاصة بـ R للتسلسل (انظر رسالتي في سلسلة الرسائل).

ربما بدأ هذا في الظهور مثل DSL.

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

أنا أؤيد بشدة فكرة بناء جملة نظيف للخريطة. يعجبني اقتراح tkelman لـ ~ أكثر من كونه يحتفظ به داخل ASCII لمثل هذه الوظائف الأساسية ، وأنا أحب sin~x تمامًا. هذا من شأنه أن يسمح بتعيين نمط خط واحد متطور جدًا كما تمت مناقشته أعلاه. سيكون استخدام sin∘x مقبولًا أيضًا. بالنسبة لأي شيء أكثر تعقيدًا ، أميل إلى الاعتقاد بأن الحلقة المناسبة هي أكثر وضوحًا (وعادة ما تكون أفضل أداء). لا أحب كثيرًا البث "السحري" ، فهو يجعل متابعة الكود أكثر صعوبة. عادة ما تكون الحلقة الصريحة أكثر وضوحًا.

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

لاحظ أن أحد تأثيرات jb / function هو أن broadcast(op, x, y) يتمتع بأداء جيد تمامًا مثل الإصدار المخصص x .op y الذي خصص البث يدويًا على op .

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

أنا لا أوافق. exp(2 * x.^2) يمكن قراءته تمامًا ، وأقل إسهابًا من [exp(2 * v^2) for v in x] . التحدي هنا IMHO هو تجنب محاصرة الناس عن طريق السماح لهم باستخدام الأول (الذي يخصص النسخ ولا يدمج العمليات): لهذا ، نحتاج إلى إيجاد صيغة تكون قصيرة بما يكفي بحيث يمكن إهمال النموذج البطيء.

المزيد من الأفكار. هناك العديد من الأشياء التي قد ترغب في القيام بها عند استدعاء وظيفة:

حلقة من خلال عدم وجود وسيطات (سلسلة)
حلقة من خلال الحجة المقيدة فقط (خريطة)
حلقة من خلال جميع الحجج (مابال)

يمكن تعديل كل مما سبق من خلال:
وضع علامة على عنصر لإجراء تكرار حلقي خلاله (~)
وضع علامة على عنصر بحيث لا يتم تكرار حلقاته (مجموعة إضافية من [])

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

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

الخطيئة [x ...] تخسر حقًا الإسهاب في الخطيئة (x) أو الخطيئة ~ x إلخ.

أيضًا ، استمرارًا للفكر ، الخريطة sin[x...] هي نسخة أقل حماسًا على [f(x...)] .
النحو

[exp(2 * (...x)^2)]

أو شيء مشابه مثل [exp(2 * (x..)^2)] سيكون متاحًا وشرح ذاتيًا إذا تم تقديم تسلسل وظيفي ضمني حقيقي.

nalimilan نعم ، لكن هذا يناسب فئة "الخط الواحد" التي قلتها على ما يرام بدون حلقة.

بينما نقوم بإدراج جميع رغباتنا: الأهم بالنسبة لي هو أن تكون نتائج map قابلة للتخصيص دون تخصيص أو نسخ. هذا سبب آخر لأنني ما زلت أفضّل الحلقات من أجل الكود الحرج للأداء ، ولكن إذا كان من الممكن تخفيف ذلك (# 249 لا يبحث حاليًا عن أجهزة الصراف الآلي) ، فسيصبح كل هذا أكثر جاذبية.

يمكن تخصيص نتائج الخريطة دون تخصيص أو نسخ

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

أفترض أنه يعني تخزين ناتج map في مصفوفة مخصصة مسبقًا.

نعم بالضبط. أعتذر إذا كان ذلك ممكنا بالفعل.

آه ، بالطبع. لدينا map! ، ولكن كما لاحظت ، فإن # 249 يطلب طريقة أفضل للقيام بذلك.

jtravs لقد اقترحت حلاً أعلاه بـ LazyArray (https://github.com/JuliaLang/julia/issues/8450#issuecomment-65106563) ، لكن الأداء لم يكن مثاليًا حتى الآن.

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

نعم map! صحيح تمامًا. سيكون من الجيد إذا تم تناول أي سكر نحوي لطيف هنا أيضًا هذه الحالة. لا يمكن أن يكون لدينا x := ... يعين ضمنيًا RHS على x .

لقد وضعت حزمة تسمى ChainMap والتي تدمج التخطيط والتسلسل.

إليك مثال قصير:

<strong i="7">@chain</strong> begin
  [1, 2]
  -(1)
  (_, _)
  map_all(+)
  <strong i="8">@chain_map</strong> begin
    -(1)
    ^(2. , _)
  end
  begin
    a = _ - 1
    b = _ + 1
    [a, b]
  end
  sum
end

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

  1. بدءًا من f[a...] والذي تم اقتراحه بالفعل بواسطة Jutho ، الاتفاقية
    هذا لناقلات أ ، ب
f[a...] == map(f, a[:])
f[a..., b...] == map(f, a[:], b[:])
etc

التي لا تقدم رموزًا جديدة.

2.) علاوة على ذلك ، أود أن أقترح إدخال عامل إضافي واحد: عامل حفظ الشكل _ عامل الضرب .. (على سبيل المثال). هذا لأن ... هو عامل توزيع متساوي ، لذا يجب أن يقوم f[a...] بإرجاع متجه وليس مصفوفة حتى إذا كان a هو n -أبعاد. إذا تم اختيار .. ، ففي هذا السياق ،

f[a.., ] == map(f, a)
f[a.., b..] == map(f, a, b)

والنتيجة ترث شكل الحجج. السماح بالبث

f[a.., b..] == broadcast(f, a, b)

من شأنه أن يسمح يفكر مثل الكتابة

sum(*[v.., v'..]) == dot(v,v)

هيوريكا؟

هذا لا يساعد في رسم خرائط التعبيرات ، أليس كذلك؟ تتمثل إحدى مزايا بناء الجملة over في كيفية عمله مع التعبيرات:

sin(x * (y - 2)) over x, y  == map((x, y) -> sin(x * (y - 2)), x, y) 

حسنًا ، ربما عبر [sin(x.. * y..)] أو sin[x.. * y..] أعلاه إذا كنت تريد السماح بذلك. يعجبني ذلك أكثر بقليل من البنية الزائدة لأنه يعطي تلميحًا مرئيًا بأن الوظائف تعمل على العناصر وليس على الحاويات.

ولكن لا يمكنك تبسيط ذلك بحيث يتم تعيين x.. ببساطة فوق x ؟ لذا فإن مثال johansigfrids سيكون:

sin(x.. * (y.. - 2))  == map((x, y) -> sin(x * (y - 2)), x, y)

jtravs بسبب تحديد النطاق ( [println(g(x..))] مقابل println([g(x..)]) ) والاتساق [x..] = x .

أحد الاحتمالات الأخرى هو أخذ x.. = x[:, 1], x[:, 2], etc. كدائرة جزئية للمصفوفات الفرعية (الأعمدة) الرائدة و ..y كدائرة جزئية للمصفوفات الفرعية اللاحقة ..y = y[1,:], y[2,:] . إذا كان كلاهما يعمل على مؤشرات مختلفة ، فإن هذا يغطي العديد من الحالات المثيرة للاهتمام

[f(v..)] == [f(v[i]) for i in 1:m ]
[v.. * v..] == [v[i] * v[i] for 1:m]
[v.. * ..v] == [v[i] * v[j] for i in 1:m, j in 1:n]
[f(..A)] == [f(A[:, j]) for j in 1:n]
[f(A..)] == [f(A[i, :]) for i in 1:m]
[dot(A.., ..A)] == [dot(A[:,i], A[j,:]) for i in 1:m, j in 1:n] == A*A
[f(..A..)] == [f(A[i,j]) for i in 1:m, j in 1:n]
[v..] == [..v] = v
[..A..] == A

( v a Vector، A مصفوفة)

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

أنت محق بشأن الفوضى ، على ما أعتقد وحاولت تكييف اقتراحي وتنظيمه. حتى لا أرهق صبر الجميع أكثر ، كتبت أفكاري حول الخرائط والمؤشرات وما إلى ذلك في جوهر https://gist.github.com/mschauer/b04e000e9d0963e40058 .

بعد قراءة سلسلة الرسائل هذه ، أفضّل حتى الآن أن يكون لديّ _both_ f.(x) للأشياء البسيطة والأشخاص المعتادين على الدوال الموجهة (المصطلح " . = vectorized" شائع جدًا) ، و f(x^2)-x over x لتعبيرات أكثر تعقيدًا.

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

بناء الجملة over يفركني حقًا بطريقة خاطئة. لقد خطر لي السبب: إنه يفترض أن جميع استخدامات كل متغير في تعبير متجه أو غير متجه ، وقد لا يكون الأمر كذلك. على سبيل المثال ، log(A) .- sum(A,1) - افترض أننا أزلنا تحويل الاتجاه لـ log . لا يمكنك أيضًا توجيه الدالات على التعبيرات ، والتي تبدو وكأنها عيب رئيسي إلى حد ما ، ماذا لو أردت كتابة exp(log(A) .- sum(A,1)) ولديك exp و log متجه و sum لا؟

StefanKarpinski ، إذًا يجب عليك إما exp.(log.(A) .- sum(A,1)) وقبول المؤقتات الإضافية (على سبيل المثال في الاستخدام التفاعلي حيث لا يكون الأداء حرجًا) ، أو s = sum(A, 1); exp(log(A) - s) over A (على الرغم من أن هذا ليس صحيحًا تمامًا إذا كان sum(A,1) هو متجه وأردت البث) ؛ قد تضطر فقط إلى استخدام الفهم. بغض النظر عن البنية التي توصلنا إليها ، لن نغطي جميع الحالات المحتملة ، ومثالك يمثل مشكلة بشكل خاص لأن أي بناء جملة "آلي" يجب أن يعرف أن sum خالص وأنه يمكن أن يكون تم رفعه خارج الحلقة / الخريطة.

بالنسبة لي ، الأولوية الأولى هي بناء جملة f.(x...) لـ broadcast(f, x...) أو map(f, x...) حتى نتمكن من التخلص من @vectorize . بعد ذلك ، يمكننا مواصلة العمل على بناء جملة مثل over (أو أيًا كان) لاختصار الاستخدامات العامة لـ map وعمليات الفهم.

stevengj لا أعتقد أن المثال الثاني يعمل ، لأن - لن يتم بثه. بافتراض أن A مصفوفة ، سيكون الناتج مصفوفة من مصفوفات من صف واحد ، كل منها عبارة عن سجل عنصر A مطروحًا منه متجه المجاميع على طول البعد الأول. ستحتاج إلى broadcast((x, y)->exp(log(x)-y), A, sum(A, 1)) . لكنني أعتقد أن وجود صيغة موجزة لـ map مفيد ولا يحتاج بالضرورة إلى أن يكون تركيبًا موجزًا ​​لـ broadcast أيضًا.

هل ستستمر الوظائف التي تم تحويلها تلقائيًا في السابق مثل sin على هذا النحو مع البنية الجديدة ، أم ستصبح مهملة؟ أشعر بالقلق من أنه حتى بناء الجملة f. سيشعر وكأنه "مسكتك" لمجموعة كبيرة من المبرمجين العلميين الذين لا تحفزهم حجج الأناقة المفاهيمية.

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

مشكلة صغيرة واحدة (؟) مع f.(args...) : على الرغم من أن بناء الجملة object.(field) نادرًا ما يستخدم في معظم الأحيان ويمكن استبداله بـ getfield(object, field) بدون ألم شديد ، هناك _lot_ من تعريفات / مراجع الطريقة بالصيغة Base.(:+)(....) = .... ، وسيكون من المؤلم تغييرها إلى getfield .

سيكون أحد الحلول هو:

  • احصل Base.(:+) تحول إلى map(Base, :+) مثل كل f.(args...) ، لكن حدد طريقة مهملة map(m::Module, s::Symbol) = getfield(m, s) للتوافق مع الإصدارات السابقة
  • دعم بناء الجملة Base.:+ (الذي فشل حاليًا) ووصي بهذا في تحذير الإيقاف لـ Base.(:+)

أود أن أسأل مرة أخرى - إذا كان هذا شيء يمكننا القيام به في 0.5.0؟ أعتقد أنه مهم بسبب إهمال العديد من الصانعين المتجهين. اعتقدت أنني سأكون على ما يرام مع هذا ، لكنني أجد map(Int32, a) ، بدلاً من int32(a) أمرًا مملًا بعض الشيء.

هل هذا في الأساس مجرد مسألة اختيار النحو في هذه المرحلة؟

هل هذا في الأساس مجرد مسألة اختيار النحو في هذه المرحلة؟

أعتقد أن stevengj قدم حججًا جيدة لصالح كتابة sin.(x) بدلاً من .sin(x) في علاقاته العامة https://github.com/JuliaLang/julia/pull/15032. لذلك أقول إن الطريق قد تم فتحه.

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

JeffBezanson إنني أعود إلى هذا الإنجاز إلى 0.5.0 لإحضاره أثناء مناقشة الفرز - بشكل أساسي للتأكد من أنني لن أنسى.

هل يعمل # 15032 أيضًا مقابل call - على سبيل المثال Int32.(x) ؟

ViralBShah ، نعم. يتم تحويل أي f.(x...) إلى map(f, broadcast, x...) على مستوى بناء الجملة ، بغض النظر عن نوع f .

هذه هي الميزة الرئيسية لـ . على شيء مثل f[x...] ، وهي ميزة جذابة بخلاف ذلك (ولن تتطلب تغييرات المحلل اللغوي) ولكنها ستعمل فقط مقابل f::Function . f[x...] أيضًا من الناحية المفاهيمية قليلاً مع مصفوفة استيعاب المصفوفة T[...] . على الرغم من أنني أعتقد أن StefanKarpinski يحب بناء الجملة؟

(لاختيار مثال آخر ، تكون العناصر o::PyObject في PyCall قابلة للاستدعاء ، وتستدعي طريقة __call__ لكائن Python o ، لكن نفس العناصر قد تدعم أيضًا o[...] فهرسة f[x...] ، لكنه سيعمل بشكل جيد مع البث o.(x...) .)

call لم يعد موجودًا.

(أحب أيضًا حجة nalimilan بأن f.(x...) يجعل .( التناظرية لـ .+ إلخ.)

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

هل يجب أن يكون getfield المزود بوحدة نمطية إهمالًا فعليًا؟

tkelman ، مقابل ماذا؟ ومع ذلك ، فإن تحذير الإيقاف لـ Base.(:+) (أي وسيطات الرمز الحرفي) يجب أن يقترح Base.:+ ، وليس getfield . (_Update_: مطلوب أيضًا إهمال بناء الجملة للتعامل مع تعريفات الطريقة.)

ViralBShah ، هل كان هناك أي قرار بشأن هذا في مناقشة الفرز يوم الخميس؟ # 15032 في حالة جيدة جدًا للدمج ، على ما أعتقد.

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

  1. مشغل infix الذي سيكون أبسط من الناحية المفاهيمية والتنفيذية ، ولكن ليس لدينا أي عامل ASCII متاح مما يمكنني رؤيته. ستتطلب فكرتي السابقة المتمثلة في إهمال التحليل الكلي لـ ~ عملاً لاستبدال الحزم وربما فات الأوان لمحاولة القيام بذلك في هذه الدورة.
  2. أو صيغة جديدة بديلة تجعل من السهل دمج الحلقات وإلغاء الموقتات. لم يتم تنفيذ أي بدائل أخرى لأي مكان بالقرب من المستوى 15032 ، لذلك يبدو أنه يجب علينا دمج ذلك وتجربته على الرغم من الحجوزات المتبقية.

نعم ، لدي بعض الحجوزات ولكن لا يمكنني رؤية خيار أفضل من f.(x) الآن. يبدو أفضل من اختيار رمز تعسفي مثل ~ ، وأراهن أن العديد ممن اعتادوا على .* (وما إلى ذلك) يمكنهم حتى تخمين ما يعنيه على الفور.

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

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

أنا أستخدم الموجهات في جوليا إلى حد كبير لسهولة القراءة لأن الحلقات سريعة. لذلك أحب استخدامه كثيرًا في exp ، و sin ، وما إلى ذلك ، كما ذكرنا سابقًا. كما سأستخدم بالفعل. ^ ،. * في مثل هذه التعبيرات إضافة النقطة الإضافية إلى الخطيئة. إكسب. إلخ يبدو طبيعيًا حقًا ، وأكثر وضوحًا بالنسبة لي ... خاصةً عندما يمكنني حينئذٍ طي الدالات الخاصة بي بسهولة مع الترميز العام بدلاً من الخلط بين الخطيئة (x) والخريطة (f ، x).

كل ما يمكن قوله ، بصفتي مستخدمًا عاديًا ، آمل حقًا أن يتم دمج هذا!

تعجبني بنية fun[vec] المقترحة أكثر من بناء الجملة fun.(vec) .
ما رأيك في [fun vec] ؟ إنه مثل قائمة الفهم ولكن مع متغير ضمني. يمكن أن يسمح بعمل T[fun vec]

هذه الصيغة مجانية في Julia 0.4 للمتجهات ذات الطول> 1:

julia> [sin rand(1)]
1x2 Array{Any,2}:
 sin  0.0976151

julia> [sin rand(10)]
ERROR: DimensionMismatch("mismatch in dimension 1 (expected 1 got 10)")
 in cat_t at abstractarray.jl:850
 in hcat at abstractarray.jl:875

شيء مثل [fun over vec] يمكن تحويله على مستوى بناء الجملة وربما يستحق تبسيط [fun(x) for x in vec] لكن ليس أبسط من map(fun,vec) .

تراكيب مشابهة لـ [fun vec] : بناء الجملة (fun vec) مجاني و {fun vec} تم إهماله.

julia> (fun vec)
ERROR: syntax: missing separator in tuple

julia> {fun vec}

WARNING: deprecated syntax "{a b ...}".
Use "Any[a b ...]" instead.
1x2 Array{Any,2}:
 fun  [0.3231600663395422,0.10208482721149204,0.7964663210635679,0.5064134055014935,0.7606900072242995,0.29583012284224064,0.5501131920491444,0.35466150455688483,0.6117729165962635,0.7138111929010424]

diegozea ، تم استبعاد fun[vec] لأنه يتعارض مع T[vec] . (fun vec) هو أساسًا بناء جملة المخطط ، حيث يفترض أن تكون حالة الوسائط المتعددة (fun vec1 vec2 ...) ... وهذا يختلف تمامًا عن أي صيغة أخرى لجوليا. أم أنك تنوي (fun vec1, vec2, ...) ، والذي يتعارض مع بناء جملة tuple؟ كما أنه ليس من الواضح ما هي الميزة التي ستكون أكثر من fun.(vecs...) .

علاوة على ذلك ، تذكر أن الهدف الرئيسي هو الحصول في النهاية على بناء جملة لاستبدال وظائف @vectorized (حتى لا يكون لدينا مجموعة فرعية "مباركة" من الوظائف التي "تعمل على المتجهات") ، وهذا يعني أن يجب أن يكون بناء الجملة مستساغًا / بديهيًا / مناسبًا للأشخاص الذين يستخدمون الوظائف الموجهة في Matlab و Numpy وما إلى ذلك. يجب أيضًا أن تكون سهلة التركيب لتعبيرات مثل sin(A .+ cos(B[:,1])) . تستبعد هذه المتطلبات الكثير من المقترحات الأكثر "إبداعًا".

لا يبدو sin.(A .+ cos.(B[:,1])) بهذا السوء. سيحتاج هذا إلى توثيق جيد. هل سيتم توثيق $ # $ f.(x) كـ .( مشابه لـ .+ ؟
هل يمكن إهمال .+ لصالح +. ؟

# Since 
sin.(A .+ cos.(B[:,1]))
# could be written as
sin.(.+(A, cos.(B[:,1])))
# +.
sin.(+.(A, cos.(B[:,1]))) #  will be more coherent.

diegozea ، يحتوي # 15032 بالفعل على وثائق ، ولكن نرحب بأي اقتراحات إضافية.

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

(بمجرد تسوية الصعوبات المتعلقة بحساب النوع في broadcast (# 4883) ، آمل أن أقوم بعلاقات عامة أخرى بحيث يكون a .⧆ b لأي مشغل مجرد سكر للاتصال بـ broadcast(⧆, a, b) . بهذه الطريقة ، لن نحتاج بعد الآن إلى تنفيذ .+ آخره بشكل صريح - ستحصل على مشغل البث تلقائيًا بمجرد تحديد + إلخ. لا تزال قادرًا على تنفيذ طرق متخصصة ، مثل المكالمات إلى BLAS ، عن طريق التحميل الزائد broadcast لمشغلين معينين.)

يجب أيضًا أن تكون قابلة للتكوين بسهولة لتعبيرات مثل sin(A .+ cos(B[:,1])) .

هل من الممكن تحليل f1.(x, f2.(y .+ z)) كـ broadcast((a, b, c)->(f1(a, f2(b + c))), x, y, z) ؟

تحرير: أرى أنه سبق ذكره أعلاه ... في التعليق مخفي افتراضيًا بواسطةgithub ..

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

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

هل هناك أي عيب إذا كان يعمل على دمج الحلقة أيضًا؟

yuyichao ، يعد اندماج الحلقات مشكلة أصعب بكثير ، وليس من الممكن دائمًا استبعاد الوظائف غير البحتة (على سبيل المثال ، انظر مثال StefanKarpinski exp(log(A) .- sum(A,1)) أعلاه). من المحتمل أن يؤدي التمسك بتنفيذ ذلك إلى عدم تنفيذه أبدًا ، في رأيي - علينا القيام بذلك بشكل تدريجي. ابدأ بفضح نية المستخدم. إذا تمكنا من إجراء المزيد من التحسين في المستقبل ، فهذا رائع. إذا لم يكن الأمر كذلك ، فلا يزال لدينا بديل معمم لعدد قليل من الوظائف "الموجهة" المتاحة الآن.

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

ما أعنيه هو أن إجراء اندماج الحلقة من خلال إثبات صحة القيام بذلك أمر صعب ، لذلك يمكننا السماح للمحلل بإجراء التحويل كجزء من المخططات. في المثال أعلاه ، يمكن كتابتها كـ. exp.(log.(A) .- sum(A,1)) وسيتم تحليلها كـ broadcast((x, y)->exp(log(x) - y), A, sum(A, 1)) .

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

من الصعب إجراء اندماج الحلقة من خلال إثبات صحة ذلك

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

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

أنا أميل إلى القيام بذلك بعد العلاقات العامة "غير القابلة للانصهار".

موافق تمامًا ، خاصة وأن .+ لا يتم التعامل معه على أي حال.

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

StefanKarpinski ، عندما تقوم باستدعاء f.(args...) أو broadcast(f, args...) ، فإنه يقوم بتوجيه أكثر من _ all_ الوسيطات. (لهذا الغرض ، تذكر أنه يتم التعامل مع الحجميات كمصفوفات ذات أبعاد صفرية.) في اقتراح yuyichao لـ f.(args...) = _ تركيب البث المدمج_ (وهو ما يعجبني أكثر وأكثر) ، أعتقد أن الدمج سيكون " stop "عند أي تعبير ليس func.(args...) (لتضمين .+ وما إلى ذلك في المستقبل).

لذلك ، على سبيل المثال ، سيتحول sin.(x .+ cos.(x .^ sum(x.^2))) (بـ julia-syntax.scm ) إلى broadcast((x, _s_) -> sin(x + cos(x^_s_)), x, sum(broacast(^, x, 2))) . لاحظ أن الدالة sum ستكون "حد الاندماج". سيكون المتصل مسؤولاً عن عدم استخدام f.(args...) في الحالات التي قد يؤدي فيها الاندماج إلى إفساد الآثار الجانبية.

هل لديك مثال في ذهنك حيث لن يكون هذا كافيًا؟

الذي يعجبني أكثر فأكثر

أنا سعيد أنها أعجبتك. =)

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

نعم ، كان الافتقار إلى الاندماج للعمليات الأخرى هو اعتراضي الرئيسي على .+= آخره في # 7052 ، لكن أعتقد أنه سيتم حل ذلك من خلال وجود مصهر .= مع مكالمات أخرى func.(args...) . أو فقط قم بدمج x[:] = ... .

: thumbsup: هناك مفهومان متجمعان معًا في هذه المناقشة وهما في الواقع متعامدان تمامًا:
matlab'y "عمليات البث المدمجة" أو x .* y .+ z و apl'y "خرائط على المنتجات والرمز البريدي" مثل f[product(I,J)...] و f[zip(I,J)...] . الحديث الماضي قد يكون له علاقة بذلك أيضًا.

mschauer ، f.(I, J) بالفعل (في 15032 #) ما يعادل map(x -> f(x...), zip(I, J) إذا كان I و J لهما نفس الشكل. وإذا كان I متجهًا للصف وكان J متجهًا للعمود أو العكس ، فإن broadcast يقوم بالفعل بتعيين مجموعة المنتجات (أو يمكنك عمل f.(I, J') إذا كانا كلاهما مصفوفتان 1d). لذلك أنا لا أفهم لماذا تعتقد أن المفاهيم "متعامدة تمامًا".

لم تكن الكلمة المتعامدة هي الكلمة الصحيحة ، فهي مختلفة بما يكفي للتعايش.

لكن النقطة المهمة هي أننا لسنا بحاجة إلى صياغة منفصلة للحالتين. يمكن func.(args...) دعم كليهما.

بمجرد أن يدمج عضو في الثلاثي (ستيفان ، جيف ، فيال) # 15032 (والذي أعتقد أنه جاهز للدمج) ، سأغلق هذا وأقدم مشكلة في خريطة الطريق لتوضيح التغييرات المقترحة المتبقية: إصلاح حساب نوع البث ، وإيقاف @vectorize ، حوّل .op إلى سكر البث ، أضف "انصهار البث" على مستوى بناء الجملة ، وأخيراً اندمج مع التعيين الموضعي. ربما لن يصل الاثنان الأخيران إلى 0.5.

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

أعتقد أنه يمكن إغلاق هذا الآن لصالح # 16285.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات