Constructors

البناؤون [1] هم دوال تقوم بإنشاء كائنات جديدة - تحديدًا، حالات من Composite Types. في جوليا، تعمل كائنات النوع أيضًا كدوال بنائية: فهي تنشئ حالات جديدة من نفسها عند تطبيقها على مجموعة من الوسائط كدالة. تم ذكر هذا بالفعل بإيجاز عندما تم تقديم الأنواع المركبة. على سبيل المثال:

julia> struct Foo
           bar
           baz
       end

julia> foo = Foo(1, 2)
Foo(1, 2)

julia> foo.bar
1

julia> foo.baz
2

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

Outer Constructor Methods

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

julia> Foo(x) = Foo(x,x)
Foo

julia> Foo(1)
Foo(1, 1)

يمكنك أيضًا إضافة طريقة مُنشئ Foo بدون معلمات التي توفر قيم افتراضية لكل من حقلي bar و baz:

julia> Foo() = Foo(0)
Foo

julia> Foo()
Foo(0, 0)

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

Inner Constructor Methods

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

  1. يتم الإعلان عنه داخل كتلة إعلان النوع، بدلاً من خارجه مثل الطرق العادية.
  2. لديها وصول إلى دالة محلية خاصة تُدعى new التي تنشئ كائنات من نوع الكتلة.

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

julia> struct OrderedPair
           x::Real
           y::Real
           OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
       end

الآن يمكن إنشاء كائنات OrderedPair فقط بالشكل الذي يكون فيه x <= y:

julia> OrderedPair(1, 2)
OrderedPair(1, 2)

julia> OrderedPair(2,1)
ERROR: out of order
Stacktrace:
 [1] error at ./error.jl:33 [inlined]
 [2] OrderedPair(::Int64, ::Int64) at ./none:4
 [3] top-level scope

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

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

julia> struct Foo
           bar
           baz
           Foo(bar,baz) = new(bar,baz)
       end

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

julia> struct T1
           x::Int64
       end

julia> struct T2
           x::Int64
           T2(x) = new(x)
       end

julia> T1(1)
T1(1)

julia> T2(1)
T2(1)

julia> T1(1.0)
T1(1)

julia> T2(1.0)
T2(1)

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

Incomplete Initialization

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

julia> mutable struct SelfReferential
           obj::SelfReferential
       end

قد يبدو هذا النوع غير ضار بما فيه الكفاية، حتى يتم النظر في كيفية إنشاء مثيل له. إذا كان a مثيلًا لـ SelfReferential، فيمكن إنشاء مثيل ثانٍ من خلال الاستدعاء:

julia> b = SelfReferential(a)

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

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

julia> mutable struct SelfReferential
           obj::SelfReferential
           SelfReferential() = (x = new(); x.obj = x)
       end

يمكننا التحقق من أن هذا المُنشئ يعمل ويقوم بإنشاء كائنات هي، في الواقع، ذاتية الإشارة:

julia> x = SelfReferential();

julia> x === x
true

julia> x === x.obj
true

julia> x === x.obj.obj
true

على الرغم من أنه من الجيد عمومًا إرجاع كائن مُهيأ بالكامل من مُنشئ داخلي، إلا أنه من الممكن إرجاع كائنات غير مُهيأة بالكامل:

julia> mutable struct Incomplete
           data
           Incomplete() = new()
       end

julia> z = Incomplete();

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

julia> z.data
ERROR: UndefRefError: access to undefined reference

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

julia> struct HasPlain
           n::Int
           HasPlain() = new()
       end

julia> HasPlain()
HasPlain(438103441441)

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

يمكنك تمرير كائنات غير مكتملة إلى دوال أخرى من المنشئين الداخليين لتفويض إكمالها:

julia> mutable struct Lazy
           data
           Lazy(v) = complete_me(new(), v)
       end

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

Parametric Constructors

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

julia> struct Point{T<:Real}
           x::T
           y::T
       end

julia> Point(1,2) ## implicit T ##
Point{Int64}(1, 2)

julia> Point(1.0,2.5) ## implicit T ##
Point{Float64}(1.0, 2.5)

julia> Point(1,2.5) ## implicit T ##
ERROR: MethodError: no method matching Point(::Int64, ::Float64)
The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  Point(::T, ::T) where T<:Real at none:2

julia> Point{Int64}(1, 2) ## explicit T ##
Point{Int64}(1, 2)

julia> Point{Int64}(1.0,2.5) ## explicit T ##
ERROR: InexactError: Int64(2.5)
Stacktrace:
[...]

julia> Point{Float64}(1.0, 2.5) ## explicit T ##
Point{Float64}(1.0, 2.5)

julia> Point{Float64}(1,2) ## explicit T ##
Point{Float64}(1.0, 2.0)

كما ترى، بالنسبة لاستدعاءات المُنشئ مع معلمات نوع صريحة، يتم تحويل الوسائط إلى أنواع الحقول الضمنية: Point{Int64}(1,2) يعمل، ولكن Point{Int64}(1.0,2.5) يرفع InexactError عند تحويل 2.5 إلى Int64. عندما يكون النوع مستنتجًا من الوسائط لاستدعاء المُنشئ، كما في Point(1,2)، يجب أن تتفق أنواع الوسائط – وإلا فلا يمكن تحديد T – ولكن يمكن إعطاء أي زوج من الوسائط الحقيقية ذات النوع المتطابق إلى مُنشئ Point العام.

ما يحدث هنا حقًا هو أن Point و Point{Float64} و Point{Int64} كلها دوال منشئ مختلفة. في الواقع، Point{T} هي دالة منشئ مميزة لكل نوع T. بدون أي منشئات داخلية مقدمة بشكل صريح، فإن إعلان النوع المركب Point{T<:Real} يوفر تلقائيًا منشئًا داخليًا، Point{T}، لكل نوع ممكن T<:Real، والذي يتصرف تمامًا مثل ما تفعله المنشئات الداخلية الافتراضية غير المعاملات. كما أنه يوفر منشئًا خارجيًا عامًا واحدًا لـ Point يأخذ أزواجًا من المعاملات الحقيقية، والتي يجب أن تكون من نفس النوع. هذه التوفير التلقائي للمنشئات يعادل الإعلان الصريح التالي:

julia> struct Point{T<:Real}
           x::T
           y::T
           Point{T}(x,y) where {T<:Real} = new(x,y)
       end

julia> Point(x::T, y::T) where {T<:Real} = Point{T}(x,y);

لاحظ أن كل تعريف يبدو مثل شكل استدعاء المُنشئ الذي يتعامل معه. سيؤدي الاستدعاء Point{Int64}(1,2) إلى استدعاء التعريف Point{T}(x,y) داخل كتلة struct. من ناحية أخرى، يحدد إعلان المُنشئ الخارجي طريقة لمُنشئ Point العام الذي ينطبق فقط على أزواج القيم من نفس النوع الحقيقي. يجعل هذا الإعلان استدعاءات المُنشئ بدون معلمات نوع صريحة، مثل Point(1,2) و Point(1.0,2.5) تعمل. نظرًا لأن إعلان الطريقة يقيد المعاملات لتكون من نفس النوع، فإن الاستدعاءات مثل Point(1,2.5)، مع معاملات من أنواع مختلفة، تؤدي إلى أخطاء "لا توجد طريقة".

افترض أننا أردنا جعل استدعاء المُنشئ Point(1,2.5) يعمل عن طريق "ترقية" القيمة الصحيحة 1 إلى القيمة العشرية 1.0. أبسط طريقة لتحقيق ذلك هي تعريف طريقة مُنشئ خارجية إضافية كما يلي:

julia> Point(x::Int64, y::Float64) = Point(convert(Float64,x),y);

تستخدم هذه الطريقة دالة convert لتحويل x بشكل صريح إلى Float64 ثم تفوض البناء إلى المُنشئ العام للحالة التي تكون فيها كلا الحجتين 4d61726b646f776e2e436f64652822222c2022466c6f617436342229_40726566. مع تعريف هذه الطريقة، ما كان سابقًا MethodError الآن ينشئ بنجاح نقطة من النوع Point{Float64}:

julia> p = Point(1,2.5)
Point{Float64}(1.0, 2.5)

julia> typeof(p)
Point{Float64}

ومع ذلك، لا تزال المكالمات المماثلة الأخرى لا تعمل:

julia> Point(1.5,2)
ERROR: MethodError: no method matching Point(::Float64, ::Int64)
The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  Point(::T, !Matched::T) where T<:Real
   @ Main none:1
  Point(!Matched::Int64, !Matched::Float64)
   @ Main none:1

Stacktrace:
[...]

لطريقة أكثر عمومية لجعل جميع هذه المكالمات تعمل بشكل منطقي، انظر Conversion and Promotion. على خطر إفساد التشويق، يمكننا الكشف هنا أن كل ما يتطلبه الأمر هو تعريف الطريقة الخارجية التالية لجعل جميع المكالمات إلى المُنشئ العام Point تعمل كما هو متوقع:

julia> Point(x::Real, y::Real) = Point(promote(x,y)...);

تقوم دالة promote بتحويل جميع وسائطها إلى نوع مشترك - في هذه الحالة Float64. مع هذا التعريف للدالة، يقوم مُنشئ Point بترقية وسائطه بنفس الطريقة التي تقوم بها العمليات العددية مثل +، ويعمل مع جميع أنواع الأعداد الحقيقية:

julia> Point(1.5,2)
Point{Float64}(1.5, 2.0)

julia> Point(1,1//2)
Point{Rational{Int64}}(1//1, 1//2)

julia> Point(1.0,1//2)
Point{Float64}(1.0, 0.5)

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

Case Study: Rational

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

julia> struct OurRational{T<:Integer} <: Real
           num::T
           den::T
           function OurRational{T}(num::T, den::T) where T<:Integer
               if num == 0 && den == 0
                    error("invalid rational: 0//0")
               end
               num = flipsign(num, den)
               den = flipsign(den, den)
               g = gcd(num, den)
               num = div(num, g)
               den = div(den, g)
               new(num, den)
           end
       end

julia> OurRational(n::T, d::T) where {T<:Integer} = OurRational{T}(n,d)
OurRational

julia> OurRational(n::Integer, d::Integer) = OurRational(promote(n,d)...)
OurRational

julia> OurRational(n::Integer) = OurRational(n,one(n))
OurRational

julia> ⊘(n::Integer, d::Integer) = OurRational(n,d)
⊘ (generic function with 1 method)

julia> ⊘(x::OurRational, y::Integer) = x.num ⊘ (x.den*y)
⊘ (generic function with 2 methods)

julia> ⊘(x::Integer, y::OurRational) = (x*y.den) ⊘ y.num
⊘ (generic function with 3 methods)

julia> ⊘(x::Complex, y::Real) = complex(real(x) ⊘ y, imag(x) ⊘ y)
⊘ (generic function with 4 methods)

julia> ⊘(x::Real, y::Complex) = (x*y') ⊘ real(y*y')
⊘ (generic function with 5 methods)

julia> function ⊘(x::Complex, y::Complex)
           xy = x*y'
           yy = real(y*y')
           complex(real(xy) ⊘ yy, imag(xy) ⊘ yy)
       end
⊘ (generic function with 6 methods)

السطر الأول – struct OurRational{T<:Integer} <: Real – يعلن أن OurRational يأخذ معلمة نوع واحدة من نوع صحيح، وهو في حد ذاته نوع حقيقي. تشير إعلانات الحقول num::T و den::T إلى أن البيانات المحتفظ بها في كائن OurRational{T} هي زوج من الأعداد الصحيحة من النوع T، حيث يمثل أحدهما بسط القيمة الكسرية والآخر يمثل مقامها.

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

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

بعد تعريفات المُنشئ الخارجي، قمنا بتعريف عدد من الطرق لعملية ، التي توفر صيغة لكتابة الأعداد النسبية (على سبيل المثال، 1 ⊘ 2). يستخدم نوع Rational في جوليا العملية // لهذا الغرض. قبل هذه التعريفات، كانت عملية غير معرفة تمامًا مع وجود صيغة فقط دون معنى. بعد ذلك، تتصرف كما هو موصوف في Rational Numbers – سلوكها بالكامل مُعرف في هذه السطور القليلة. لاحظ أن الاستخدام في الوسط لـ يعمل لأن جوليا لديها مجموعة من الرموز المعترف بها كعمليات وسطية. التعريف الأول والأساسي يجعل a ⊘ b ينشئ OurRational عن طريق تطبيق مُنشئ OurRational على a و b عندما يكونا عددين صحيحين. عندما يكون أحد المعاملين لـ عددًا نسبيًا بالفعل، نقوم بإنشاء عدد نسبي جديد للنسبة الناتجة بشكل مختلف قليلاً؛ هذا السلوك هو في الواقع مطابق لقسمة عدد نسبي على عدد صحيح. أخيرًا، تطبيق على قيم عددية معقدة ينشئ مثيلًا من Complex{<:OurRational} – عدد معقد تكون أجزاؤه الحقيقية والتخيلية أعدادًا نسبية:

julia> z = (1 + 2im) ⊘ (1 - 2im);

julia> typeof(z)
Complex{OurRational{Int64}}

julia> typeof(z) <: Complex{<:OurRational}
true

لذا، على الرغم من أن عامل التشغيل عادةً ما يُرجع مثيلًا من OurRational، إذا كان أحد معاييره عددًا صحيحًا مركبًا، فسيرجع مثيلًا من Complex{<:OurRational} بدلاً من ذلك. يجب على القارئ المهتم أن يفكر في تصفح بقية rational.jl: إنه قصير، ومحتوى ذاتي، وينفذ نوع جوليا أساسي كامل.

Outer-only constructors

كما رأينا، يحتوي النوع البارامتري النموذجي على منشئات داخلية تُستدعى عندما تكون معلمات النوع معروفة؛ على سبيل المثال، تنطبق على Point{Int} ولكن لا تنطبق على Point. يمكن إضافة منشئات خارجية تحدد معلمات النوع تلقائيًا، على سبيل المثال بناء Point{Int} من الاستدعاء Point(1,2). تستدعي المنشئات الخارجية المنشئات الداخلية لصنع الحالات فعليًا. ومع ذلك، في بعض الحالات، قد يفضل المرء عدم توفير منشئات داخلية، بحيث لا يمكن طلب معلمات النوع المحددة يدويًا.

على سبيل المثال، لنفترض أننا نحدد نوعًا يخزن متجهًا مع تمثيل دقيق لمجموعه:

julia> struct SummedArray{T<:Number,S<:Number}
           data::Vector{T}
           sum::S
       end

julia> SummedArray(Int32[1; 2; 3], Int32(6))
SummedArray{Int32, Int32}(Int32[1, 2, 3], 6)

المشكلة هي أننا نريد أن يكون S نوعًا أكبر من T، حتى نتمكن من جمع العديد من العناصر مع فقدان أقل للمعلومات. على سبيل المثال، عندما يكون T هو Int32، نود أن يكون S هو Int64. لذلك نريد تجنب واجهة تسمح للمستخدم بإنشاء مثيلات من النوع SummedArray{Int32,Int32}. إحدى الطرق للقيام بذلك هي توفير مُنشئ فقط لـ SummedArray، ولكن داخل كتلة تعريف struct لقمع توليد المُنشئات الافتراضية:

julia> struct SummedArray{T<:Number,S<:Number}
           data::Vector{T}
           sum::S
           function SummedArray(a::Vector{T}) where T
               S = widen(T)
               new{T,S}(a, sum(S, a))
           end
       end

julia> SummedArray(Int32[1; 2; 3], Int32(6))
ERROR: MethodError: no method matching SummedArray(::Vector{Int32}, ::Int32)
The type `SummedArray` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  SummedArray(::Vector{T}) where T
   @ Main none:4

Stacktrace:
[...]

سيتم استدعاء هذا المُنشئ بواسطة الصيغة SummedArray(a). تتيح الصيغة new{T,S} تحديد المعلمات لنوع الكائن الذي سيتم إنشاؤه، أي أن هذه المكالمة ستعيد SummedArray{T,S}. يمكن استخدام new{T,S} في أي تعريف مُنشئ، ولكن لسهولة الاستخدام، يتم اشتقاق المعلمات لـ new{} تلقائيًا من النوع الذي يتم إنشاؤه عند الإمكان.

Constructors are just callable objects

يمكن أن يكون كائن من أي نوع made callable عن طريق تعريف طريقة. وهذا يشمل الأنواع، أي كائنات من نوع Type؛ ويمكن، في الواقع، اعتبار المنشئات ككائنات نوع قابلة للاستدعاء. على سبيل المثال، هناك العديد من الطرق المعرفة على Bool وأنواعها العليا المختلفة:

julia> methods(Bool)
# 10 methods for type constructor:
  [1] Bool(x::BigFloat)
     @ Base.MPFR mpfr.jl:393
  [2] Bool(x::Float16)
     @ Base float.jl:338
  [3] Bool(x::Rational)
     @ Base rational.jl:138
  [4] Bool(x::Real)
     @ Base float.jl:233
  [5] (dt::Type{<:Integer})(ip::Sockets.IPAddr)
     @ Sockets ~/tmp/jl/jl/julia-nightly-assert/share/julia/stdlib/v1.11/Sockets/src/IPAddr.jl:11
  [6] (::Type{T})(x::Enum{T2}) where {T<:Integer, T2<:Integer}
     @ Base.Enums Enums.jl:19
  [7] (::Type{T})(z::Complex) where T<:Real
     @ Base complex.jl:44
  [8] (::Type{T})(x::Base.TwicePrecision) where T<:Number
     @ Base twiceprecision.jl:265
  [9] (::Type{T})(x::T) where T<:Number
     @ boot.jl:894
 [10] (::Type{T})(x::AbstractChar) where T<:Union{AbstractChar, Number}
     @ char.jl:50

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

julia> struct S
           f::Int
       end

julia> S() = S(7)
S

julia> (::Type{S})() = S(8)  # overwrites the previous constructor method

julia> S()
S(8)
  • 1Nomenclature: while the term "constructor" generally refers to the entire function which constructs objects of a type, it is common to abuse terminology slightly and refer to specific constructor methods as "constructors". In such situations, it is generally clear from the context that the term is used to mean "constructor method" rather than "constructor function", especially as it is often used in the sense of singling out a particular method of the constructor from all of the others.