Frequently Asked Questions

General

Is Julia named after someone or something?

لا.

Why don't you compile Matlab/Python/R/… code to Julia?

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

المسألة الأساسية هي أنه لا يوجد شيء خاص في مترجم جوليا: نحن نستخدم مترجمًا شائعًا (LLVM) بدون "صوص سري" لا يعرفه مطورو اللغات الآخرون. في الواقع، مترجم جوليا أبسط بكثير في العديد من النواحي من مترجمات اللغات الديناميكية الأخرى (مثل PyPy أو LuaJIT). ميزة أداء جوليا تأتي تقريبًا بالكامل من واجهتها الأمامية: دلالات لغتها تسمح لـ well-written Julia program بتوفير المزيد من الفرص للمترجم لتوليد كود وتخطيطات ذاكرة فعالة. إذا حاولت تجميع كود Matlab أو Python إلى جوليا، سيكون مترجمنا محدودًا بدلالات Matlab أو Python في إنتاج كود لا أفضل من ذلك الموجود لمترجمات تلك اللغات (وربما أسوأ). الدور الرئيسي للدلالات هو أيضًا السبب في أن العديد من مترجمات Python الحالية (مثل Numba و Pythran) تحاول فقط تحسين مجموعة صغيرة من اللغة (مثل العمليات على مصفوفات Numpy والسكالار)، ولهذه المجموعة هم بالفعل يقومون بعمل جيد على الأقل مثلما يمكننا لنفس الدلالات. الأشخاص الذين يعملون على تلك المشاريع أذكياء بشكل لا يصدق وقد حققوا أشياء مذهلة، لكن تعديل مترجم على لغة تم تصميمها لتكون مترجمة هو مشكلة صعبة جدًا.

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

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

من ناحية أخرى، فإن التشغيل البيني للغات مفيد للغاية: نريد استغلال الشيفرة عالية الجودة الموجودة في لغات أخرى من جوليا (وعكس ذلك)! أفضل طريقة لتمكين ذلك ليست عبر مترجم، بل من خلال تسهيلات استدعاء بين اللغات بسهولة. لقد عملنا بجد على هذا، بدءًا من الدالة المدمجة ccall (للاستدعاء من مكتبات C و Fortran) إلى JuliaInterop الحزم التي تربط جوليا بـ Python و Matlab و C++ والمزيد.

Public API

How does Julia define its public API?

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

الرموز العامة هي تلك التي تم وضع علامة عليها إما بـ public foo أو export foo.

بعبارة أخرى:

  • سلوك الرموز العامة الموثق هو جزء من واجهة برمجة التطبيقات العامة.
  • سلوك الرموز العامة غير الموثق ليس جزءًا من واجهة برمجة التطبيقات العامة.
  • سلوك الرموز الخاصة الموثق ليس جزءًا من واجهة برمجة التطبيقات العامة.
  • سلوك الرموز الخاصة غير الموثق ليس جزءًا من واجهة برمجة التطبيقات العامة.

يمكنك الحصول على قائمة كاملة بالرموز العامة من وحدة باستخدام names(MyModule).

يُشجَّع مؤلفو الحزم على تعريف واجهة برمجة التطبيقات العامة الخاصة بهم بطريقة مماثلة.

أي شيء في واجهة برمجة التطبيقات العامة لجوليا مشمول بـ SemVer وبالتالي لن يتم إزالته أو تلقي تغييرات كبيرة ذات مغزى قبل جوليا 2.0.

There is a useful undocumented function/type/constant. Can I use it?

Updating Julia may break your code if you use non-public API. If the code is self-contained, it may be a good idea to copy it into your project. If you want to rely on a complex non-public API, especially when using it from a stable package, it is a good idea to open an issue or pull request to start a discussion for turning it into a public API. However, we do not discourage the attempt to create packages that expose stable public interfaces while relying on non-public implementation details of Julia and buffering the differences across different Julia versions.

The documentation is not accurate enough. Can I rely on the existing behavior?

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

Sessions and the REPL

How do I delete an object in memory?

جوليا لا تحتوي على نظير لدالة clear في MATLAB؛ بمجرد تعريف اسم في جلسة جوليا (تقنيًا، في الوحدة Main)، فإنه يظل موجودًا دائمًا.

إذا كانت استخدام الذاكرة هي مصدر قلقك، يمكنك دائمًا استبدال الكائنات بأخرى تستهلك ذاكرة أقل. على سبيل المثال، إذا كانت A مصفوفة بحجم غيغابايت لم تعد بحاجة إليها، يمكنك تحرير الذاكرة باستخدام A = nothing. سيتم تحرير الذاكرة في المرة القادمة التي يعمل فيها جامع القمامة؛ يمكنك إجبار ذلك على الحدوث باستخدام GC.gc(). علاوة على ذلك، فإن محاولة استخدام A ستؤدي على الأرجح إلى حدوث خطأ، لأن معظم الطرق غير معرفة على النوع Nothing.

How can I modify the declaration of a type in my session?

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

ERROR: invalid redefinition of constant MyType

لا يمكن إعادة تعريف الأنواع في الوحدة Main.

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

include("mynewcode.jl")              # this defines a module MyModule
obj1 = MyModule.ObjConstructor(a, b)
obj2 = MyModule.somefunction(obj1)
# Got an error. Change something in "mynewcode.jl"
include("mynewcode.jl")              # reload the module
obj1 = MyModule.ObjConstructor(a, b) # old objects are no longer valid, must reconstruct
obj2 = MyModule.somefunction(obj1)   # this time it worked!
obj3 = MyModule.someotherfunction(obj2, c)
...

Scripting

How do I check if the current file is being run as the main script?

عند تشغيل ملف كبرنامج رئيسي باستخدام julia file.jl، قد يرغب المرء في تفعيل وظائف إضافية مثل معالجة وسائط سطر الأوامر. طريقة لتحديد ما إذا كان الملف قد تم تشغيله بهذه الطريقة هي التحقق مما إذا كان abspath(PROGRAM_FILE) == @__FILE__ هو true.

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

How do I catch CTRL-C in a script?

تشغيل نص جوليّا باستخدام julia file.jl لا يُطلق InterruptException عندما تحاول إنهاءه باستخدام CTRL-C (SIGINT). لتشغيل كود معين قبل إنهاء نص جوليّا، والذي قد يكون أو لا يكون ناتجًا عن CTRL-C، استخدم atexit. بدلاً من ذلك، يمكنك استخدام julia -e 'include(popfirst!(ARGS))' file.jl لتنفيذ نص مع القدرة على التقاط InterruptException في كتلة try. لاحظ أنه مع هذه الاستراتيجية، لن يتم تعيين PROGRAM_FILE.

How do I pass options to julia using #!/usr/bin/env?

تمرير الخيارات إلى julia في ما يُعرف بسطر shebang، كما في #!/usr/bin/env julia --startup-file=no، لن يعمل على العديد من المنصات (BSD، macOS، Linux) حيث أن النواة، على عكس الصدفة، لا تقسم الوسائط عند أحرف المسافة. الخيار env -S، الذي يقسم سلسلة وسيط واحدة إلى وسائط متعددة عند المسافات، مشابهًا للصدفة، يقدم حلاً بسيطًا:

#!/usr/bin/env -S julia --color=yes --startup-file=no
@show ARGS  # put any Julia code here
Note

ظهر الخيار env -S في FreeBSD 6.0 (2005) وmacOS Sierra (2016) وGNU/Linux coreutils 8.30 (2018).

Why doesn't run support * or pipes for scripting external programs?

تقوم دالة جوليا run بإطلاق البرامج الخارجية مباشرة، دون استدعاء operating-system shell (على عكس دالة system("...") في لغات أخرى مثل بايثون، R، أو C). هذا يعني أن run لا يقوم بتوسيع الأحرف النائبة * ("globbing")، ولا يقوم بتفسير shell pipelines مثل | أو >.

يمكنك still القيام بالتجميع وعمليات الأنابيب باستخدام ميزات جوليا، ومع ذلك. على سبيل المثال، الدالة المدمجة pipeline تتيح لك ربط البرامج الخارجية والملفات، مشابهة لعمليات الأنابيب في الشل، و Glob.jl package تنفذ التجميع المتوافق مع POSIX.

يمكنك، بالطبع، تشغيل البرامج من خلال الصدفة عن طريق تمرير صدفة وسلسلة أوامر إلى run، على سبيل المثال run(`sh -c "ls > files.txt"`) لاستخدام Bourne shell في يونكس، ولكن يجب عليك عمومًا تفضيل البرمجة النقية بلغة جوليا مثل run(pipeline(`ls`, "files.txt")). السبب في أننا نتجنب الصدفة بشكل افتراضي هو أن shelling out sucks: إطلاق العمليات عبر الصدفة بطيء، وهش بالنسبة لاقتباس الأحرف الخاصة، ويعاني من معالجة أخطاء ضعيفة، ومشكلة في قابلية النقل. (توصل مطورو بايثون إلى similar conclusion.)

Variables and Assignments

Why am I getting UndefVarError from a simple loop?

قد يكون لديك شيء مثل:

x = 0
while x < 10
    x += 1
end

ولاحظ أنه يعمل بشكل جيد في بيئة تفاعلية (مثل REPL لجوليا)، ولكنه يعطي UndefVarError: `x` not defined عندما تحاول تشغيله في نص أو ملف آخر. ما يحدث هو أن جوليا تتطلب عمومًا منك أن تكون صريحًا بشأن التعيين لمتغيرات عالمية في نطاق محلي.

هنا، x هو متغير عالمي، while يحدد local scope، و x += 1 هو تعيين لمتغير عالمي في ذلك النطاق المحلي.

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

  1. ضع الكود في دالة (حتى تكون x متغيرًا محليًا في دالة). بشكل عام، من الجيد في هندسة البرمجيات استخدام الدوال بدلاً من السكربتات العالمية (ابحث عبر الإنترنت عن "لماذا المتغيرات العالمية سيئة" لرؤية العديد من الشروحات). في جوليا، المتغيرات العالمية هي أيضًا slow.
  2. قم بتغليف الشيفرة في كتلة let. (هذا يجعل x متغيرًا محليًا داخل عبارة let ... end، مما يلغي مرة أخرى الحاجة إلى global).
  3. حدد x بشكل صريح كـ global داخل النطاق المحلي قبل تعيينه، على سبيل المثال، اكتب global x += 1.

يمكن العثور على مزيد من الشرح في قسم الدليل on soft scope.

Functions

I passed an argument x to a function, modified it inside that function, but on the outside, the variable x is still unchanged. Why?

افترض أنك تستدعي دالة مثل هذه:

julia> x = 10
10

julia> function change_value!(y)
           y = 17
       end
change_value! (generic function with 1 method)

julia> change_value!(x)
17

julia> x # x is unchanged!
10

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

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

julia> x = [1,2,3]
3-element Vector{Int64}:
 1
 2
 3

julia> function change_array!(A)
           A[1] = 5
       end
change_array! (generic function with 1 method)

julia> change_array!(x)
5

julia> x
3-element Vector{Int64}:
 5
 2
 3

هنا أنشأنا دالة change_array!، التي تعين 5 للعنصر الأول من المصفوفة الممررة (المربوطة بـ x في موقع الاستدعاء، والمربوطة بـ A داخل الدالة). لاحظ أنه بعد استدعاء الدالة، لا يزال x مربوطًا بنفس المصفوفة، لكن محتوى تلك المصفوفة قد تغير: كانت المتغيرات A و x روابط متميزة تشير إلى نفس كائن Array القابل للتغيير.

Can I use using or import inside a function?

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

  1. استخدم import:

    julia import Foo function bar(...) # ... refer to Foo symbols via Foo.baz ... end

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

  2. قم بتغليف دالتك في وحدة:

    julia module Bar export bar using Foo function bar(...) # ... refer to Foo.baz as simply baz .... end end using Bar

    هذا يستورد جميع الرموز من Foo، ولكن فقط داخل الوحدة Bar.

What does the ... operator do?

The two uses of the ... operator: slurping and splatting

يجد العديد من القادمين الجدد إلى جوليا استخدام عامل ... محيرًا. جزء من ما يجعل عامل ... محيرًا هو أنه يعني شيئين مختلفين اعتمادًا على السياق.

... combines many arguments into one argument in function definitions

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

julia> function printargs(args...)
           println(typeof(args))
           for (i, arg) in enumerate(args)
               println("Arg #$i = $arg")
           end
       end
printargs (generic function with 1 method)

julia> printargs(1, 2, 3)
Tuple{Int64, Int64, Int64}
Arg #1 = 1
Arg #2 = 2
Arg #3 = 3

إذا كانت جوليا لغة تستخدم المزيد من أحرف ASCII بحرية، لكان من الممكن كتابة عامل السحب كـ <-... بدلاً من ....

... splits one argument into many different arguments in function calls

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

julia> function threeargs(a, b, c)
           println("a = $a::$(typeof(a))")
           println("b = $b::$(typeof(b))")
           println("c = $c::$(typeof(c))")
       end
threeargs (generic function with 1 method)

julia> x = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> threeargs(x...)
a = 1::Int64
b = 2::Int64
c = 3::Int64

إذا كانت جوليا لغة تستخدم المزيد من الأحرف ASCII بحرية، لكان من الممكن كتابة عامل التوزيع كـ ...-> بدلاً من ....

What is the return value of an assignment?

المشغل = دائمًا يُرجع الجانب الأيمن، لذلك:

julia> function threeint()
           x::Int = 3.0
           x # returns variable x
       end
threeint (generic function with 1 method)

julia> function threefloat()
           x::Int = 3.0 # returns 3.0
       end
threefloat (generic function with 1 method)

julia> threeint()
3

julia> threefloat()
3.0

و بنفس الطريقة:

julia> function twothreetup()
           x, y = [2, 3] # assigns 2 to x and 3 to y
           x, y # returns a tuple
       end
twothreetup (generic function with 1 method)

julia> function twothreearr()
           x, y = [2, 3] # returns an array
       end
twothreearr (generic function with 1 method)

julia> twothreetup()
(2, 3)

julia> twothreearr()
2-element Vector{Int64}:
 2
 3

Types, type declarations, and constructors

What does "type-stable" mean?

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

julia> function unstable(flag::Bool)
           if flag
               return 1
           else
               return 1.0
           end
       end
unstable (generic function with 1 method)

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

Why does Julia give a DomainError for certain seemingly-sensible operations?

بعض العمليات لها معنى رياضي ولكن تؤدي إلى أخطاء:

julia> sqrt(-2.0)
ERROR: DomainError with -2.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
[...]

هذا السلوك هو نتيجة غير مريحة لمتطلبات استقرار النوع. في حالة sqrt، يرغب معظم المستخدمين في أن تعطي sqrt(2.0) رقمًا حقيقيًا، وسيكونون غير سعداء إذا أنتجت الرقم المركب 1.4142135623730951 + 0.0im. يمكن للمرء كتابة دالة 4d61726b646f776e2e436f64652822222c2022737172742229_40726566 لتبديل الإخراج إلى قيمة مركبة فقط عند تمرير رقم سالب (وهو ما تفعله 4d61726b646f776e2e436f64652822222c2022737172742229_40726566 في بعض اللغات الأخرى)، ولكن بعد ذلك لن تكون النتيجة type-stable وستكون دالة 4d61726b646f776e2e436f64652822222c2022737172742229_40726566 ذات أداء ضعيف.

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

julia> sqrt(-2.0+0im)
0.0 + 1.4142135623730951im

How can I constrain or compute type parameters?

يمكن أن تحتوي معلمات parametric type على قيم من نوع أو بت، ويختار النوع نفسه كيفية استخدام هذه المعلمات. على سبيل المثال، يتم تهيئة Array{Float64, 2} بواسطة النوع Float64 للتعبير عن نوع عناصره والقيمة الصحيحة 2 للتعبير عن عدد أبعاده. عند تعريف نوع بارامتري خاص بك، يمكنك استخدام قيود الأنواع الفرعية للإعلان عن أن معلمة معينة يجب أن تكون نوعًا فرعيًا (<:) من نوع تجريدي معين أو معلمة نوع سابقة. ومع ذلك، لا توجد صياغة مخصصة للإعلان عن أن معلمة يجب أن تكون قيمة من نوع معين - أي أنك لا تستطيع مباشرةً الإعلان عن أن معلمة تشبه الأبعاد isa Int داخل تعريف struct، على سبيل المثال. وبالمثل، لا يمكنك إجراء عمليات حسابية (بما في ذلك أشياء بسيطة مثل الجمع أو الطرح) على معلمات النوع. بدلاً من ذلك، يمكن التعبير عن هذه الأنواع من القيود والعلاقات من خلال معلمات نوع إضافية يتم حسابها وفرضها داخل constructors للنمط.

كمثال، اعتبر

struct ConstrainedType{T,N,N+1} # NOTE: INVALID SYNTAX
    A::Array{T,N}
    B::Array{T,N+1}
end

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

struct ConstrainedType{T,N,M}
    A::Array{T,N}
    B::Array{T,M}
    function ConstrainedType(A::Array{T,N}, B::Array{T,M}) where {T,N,M}
        N + 1 == M || throw(ArgumentError("second argument should have one more axis" ))
        new{T,N,M}(A, B)
    end
end

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

ConstrainedType(A) = ConstrainedType(A, compute_B(A))

Why does Julia use native machine integer arithmetic?

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

julia> x = typemax(Int)
9223372036854775807

julia> y = x+1
-9223372036854775808

julia> z = -y
-9223372036854775808

julia> 2*z
0

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

واحدة من البدائل التي يجب النظر فيها هي التحقق من كل عملية عدد صحيح للتأكد من عدم حدوث تجاوز، وترقية النتائج إلى أنواع أعداد صحيحة أكبر مثل Int128 أو BigInt في حالة حدوث تجاوز. للأسف، هذا يقدم عبئًا كبيرًا على كل عملية عدد صحيح (فكر في زيادة عداد حلقة) - يتطلب إصدار كود لأداء فحوصات تجاوز في وقت التشغيل بعد التعليمات الحسابية وفروع للتعامل مع التجاوزات المحتملة. والأسوأ من ذلك، أن هذا سيتسبب في أن تكون كل عملية حسابية تتضمن أعدادًا صحيحة غير مستقرة من حيث النوع. كما ذكرنا أعلاه، type-stability is crucial من أجل توليد فعال لكود كفء. إذا لم تتمكن من الاعتماد على نتائج العمليات العددية الصحيحة كونها أعدادًا صحيحة، فمن المستحيل توليد كود سريع وبسيط بالطريقة التي تفعلها مترجمات C و Fortran.

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

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

>> int64(9223372036854775807)

ans =

  9223372036854775807

>> int64(9223372036854775807) + 1

ans =

  9223372036854775807

>> int64(-9223372036854775808)

ans =

 -9223372036854775808

>> int64(-9223372036854775808) - 1

ans =

 -9223372036854775808

في الوهلة الأولى، يبدو أن هذا معقول بما فيه الكفاية لأن 9223372036854775807 أقرب بكثير إلى 9223372036854775808 من -9223372036854775808، ولا تزال الأعداد الصحيحة ممثلة بحجم ثابت بطريقة طبيعية تتوافق مع C و Fortran. ومع ذلك، فإن حساب الأعداد الصحيحة المشبعة يمثل مشكلة عميقة. القضية الأولى والأكثر وضوحًا هي أن هذه ليست الطريقة التي تعمل بها حسابات الأعداد الصحيحة في الآلات، لذا فإن تنفيذ العمليات المشبعة يتطلب إصدار تعليمات بعد كل عملية عدد صحيح في الآلة للتحقق من الانخفاض أو الزيادة واستبدال النتيجة بـ typemin(Int) أو typemax(Int) حسب الاقتضاء. هذا وحده يوسع كل عملية عدد صحيح من تعليمات سريعة واحدة إلى نصف دزينة من التعليمات، ربما تشمل الفروع. أوه. لكن الأمر يزداد سوءًا - حساب الأعداد الصحيحة المشبعة ليس تجميعيًا. اعتبر هذا الحساب في Matlab:

>> n = int64(2)^62
4611686018427387904

>> n + (n - 1)
9223372036854775807

>> (n + n) - 1
9223372036854775806

هذا يجعل من الصعب كتابة العديد من خوارزميات الأعداد الصحيحة الأساسية حيث تعتمد الكثير من التقنيات الشائعة على حقيقة أن الجمع الآلي مع تجاوز السعة هو تجميعي. اعتبر العثور على نقطة المنتصف بين القيم الصحيحة lo و hi في جوليا باستخدام التعبير (lo + hi) >>> 1:

julia> n = 2^62
4611686018427387904

julia> (n + 2n) >>> 1
6917529027641081856

هل ترى؟ لا توجد مشكلة. هذه هي النقطة المتوسطة الصحيحة بين 2^62 و 2^63، على الرغم من أن n + 2n هو -4611686018427387904. الآن جرب ذلك في Matlab:

>> (n + 2*n)/2

ans =

  4611686018427387904

عذرًا. إضافة عامل >>> إلى Matlab لن تساعد، لأن التشبع الذي يحدث عند إضافة n و 2n قد دمر بالفعل المعلومات اللازمة لحساب النقطة الوسطى الصحيحة.

ليس فقط أن عدم التوافق مؤسف للمبرمجين الذين لا يمكنهم الاعتماد عليه في تقنيات مثل هذه، ولكنه أيضًا يقضي على أي شيء قد ترغب المترجمات في القيام به لتحسين الحسابات الصحيحة. على سبيل المثال، نظرًا لأن أعداد جوليا تستخدم حسابات صحيحة عادية، فإن LLVM حر في تحسين الوظائف البسيطة مثل f(k) = 5k-1 بشكل عدواني. الشيفرة الآلية لهذه الوظيفة هي فقط:

julia> code_native(f, Tuple{Int})
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 1
  leaq  -1(%rdi,%rdi,4), %rax
  popq  %rbp
  retq
  nopl  (%rax,%rax)

جسم الدالة الفعلي هو تعليمة leaq واحدة، والتي تحسب الضرب والجمع الصحيحين في آن واحد. هذا يكون أكثر فائدة عندما يتم تضمين f في دالة أخرى:

julia> function g(k, n)
           for i = 1:n
               k = f(k)
           end
           return k
       end
g (generic function with 1 methods)

julia> code_native(g, Tuple{Int,Int})
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 2
  testq %rsi, %rsi
  jle L26
  nopl  (%rax)
Source line: 3
L16:
  leaq  -1(%rdi,%rdi,4), %rdi
Source line: 2
  decq  %rsi
  jne L16
Source line: 5
L26:
  movq  %rdi, %rax
  popq  %rbp
  retq
  nop

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

julia> function g(k)
           for i = 1:10
               k = f(k)
           end
           return k
       end
g (generic function with 2 methods)

julia> code_native(g,(Int,))
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 3
  imulq $9765625, %rdi, %rax    # imm = 0x9502F9
  addq  $-2441406, %rax         # imm = 0xFFDABF42
Source line: 5
  popq  %rbp
  retq
  nopw  %cs:(%rax,%rax)

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

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

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

What are the possible causes of an UndefVarError during remote execution?

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

julia> module Foo
           foo() = remotecall_fetch(x->x, 2, "Hello")
       end

julia> Foo.foo()
ERROR: On worker 2:
UndefVarError: `Foo` not defined in `Main`
Stacktrace:
[...]

الاختصار x->x يحمل مرجعًا إلى Foo، وبما أن Foo غير متاح في العقدة 2، يتم طرح UndefVarError.

لا يتم تسلسل المتغيرات العالمية تحت الوحدات الأخرى غير Main بالقيمة إلى العقدة البعيدة. يتم إرسال مرجع فقط. قد تتسبب الدوال التي تنشئ ارتباطات عالمية (باستثناء تحت Main) في حدوث UndefVarError لاحقًا.

julia> @everywhere module Foo
           function foo()
               global gvar = "Hello"
               remotecall_fetch(()->gvar, 2)
           end
       end

julia> Foo.foo()
ERROR: On worker 2:
UndefVarError: `gvar` not defined in `Main.Foo`
Stacktrace:
[...]

في المثال أعلاه، @everywhere module Foo عرّف Foo على جميع العقد. ومع ذلك، فإن الاستدعاء لـ Foo.foo() أنشأ ربطًا عالميًا جديدًا gvar على العقدة المحلية، لكن لم يتم العثور على ذلك في العقدة 2 مما أدى إلى حدوث خطأ UndefVarError.

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

julia> gvar_self = "Node1"
"Node1"

julia> remotecall_fetch(()->gvar_self, 2)
"Node1"

julia> remotecall_fetch(varinfo, 2)
name          size summary
––––––––– –––––––– –––––––
Base               Module
Core               Module
Main               Module
gvar_self 13 bytes String

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

julia> bar() = 1
bar (generic function with 1 method)

julia> remotecall_fetch(bar, 2)
ERROR: On worker 2:
UndefVarError: `#bar` not defined in `Main`
[...]

julia> anon_bar  = ()->1
(::#21) (generic function with 1 method)

julia> remotecall_fetch(anon_bar, 2)
1

Troubleshooting "method not matched": parametric type invariance and MethodErrors

Why doesn't it work to declare foo(bar::Vector{Real}) = 42 and then call foo([1])?

كما سترى إذا جربت هذا، فإن النتيجة هي MethodError:

julia> foo(x::Vector{Real}) = 42
foo (generic function with 1 method)

julia> foo([1])
ERROR: MethodError: no method matching foo(::Vector{Int64})
The function `foo` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  foo(!Matched::Vector{Real})
   @ Main none:1

Stacktrace:
[...]

هذا لأن Vector{Real} ليس سوبرتايب لـ Vector{Int}! يمكنك حل هذه المشكلة بشيء مثل foo(bar::Vector{T}) where {T<:Real} (أو الشكل القصير foo(bar::Vector{<:Real}) إذا لم يكن المعامل الثابت T مطلوبًا في جسم الدالة). الـ T هو بطاقة برية: تحدد أولاً أنه يجب أن يكون من نوع فرعي لـ Real، ثم تحدد أن الدالة تأخذ Vector من العناصر من هذا النوع.

تذهب هذه المشكلة نفسها لأي نوع مركب Comp، وليس فقط Vector. إذا كان لدى Comp معلمة معلنة من النوع Y، فإن نوعًا آخر Comp2 مع معلمة من النوع X<:Y ليس من نوع Comp. هذه هي عدم التغاير في النوع (على النقيض، فإن Tuple هو تغاير نوعي في معلماته). انظر Parametric Composite Types لمزيد من الشرح حول هذه.

Why does Julia use * for string concatenation? Why not + or something else?

الـ main argument ضد + هو أن دمج السلاسل النصية ليس تبادليًا، بينما يُستخدم + عمومًا كعامل تبادلي. بينما تعترف مجتمع جوليا بأن لغات أخرى تستخدم عوامل مختلفة وقد يكون * غير مألوف لبعض المستخدمين، إلا أنه ينقل خصائص جبرية معينة.

لاحظ أنه يمكنك أيضًا استخدام string(...) لدمج السلاسل (وأي قيم أخرى تم تحويلها إلى سلاسل)؛ وبالمثل، يمكن استخدام repeat بدلاً من ^ لتكرار السلاسل. الـ interpolation syntax مفيد أيضًا لبناء السلاسل.

Packages and Modules

What is the difference between "using" and "import"?

هناك عدة اختلافات بين using و import (انظر Modules section)، ولكن هناك اختلاف مهم قد لا يبدو بديهيًا للوهلة الأولى، وعلى السطح (أي من حيث الصياغة) قد يبدو صغيرًا جدًا. عند تحميل الوحدات باستخدام using، تحتاج إلى قول function Foo.bar(... لتمديد دالة bar في الوحدة Foo بطريقة جديدة، ولكن مع import Foo.bar، تحتاج فقط إلى قول function bar(... وسيتم تمديد دالة bar في الوحدة Foo تلقائيًا.

السبب في أن هذا مهم بما يكفي ليتم إعطاؤه بناء جملة منفصل هو أنك لا تريد أن تقوم بتمديد دالة لم تكن تعرف بوجودها، لأن ذلك قد يتسبب بسهولة في حدوث خطأ. من المرجح أن يحدث هذا مع طريقة تأخذ نوعًا شائعًا مثل سلسلة نصية أو عدد صحيح، لأن كلا منكما والوحدة الأخرى يمكن أن تعرف طريقة للتعامل مع مثل هذا النوع الشائع. إذا استخدمت import، فسوف تستبدل تنفيذ الوحدة الأخرى لـ bar(s::AbstractString) بتنفيذك الجديد، مما قد يؤدي بسهولة إلى القيام بشيء مختلف تمامًا (ويكسر جميع/الكثير من الاستخدامات المستقبلية للدوال الأخرى في الوحدة Foo التي تعتمد على استدعاء bar).

Nothingness and missing values

How does "null", "nothingness" or "missingness" work in Julia?

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

تُستخدم بعض الدوال فقط من أجل آثارها الجانبية، ولا تحتاج إلى إرجاع قيمة. في هذه الحالات، القاعدة هي إرجاع القيمة nothing، وهي مجرد كائن فردي من نوع Nothing. هذا نوع عادي بلا حقول؛ لا يوجد شيء مميز عنه باستثناء هذه القاعدة، وأن REPL لا يطبع أي شيء له. بعض البنى اللغوية التي لن يكون لها قيمة بخلاف ذلك تُنتج أيضًا nothing، على سبيل المثال if false; end.

في الحالات التي يوجد فيها قيمة x من النوع T فقط في بعض الأحيان، يمكن استخدام نوع Union{T, Nothing} كبديل لوسائط الدالة، وحقول الكائنات، وأنواع عناصر المصفوفة كما هو الحال في Nullable, Option or Maybe في لغات أخرى. إذا كانت القيمة نفسها يمكن أن تكون nothing (لا سيما عندما يكون T هو Any)، فإن نوع Union{Some{T}, Nothing} هو الأكثر ملاءمة حيث أن x == nothing تشير إلى غياب قيمة، و x == Some(nothing) تشير إلى وجود قيمة تساوي nothing. الدالة something تسمح بفك تغليف كائنات Some واستخدام قيمة افتراضية بدلاً من وسائط nothing. لاحظ أن المترجم قادر على توليد كود فعال عند العمل مع وسائط أو حقول Union{T, Nothing}.

لتمثيل البيانات المفقودة بالمعنى الإحصائي (NA في R أو NULL في SQL)، استخدم كائن missing. راجع قسم Missing Values لمزيد من التفاصيل.

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

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

Memory

Why does x += y allocate memory when x and y are arrays?

في جوليا، يتم استبدال x += y أثناء التخفيض بـ x = x + y. بالنسبة للمصفوفات، فإن هذا له عواقب تتمثل في أنه بدلاً من تخزين النتيجة في نفس الموقع في الذاكرة مثل x، فإنه يخصص مصفوفة جديدة لتخزين النتيجة. إذا كنت تفضل تعديل x، استخدم x .+= y لتحديث كل عنصر بشكل فردي.

بينما قد يفاجئ هذا السلوك البعض، فإن الاختيار متعمد. السبب الرئيسي هو وجود كائنات غير قابلة للتغيير داخل جوليا، والتي لا يمكنها تغيير قيمتها بمجرد إنشائها. في الواقع، الرقم هو كائن غير قابل للتغيير؛ العبارات x = 5; x += 1 لا تعدل معنى 5، بل تعدل القيمة المرتبطة بـ x. بالنسبة لكائن غير قابل للتغيير، فإن الطريقة الوحيدة لتغيير القيمة هي إعادة تعيينها.

لتعزيز الفكرة قليلاً، اعتبر الدالة التالية:

function power_by_squaring(x, n::Int)
    ispow2(n) || error("This implementation only works for powers of 2")
    while n >= 2
        x *= x
        n >>= 1
    end
    x
end

بعد استدعاء مثل x = 5; y = power_by_squaring(x, 4)، ستحصل على النتيجة المتوقعة: x == 5 && y == 625. ومع ذلك، افترض الآن أن *=، عند استخدامه مع المصفوفات، قام بتغيير الجانب الأيسر. سيكون هناك مشكلتان:

  • بالنسبة للمصفوفات المربعة العامة، لا يمكن تنفيذ A = A*B بدون تخزين مؤقت: يتم حساب A[1,1] وتخزينه على الجانب الأيسر قبل الانتهاء من استخدامه على الجانب الأيمن.
  • افترض أنك كنت مستعدًا لتخصيص مؤقت للحساب (مما سيقضي على معظم فكرة جعل *= يعمل في المكان)؛ إذا استفدت من قابلية تغيير x، فإن هذه الدالة ستتصرف بشكل مختلف بالنسبة للمدخلات القابلة للتغيير وغير القابلة للتغيير. على وجه الخصوص، بالنسبة لـ x غير القابل للتغيير، بعد الاستدعاء سيكون لديك (بشكل عام) y != x، ولكن بالنسبة لـ x القابل للتغيير سيكون لديك y == x.

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

Asynchronous IO and concurrent synchronous writes

Why do concurrent writes to the same stream result in inter-mixed output?

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

يرجى تقديم المحتوى الذي ترغب في ترجمته.

julia> @sync for i in 1:3
           @async write(stdout, string(i), " Foo ", " Bar ")
       end
123 Foo  Foo  Foo  Bar  Bar  Bar

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

print و println "يقفلان" الدفق أثناء الاستدعاء. وبالتالي، فإن تغيير write إلى println في المثال أعلاه يؤدي إلى:

julia> @sync for i in 1:3
           @async println(stdout, string(i), " Foo ", " Bar ")
       end
1 Foo  Bar
2 Foo  Bar
3 Foo  Bar

يمكنك قفل كتاباتك باستخدام ReentrantLock بهذه الطريقة:

julia> l = ReentrantLock();

julia> @sync for i in 1:3
           @async begin
               lock(l)
               try
                   write(stdout, string(i), " Foo ", " Bar ")
               finally
                   unlock(l)
               end
           end
       end
1 Foo  Bar 2 Foo  Bar 3 Foo  Bar

Arrays

What are the differences between zero-dimensional arrays and scalars?

المصفوفات ذات الأبعاد الصفرية هي مصفوفات من الشكل Array{T,0}. إنها تتصرف بشكل مشابه للعدادات، ولكن هناك اختلافات مهمة. تستحق هذه المصفوفات ذكرًا خاصًا لأنها حالة خاصة تجعل المعنى المنطقي للمصفوفات واضحًا، ولكن قد تكون غير بديهية بعض الشيء في البداية. السطر التالي يعرّف مصفوفة ذات أبعاد صفرية:

julia> A = zeros()
0-dimensional Array{Float64,0}:
0.0

في هذا المثال، A هو حاوية قابلة للتغيير تحتوي على عنصر واحد، يمكن تعيينه بواسطة A[] = 1.0 واسترجاعه باستخدام A[]. جميع المصفوفات ذات الأبعاد الصفرية لها نفس الحجم (size(A) == ())، والطول (length(A) == 1). على وجه الخصوص، المصفوفات ذات الأبعاد الصفرية ليست فارغة. إذا وجدت هذا غير بديهي، فإليك بعض الأفكار التي قد تساعد في فهم تعريف جوليا.

  • المصفوفات ذات الأبعاد الصفرية هي "نقطة" إلى "خط" المتجه و"سطح" المصفوفة. تمامًا كما أن الخط ليس له مساحة (لكنه لا يزال يمثل مجموعة من الأشياء)، فإن النقطة ليس لها طول أو أي أبعاد على الإطلاق (لكنها لا تزال تمثل شيئًا).
  • نحن نعرف prod(()) بأنه 1، وعدد العناصر الإجمالي في مصفوفة هو حاصل ضرب الحجم. حجم مصفوفة ذات أبعاد صفرية هو ()، وبالتالي فإن طولها هو 1.
  • المصفوفات ذات الأبعاد الصفرية لا تحتوي بشكل أصلي على أي أبعاد يمكنك الفهرسة فيها - إنها فقط A[]. يمكننا تطبيق نفس قاعدة "الواحد المتأخر" عليها كما هو الحال مع جميع أبعاد المصفوفات الأخرى، لذا يمكنك بالفعل فهرستها كـ A[1]، A[1,1]، إلخ؛ انظر Omitted and extra indices.

من المهم أيضًا فهم الفروق بين القيم العادية. القيم ليست حاويات قابلة للتغيير (على الرغم من أنها قابلة للتكرار وتحدد أشياء مثل length و getindex، على سبيل المثال 1[] == 1). على وجه الخصوص، إذا تم تعريف x = 0.0 كقيمة، فإن محاولة تغيير قيمتها عبر x[] = 1.0 هي خطأ. يمكن تحويل قيمة x إلى مصفوفة بعدد أبعاد صفرية تحتوي عليها عبر fill(x)، وعلى العكس، يمكن تحويل مصفوفة بعدد أبعاد صفرية a إلى القيمة المحتواة عبر a[]. فرق آخر هو أن القيمة يمكن أن تشارك في عمليات الجبر الخطي مثل 2 * rand(2,2)، لكن العملية المماثلة مع مصفوفة بعدد أبعاد صفرية fill(2) * rand(2,2) هي خطأ.

Why are my Julia benchmarks for linear algebra operations different from other languages?

يمكنك أن تجد أن المعايير البسيطة لبناء الكتل الأساسية للجبر الخطي مثل

using BenchmarkTools
A = randn(1000, 1000)
B = randn(1000, 1000)
@btime $A \ $B
@btime $A * $B

يمكن أن تكون مختلفة عند مقارنتها بلغات أخرى مثل Matlab أو R.

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

  1. مكتبة BLAS التي تستخدمها كل لغة،
  2. عدد الخيوط المتزامنة.

تقوم جوليا بتجميع واستخدام نسختها الخاصة من OpenBLAS، مع تحديد عدد الخيوط حاليًا عند 8 (أو عدد النوى لديك).

تعديل إعدادات OpenBLAS أو تجميع Julia مع مكتبة BLAS مختلفة، مثل Intel MKL، قد يوفر تحسينات في الأداء. يمكنك استخدام MKL.jl، وهي حزمة تجعل الجبر الخطي في Julia يستخدم Intel MKL BLAS و LAPACK بدلاً من OpenBLAS، أو البحث في منتدى المناقشة عن اقتراحات حول كيفية إعداد ذلك يدويًا. لاحظ أن Intel MKL لا يمكن تضمينه مع Julia، لأنه ليس مفتوح المصدر.

Computing cluster

How do I manage precompilation caches in distributed file systems?

عند استخدام جوليا في مرافق الحوسبة عالية الأداء (HPC) مع أنظمة الملفات المشتركة، يُوصى باستخدام مستودع مشترك (عبر متغير البيئة JULIA_DEPOT_PATH). منذ جوليا v1.10، ستنسق عمليات جوليا المتعددة على العمال المتشابهين وظيفيًا والتي تستخدم نفس المستودع عبر أقفال pidfile لتقليل الجهد المبذول في التحضير المسبق على عملية واحدة بينما تنتظر العمليات الأخرى. ستشير عملية التحضير المسبق إلى متى تكون العملية في حالة التحضير المسبق أو في انتظار أخرى تقوم بالتحضير المسبق. إذا كانت غير تفاعلية، فإن الرسائل تكون عبر @debug.

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

Julia Releases

Do I want to use the Stable, LTS, or nightly version of Julia?

الإصدار الثابت من جوليا هو أحدث إصدار تم إصداره من جوليا، وهذا هو الإصدار الذي يرغب معظم الناس في تشغيله. يحتوي على أحدث الميزات، بما في ذلك تحسين الأداء. يتم إصدار الإصدار الثابت من جوليا وفقًا لـ SemVer كـ v1.x.y. يتم إصدار تحديث فرعي جديد لجوليا يتوافق مع إصدار ثابت جديد تقريبًا كل 4-5 أشهر بعد بضعة أسابيع من الاختبار كمرشح للإصدار. على عكس إصدار LTS، لن يتلقى الإصدار الثابت عادةً إصلاحات الأخطاء بعد إصدار إصدار ثابت آخر من جوليا. ومع ذلك، سيكون من الممكن دائمًا الترقية إلى الإصدار الثابت التالي حيث سيستمر كل إصدار من جوليا v1.x في تشغيل الشيفرة المكتوبة للإصدارات السابقة.

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

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

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

يمكن العثور على روابط لكل من هذه الأنواع من التنزيلات في صفحة التنزيل على https://julialang.org/downloads/. لاحظ أن جميع إصدارات جوليا ليست متاحة لجميع المنصات.

How can I transfer the list of installed packages after updating my version of Julia?

كل إصدار فرعي من جوليا له environment الافتراضي الخاص به. نتيجة لذلك، عند تثبيت إصدار فرعي جديد من جوليا، لن تكون الحزم التي أضفتها باستخدام الإصدار الفرعي السابق متاحة بشكل افتراضي. يتم تعريف البيئة لإصدار جوليا معين بواسطة الملفات Project.toml و Manifest.toml في مجلد يتطابق مع رقم الإصدار في .julia/environments/، على سبيل المثال، .julia/environments/v1.3.

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

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