برنامه آموزشی تایپ در زبان های برنامه نویسی. اصول برنامه نویسی: تایپ استاتیک در مقابل تایپ پویا تایپ قوی در مقابل ضعیف

سادگی تایپ در رویکرد OO نتیجه سادگی مدل محاسبه شی است. با حذف جزئیات، می توان گفت که در طول اجرای یک سیستم OO، تنها یک نوع رویداد رخ می دهد - فراخوانی ویژگی:


نشان دهنده عملیات fبالای جسم متصل به ایکس، با گذشت استدلال ارگ(احتمالاً چندین آرگومان یا اصلاً هیچ کدام). برنامه نویسان Smalltalk در این مورد در مورد "گذراندن یک شی" صحبت می کنند ایکسپیام ها fبا استدلال ارگ"، اما این فقط یک تفاوت در اصطلاح است و بنابراین قابل توجه نیست.

اینکه همه چیز بر اساس این ساختار پایه است، بخشی از حس زیبایی را در ایده های OO توضیح می دهد.

از Basic Construction آن موقعیت های غیرعادی را که ممکن است در فرآیند اجرا ایجاد شود دنبال کنید:

تعریف: نقض نوع

نقض نوع زمان اجرا، یا به اختصار فقط نقض نوع، در زمان تماس رخ می دهد. x.f(arg)، جایی که ایکسمتصل به یک شی OBJاگر هر کدام:

[ایکس].هیچ جزء منطبقی وجود ندارد fو قابل اجرا برای OBJ,

[ایکس].با این حال، چنین جزء وجود دارد، استدلال ارگبرای او غیر قابل قبول است

مشکل تایپ این است که از موقعیت هایی مانند زیر اجتناب کنید:

مشکل تایپ برای سیستم های OO

چه زمانی متوجه می شویم که در اجرای یک سیستم OO ممکن است نقض نوع رخ دهد؟

کلمه کلیدی این است چه زمانی. دیر یا زود متوجه خواهید شد که نقض نوع وجود دارد. به عنوان مثال، تلاش برای اجرای مولفه "Torpedo Launch" روی یک شی "Employee" کار نخواهد کرد و اجرا با شکست مواجه خواهد شد. با این حال، ممکن است ترجیح دهید به جای دیرتر، اشکالات را در اسرع وقت پیدا کنید.

تایپ استاتیک و پویا

اگرچه گزینه های میانی امکان پذیر است، دو رویکرد اصلی در اینجا ارائه شده است:

[ایکس]. تایپ پویا: صبر کنید تا هر تماس کامل شود و سپس تصمیم بگیرید.

[ایکس]. تایپ استاتیک: با توجه به مجموعه ای از قوانین، از متن منبع مشخص کنید که آیا نقض نوع در طول اجرا امکان پذیر است یا خیر. سیستم در صورتی اجرا می شود که قوانین تضمین کند که هیچ خطایی وجود ندارد.

توضیح این اصطلاحات آسان است: با تایپ پویا، بررسی نوع در حین عملکرد سیستم (به صورت پویا) رخ می دهد، در حالی که با تایپ استاتیک، بررسی نوع بر روی متن به صورت ایستا (قبل از اجرا) انجام می شود.

تایپ استاتیک پیشنهاد می کند بررسی خودکاربه عنوان یک قاعده به کامپایلر اختصاص داده می شود. در نتیجه ما یک تعریف ساده داریم:

تعریف: زبان تایپ ایستا

یک زبان OO به صورت ایستا تایپ می‌شود اگر با مجموعه‌ای از قوانین ثابت همراه باشد که توسط کامپایلر بررسی می‌شود و اطمینان حاصل می‌کند که اجرای سیستم منجر به نقض نوع نمی‌شود.

عبارت " قویتایپ کردن" ( قوی). این با ماهیت اولتیماتوم تعریف مطابقت دارد و مستلزم عدم وجود کامل نقض نوع است. ممکن و ضعیف (ضعیف) اشکال تایپ ایستا، که در آن قوانین برخی از موارد نقض را بدون حذف کامل حذف می کنند. از این نظر، برخی از زبان‌های OO به صورت ایستا ضعیف تایپ می‌شوند. ما برای قوی ترین تایپ مبارزه خواهیم کرد.

در زبان‌های تایپ شده پویا، که به عنوان زبان‌های تایپ نشده شناخته می‌شوند، هیچ نوع اعلان وجود ندارد و می‌توان هر مقداری را در زمان اجرا به موجودیت‌ها متصل کرد. بررسی نوع استاتیک در آنها امکان پذیر نیست.

قوانین تایپ

نماد OO ما به صورت ایستا تایپ شده است. قوانین نوع او در سخنرانی های قبلی معرفی شد و به سه شرط ساده خلاصه می شود.

[ایکس].هنگام اعلان هر موجودیت یا تابع، نوع آن باید مشخص شود، به عنوان مثال، اکانت: ACCOUNT. هر زیربرنامه دارای 0 یا بیشتر آرگومان رسمی است که نوع آن باید داده شود، به عنوان مثال: قرار دادن (x: G؛ i: INTEGER).

[ایکس].در هر تکلیفی x: = yو در هر فراخوانی زیر روال که در آن yاستدلال واقعی برای استدلال رسمی است ایکس، نوع منبع yباید با نوع هدف سازگار باشد ایکس. تعریف سازگاری بر اساس وراثت است: بسازگار با آ، اگر از نسل آن باشد، - با قوانینی برای پارامترهای عمومی تکمیل شده است (به سخنرانی 14 مراجعه کنید).

[ایکس].صدا زدن x.f(arg)آن را ایجاب می کند fیک جزء کلاس پایه برای نوع هدف بود ایکس، و fباید به کلاسی که تماس در آن ظاهر می شود صادر شود (به 14.3 مراجعه کنید).

واقع گرایی

اگرچه تعریف یک زبان تایپ ایستا کاملاً دقیق است، اما کافی نیست - معیارهای غیررسمی هنگام ایجاد قوانین تایپ مورد نیاز است. بیایید دو مورد شدید را در نظر بگیریم.

[ایکس]. زبان کاملا درست، که در آن هر سیستم صحیح نحوی نیز از نظر نوع صحیح است. قوانین اعلام نوع مورد نیاز نیست. چنین زبان هایی وجود دارند (به نماد لهستانی برای جمع و تفریق اعداد صحیح فکر کنید). متأسفانه، هیچ زبان جهانی واقعی این معیار را برآورده نمی کند.

[ایکس]. زبان کاملا اشتباه، که با استفاده از هر زبان موجود و اضافه کردن یک قانون تایپ که ایجاد می کند آسان است هرسیستم نادرست است طبق تعریف، این زبان تایپ می شود: از آنجایی که هیچ سیستمی وجود ندارد که با قوانین مطابقت داشته باشد، هیچ سیستمی باعث نقض نوع نمی شود.

می توان گفت که زبان های نوع اول مناسب، ولی بلا استفاده، دومی ممکن است مفید باشد، اما مناسب نیست.

در عمل، آنچه مورد نیاز است یک سیستم نوع است که در عین حال مفید و مفید باشد: آنقدر قدرتمند که نیازهای محاسبات را برآورده کند و به اندازه کافی راحت باشد که ما را مجبور به رفتن به سمت پیچیدگی برای ارضای قوانین تایپ نکند.

خواهیم گفت که زبان واقع بیناگر در عمل قابل استفاده و مفید باشد. برخلاف تعریف تایپ ایستا، که پاسخی قاطع به این سوال می دهد: آیا X به صورت ایستا تایپ می شود؟"، تعریف رئالیسم تا حدی ذهنی است.

در این سخنرانی، مطمئن خواهیم شد که نمادی که پیشنهاد می کنیم واقع بینانه است.

بدبینی

تایپ ایستا طبیعتاً به یک سیاست «بدبینانه» منجر می شود. تلاشی برای تضمین آن همه محاسبات منجر به شکست نمی شود، رد می کند محاسباتی که می تواند بدون خطا به پایان برسد.

یک زبان منظم، غیر عینی و پاسکال مانند با انواع مختلف را در نظر بگیرید واقعیو عدد صحیح. هنگام توصیف n: INTEGER; r: واقعیاپراتور n:=rبه دلیل نقض قوانین رد خواهد شد. بنابراین، کامپایلر تمام عبارات زیر را رد می کند:


اگر به آنها اجازه اجرا بدهیم، خواهیم دید که [A] همیشه کار خواهد کرد، زیرا هر سیستم عددی نمایش دقیقی از عدد واقعی 0.0 دارد که به طور واضح به 0 اعداد صحیح ترجمه می شود. [B] نیز تقریباً مطمئناً کار خواهد کرد. نتیجه عمل [C] واضح نیست (آیا می خواهیم با گرد کردن یا دور انداختن قسمت کسری به نتیجه برسیم؟). [D] کار را انجام خواهد داد، همانطور که اپراتور انجام می دهد:


ifn^2< 0 then n:= 3.67 end [E]

که شامل یک تکلیف غیرقابل دسترس است ( n^2مربع عدد است n). بعد از تعویض n^2بر nفقط یک سری از اجراها نتیجه صحیح را می دهد. وظیفه nیک مقدار واقعی بزرگ که نمی تواند به عنوان یک عدد صحیح نمایش داده شود منجر به شکست می شود.

در زبان‌های تایپ‌شده، همه این مثال‌ها (در حال کار کردن، کار نکردن، گاهی اوقات کار می‌کنند) بی‌رحمانه به عنوان نقض قوانین توصیف انواع تلقی می‌شوند و توسط هر کامپایلری رد می‌شوند.

سوال این نیست ما خواهیم کردچه بدبین باشیم و چه در واقع، چقدرما می توانیم بدبین باشیم. بازگشت به شرط واقع‌گرایی: اگر قواعد نوع آنقدر بدبینانه باشد که از نوشتن آسان محاسبات جلوگیری کند، آنها را رد می‌کنیم. اما اگر دستیابی به ایمنی نوع با کاهش اندک قدرت بیان حاصل شود، آنها را می پذیریم. به عنوان مثال، در یک محیط توسعه که توابعی را برای گرد کردن و استخراج یک قسمت صحیح فراهم می کند - گردو کوتاه کردن، اپراتور n:=rنادرست در نظر گرفته شده است، درست است، زیرا شما را مجبور می کند به جای استفاده از تبدیل های پیش فرض مبهم، تبدیل واقعی به عدد صحیح را به صراحت بنویسید.

تایپ استاتیک: چگونه و چرا

در حالی که مزایای تایپ استاتیک آشکار است، خوب است که دوباره در مورد آنها صحبت کنیم.

مزایای

دلایل استفاده از تایپ استاتیک در فناوری شی را در ابتدای سخنرانی ذکر کردیم. این قابلیت اطمینان، سهولت درک و کارایی است.

قابلیت اطمینانبه دلیل تشخیص خطاهایی که در غیر این صورت فقط در حین کار و فقط در برخی موارد می توانند خود را نشان دهند. اولین مورد از قوانین، که موجودیت ها را مجبور می کند و همچنین توابع را اعلام کنند، افزونگی را در متن برنامه معرفی می کند، که به کامپایلر اجازه می دهد تا با استفاده از دو قانون دیگر، ناسازگاری بین استفاده مورد نظر و واقعی موجودیت ها، مؤلفه ها و اصطلاحات.

تشخیص زودهنگام خطاها نیز مهم است زیرا هرچه بیشتر در یافتن آنها تاخیر داشته باشیم، هزینه تصحیح بیشتر خواهد شد. این ویژگی که به طور مستقیم برای همه برنامه نویسان حرفه ای قابل درک است، از نظر کمی توسط آثار شناخته شده بوهم تأیید شده است. وابستگی هزینه اصلاح به زمان یافتن خطاها در نموداری که بر اساس تعدادی از پروژه های صنعتی بزرگ و آزمایش های انجام شده با یک پروژه کوچک کنترل شده ساخته شده است نشان داده شده است:

برنج. 17.1.هزینه های مقایسه ای رفع اشکال (منتشر شده با اجازه)

خوانایییا سهولت درک(خوانایی) مزایای خود را دارد. در تمام مثال‌های این کتاب، ظاهر یک نوع بر روی یک موجودیت، اطلاعاتی در مورد هدف آن به خواننده می‌دهد. خوانایی در مرحله نگهداری بسیار مهم است.

سرانجام، بهره وریمی تواند موفقیت یا شکست فناوری شی را در عمل تعیین کند. در صورت عدم وجود تایپ ایستا در هنگام اجرا x.f(arg)هر مقدار زمان می تواند طول بکشد. دلیل این امر این است که در زمان اجرا، پیدا نشدن fدر کلاس هدف پایه ایکس، جستجو در فرزندانش ادامه خواهد داشت و این راهی مطمئن به سوی ناکارآمدی است. می توانید با بهبود جستجوی یک جزء در سلسله مراتب مشکل را کاهش دهید. نویسندگان زبان خود کارت عالی بود، در تلاش برای تولید کد بهتر برای یک زبان تایپ شده پویا. اما تایپ ایستا بود که به چنین محصول OO اجازه می داد با نرم افزارهای سنتی کارآیی داشته باشد.

کلید تایپ استاتیک این ایده است که قبلاً بیان شده است که کامپایلری کد را برای ساختار تولید می کند x.f(arg)، نوع را می شناسد ایکس. به دلیل چندشکلی، نمی توان به طور منحصر به فرد نسخه مناسب جزء را تعیین کرد f. اما اعلان مجموعه ای از انواع ممکن را محدود می کند و به کامپایلر اجازه می دهد تا جدولی بسازد که دسترسی به صحیح را فراهم کند. fبا حداقل هزینه، با ثابت محدوددشواری دسترسی بهینه سازی های اضافی انجام شده است اتصال استاتیکو جایگزینی (در خطی)- همچنین با تایپ ایستا تسهیل می شود و در صورت لزوم به طور کامل سربار را حذف می کند.

آرگومان هایی برای تایپ پویا

با وجود همه اینها، تایپ پویا طرفداران خود را از دست نمی دهد، به ویژه در میان برنامه نویسان Smalltalk. استدلال‌های آنها اساساً مبتنی بر واقع‌گرایی است که در بالا مورد بحث قرار گرفت. آنها بر این باورند که تایپ ایستا بیش از حد محدود کننده است و آنها را از بیان آزادانه ایده های خلاقانه خود باز می دارد و گاهی آن را "کمربند عفت" می نامند.

می توان با این استدلال موافق بود، اما فقط برای زبان های تایپ ایستا که تعدادی از ویژگی ها را پشتیبانی نمی کنند. شایان ذکر است که تمام مفاهیم مرتبط با مفهوم نوع و معرفی شده در سخنرانی های قبلی ضروری است - رد هر یک از آنها مملو از محدودیت های جدی است و معرفی آنها برعکس به اعمال ما انعطاف می بخشد و می دهد. ما این فرصت را داریم که به طور کامل از عملی بودن یک تایپ ثابت لذت ببریم.

تیپ سازی: مولفه های موفقیت

مکانیسم های تایپ استاتیک واقعی چیست؟ همه آنها در سخنرانی های قبلی معرفی شدند و بنابراین تنها یادآوری مختصری از آنها باقی مانده است. شمارش مشترک آنها انسجام و قدرت انجمن آنها را نشان می دهد.

سیستم نوع ما کاملاً بر اساس مفهوم است کلاس. کلاس ها حتی از انواع ابتدایی هستند عدد صحیحو بنابراین، برای توصیف انواع از پیش تعریف شده نیازی به قوانین خاصی نداریم. (این جایی است که نماد ما با زبان‌های ترکیبی مانند Object Pascal، Java و C++ متفاوت است، جایی که سیستم نوع زبان‌های قدیمی با فناوری شی مبتنی بر کلاس ترکیب می‌شود.)

انواع گسترش یافتهبا اجازه دادن به انواعی که مقادیر آنها نشان دهنده اشیاء هستند و همچنین انواعی که مقادیر آنها نشان دهنده مراجع هستند، انعطاف بیشتری به ما می دهد.

کلمه تعیین کننده در ایجاد یک سیستم نوع انعطاف پذیر متعلق به وراثتو مفهوم مرتبط سازگاری. این بر محدودیت اصلی زبان‌های تایپ کلاسیک، به عنوان مثال، پاسکال و آدا، که در آن اپراتور x: = yمستلزم آن است که نوع ایکسو yهمینطور بود این قانون بسیار سختگیرانه است: استفاده از موجوداتی را که می توانند اشیایی از انواع مرتبط را نشان دهند ممنوع می کند. حساب پس اندازو CHECKING_ACCOUNT). در وراثت، ما فقط به سازگاری نوع نیاز داریم yبا نوع ایکس، مثلا، ایکسنوع دارد حساب، y- حساب پس انداز، و طبقه دوم جانشین اولی است.

در عمل، یک زبان تایپ ایستا نیاز به پشتیبانی دارد ارث چندگانه. اتهامات اساسی شناخته شده در مورد تایپ ایستا که فرصت تفسیر اشیاء را به روش های مختلف نمی دهد. بله، شی سند(سند) را می توان از طریق شبکه منتقل کرد و بنابراین نیاز به وجود اجزای مرتبط با نوع دارد پیام(پیام). اما این انتقاد فقط برای زبان هایی صادق است که به وراثت مفرد محدود می شوند.

برنج. 17.2.ارث چندگانه

تطبیق پذیریلازم است، برای مثال، برای توصیف ساختارهای داده کانتینری انعطاف پذیر اما ایمن (به عنوان مثال فهرست کلاس[G]...). بدون این مکانیسم، تایپ استاتیک نیاز به اعلام کلاس های مختلف برای لیست هایی با انواع عناصر مختلف دارد.

در برخی موارد، تطبیق پذیری لازم است محدود کردن، که به شما امکان می دهد از عملیاتی استفاده کنید که فقط برای موجودیت های یک نوع عمومی اعمال می شود. اگر کلاس عمومی SORTABLE_LISTمرتب‌سازی را پشتیبانی می‌کند، به موجودیت‌هایی از نوع نیاز دارد جی، جایی که جی- یک پارامتر عمومی، وجود یک عملیات مقایسه. این از طریق پیوند به دست می آید جیکلاسی که محدودیت عمومی را تعریف می کند - قابل مقایسه:


کلاس SORTABLE_LIST ...

هر پارامتر عمومی واقعی SORTABLE_LISTباید بچه یک کلاس باشد قابل مقایسه، که دارای جزء مورد نیاز است.

مکانیسم ضروری دیگر این است تلاش برای تعیین تکلیف- دسترسی به اشیایی را که نرم افزار کنترل نمی کند، سازماندهی می کند. اگر yیک شی پایگاه داده یا یک شی است که از طریق شبکه به دست می آید، سپس عبارت x ?= yاختصاص دهد ایکسمعنی y، اگر yاز نوع سازگار است یا اگر نباشد می دهد ایکسمعنی خالی.

بیانیه، که به عنوان بخشی از ایده Design by Contract با کلاس ها و اجزای آنها در قالب پیش شرط ها، پس شرط ها و متغیرهای کلاس مرتبط است، توصیف محدودیت های معنایی را که توسط مشخصات نوع پوشش داده نمی شوند، ممکن می سازد. زبان‌هایی مانند پاسکال و آدا دارای انواع محدوده هستند که می‌توانند مقدار یک موجودیت را بین 10 تا 20، مثلاً محدود کنند، اما شما نمی‌توانید از آن‌ها برای تحمیل مقدار استفاده کنید. منمنفی بود، همیشه دو برابر j. ثابت‌های کلاس به کمک می‌آیند، که به گونه‌ای طراحی شده‌اند که محدودیت‌های معرفی‌شده را به دقت منعکس کنند، مهم نیست که چقدر پیچیده هستند.

تبلیغات پین شدهبرای جلوگیری از تکرار کد در عمل مورد نیاز است. اعلام می کند y: مانند x، شما تضمین می کنید که yپس از هر گونه اعلامیه مکرر نوع تغییر خواهد کرد ایکسدر یک نسل در غیاب این مکانیسم، توسعه‌دهندگان مرتباً دوباره اعلام می‌کنند و سعی می‌کنند انواع مختلف را ثابت نگه دارند.

اعلان های چسبنده یک مورد خاص از آخرین مکانیسم زبانی است که ما به آن نیاز داریم - کوواریانس، که بعداً به تفصیل مورد بحث قرار خواهد گرفت.

هنگام توسعه سیستم های نرم افزاریدر واقع، یک ویژگی دیگر مورد نیاز است که ذاتی در خود محیط توسعه است - کامپایل مجدد تدریجی سریع. وقتی سیستمی را می نویسید یا تغییر می دهید، می خواهید تأثیر تغییرات را در اسرع وقت ببینید. با تایپ استاتیک، باید به کامپایلر زمان بدهید تا انواع را دوباره بررسی کند. روال های کامپایل سنتی مستلزم کامپایل مجدد کل سیستم (و مجلس او، و این روند می تواند به طرز دردناکی طولانی باشد، به خصوص با انتقال به سیستم های مقیاس بزرگ. این پدیده به بحثی به نفع تبدیل شده است تفسیر کردنسیستم‌هایی مانند محیط‌های اولیه Lisp یا Smalltalk که سیستم را بدون پردازش یا بدون بررسی نوع اجرا می‌کردند. حالا این بحث فراموش شده است. یک کامپایلر مدرن خوب تشخیص می دهد که کد از آخرین کامپایل چگونه تغییر کرده است و فقط تغییراتی را که پیدا می کند پردازش می کند.

"آیا نوزاد تیپ شده است"؟

هدف ما - سخت گیرانهتایپ استاتیک به همین دلیل است که ما باید از هرگونه خلاء در «بازی با قوانین» خودداری کنیم، حداقل در صورت وجود آنها را به دقت شناسایی کنیم.

رایج ترین نقطه ضعف در زبان های تایپ ایستا وجود تبدیل هایی است که نوع یک موجودیت را تغییر می دهد. در زبان C و مشتقات آن به آنها «نوع ریخته گری» یا ریخته گری می گویند. در حال ضبط (OTHER_TYPE) xنشان می دهد که مقدار ایکستوسط کامپایلر به عنوان دارای نوع درک می شود OTHER_TYPE، مشروط به محدودیت های خاصی در انواع ممکن است.

مکانیسم‌هایی مانند این محدودیت‌های بررسی نوع را دور می‌زنند. Casting در برنامه نویسی C، از جمله گویش ANSI C رایج است. حتی در C++، نوع ریخته گری، در حالی که کمتر رایج است، رایج و شاید ضروری باقی می ماند.

رعایت قوانین تایپ استاتیک چندان آسان نیست، اگر در هر زمان بتوان آنها را با ریخته گری دور زد.

تایپ و لینک دادن

اگرچه به عنوان خواننده این کتاب، مطمئناً تایپ استاتیک را از استاتیک تشخیص خواهید داد الزام آورخوب، افرادی هستند که نمی توانند این کار را انجام دهند. این ممکن است تا حدی به دلیل تأثیر زبان اسمال تاک باشد که از رویکردی پویا برای هر دو مشکل حمایت می کند و می تواند منجر به این تصور غلط شود که آنها راه حل یکسانی دارند. (ما در این کتاب استدلال می کنیم که ترکیب تایپ استاتیک و پیوند پویا برای ایجاد برنامه های قوی و انعطاف پذیر مطلوب است.)

هر دو تایپ و صحافی با معناشناسی ساختار اصلی سروکار دارند x.f(arg)اما به دو سوال متفاوت پاسخ دهید:

تایپ و لینک دادن

[ایکس]. یک سوال در مورد تایپ: زمانی که باید مطمئن شویم که یک عملیات مربوط به fقابل اعمال برای شیء متصل به موجودیت ایکس(با پارامتر ارگ)?

[ایکس]. سوال پیوند دادن: چه زمانی باید بدانیم که یک تماس معین چه عملیاتی را آغاز می کند؟

تایپ کردن به سوال در دسترس بودن پاسخ می دهد حداقل یکیعملیات، binding مسئول انتخاب است لازم است.

در چارچوب رویکرد شی:

[ایکس].مشکل تایپ مربوط به پلی مورفیسم: چون ایکسدر زمان اجرامی تواند اشیاء از چندین نوع مختلف را نشان دهد، ما باید مطمئن باشیم که عملیات نشان دهنده f, در دسترسدر هر یک از این موارد؛

[ایکس].مشکل الزام آور ایجاد شده است اعلامیه های مکرر: از آنجایی که یک کلاس می تواند اجزای ارثی را تغییر دهد، ممکن است دو یا چند عملیات وجود داشته باشند که ادعا می کنند نشان می دهند fدر این تماس

هر دو مشکل را می توان هم به صورت پویا و هم به صورت استاتیک حل کرد. زبان های موجود هر چهار راه حل را ارائه می دهند.

[ایکس].تعدادی از زبان‌های غیر هدف، مانند پاسکال و آدا، هم تایپ ثابت و هم پیوند ثابت را پیاده‌سازی می‌کنند. هر موجودیت فقط اشیایی از یک نوع استاتیکی تعریف شده را نشان می دهد. این قابلیت اطمینان راه حل را تضمین می کند که قیمت آن انعطاف پذیری آن است.

[ایکس]. Smalltalk و سایر زبان های OO حاوی پیوندهای پویا و تایپ پویا هستند. اولویت به انعطاف پذیری به قیمت قابل اعتماد بودن زبان داده می شود.

[ایکس].برخی از زبان های غیر هدف از تایپ پویا و پیوند استاتیک پشتیبانی می کنند. از جمله آنها می توان به زبان های اسمبلی و تعدادی از زبان های برنامه نویسی اشاره کرد.

[ایکس].ایده‌های تایپ استاتیک و صحافی پویا در نماد پیشنهادی در این کتاب گنجانده شده‌اند.

به ویژگی زبان C ++ توجه کنید، که از تایپ استاتیک پشتیبانی می کند، اگرچه به دلیل وجود نوع ریخته گری، اتصال استاتیک (به طور پیش فرض)، اتصال پویا در حالت مجازی ( مجازی) تبلیغات

دلیل انتخاب تایپ استاتیک و صحافی پویا واضح است. سوال اول این است: "چه زمانی از وجود اجزا مطلع خواهیم شد؟" - یک پاسخ ثابت را پیشنهاد می کند: هر چه زودتر بهتر"، که به معنی: در زمان کامپایل. سوال دوم، "از کدام مؤلفه استفاده شود؟" یک پاسخ پویا را نشان می دهد: " یکی که شما نیاز دارید"، مطابق با نوع دینامیکی شی تعیین شده در زمان اجرا. این تنها راه حل قابل قبول است اگر اتصال ایستا و پویا نتایج متفاوتی را ایجاد کند.

مثال زیر از سلسله مراتب وراثت به روشن شدن این مفاهیم کمک می کند:

برنج. 17.3.انواع هواپیما

تماس را در نظر بگیرید:


my_aircraft.lower_landing_gear

یک سوال در مورد تایپ کردن: چه زمانی باید مطمئن شد که یک مؤلفه اینجا خواهد بود پایین_دستگاه_فرود("ارابه فرود آزاد")، قابل استفاده برای یک شی (برای COPTERاصلاً نخواهد بود) سؤال الزام آور: کدام یک از چندین نسخه ممکن را انتخاب کنید.

اتصال ایستا به این معنی است که نوع شی پیوست شده را نادیده می گیریم و به اعلان موجودیت تکیه می کنیم. در نتیجه، هنگام برخورد با یک بوئینگ 747-400، ما باید نسخه ای را بخواهیم که برای هواپیماهای معمولی سری 747 طراحی شده باشد، و نه برای اصلاح آنها 747-400. اتصال پویا عملیات مورد نیاز شی را اعمال می کند و این رویکرد صحیح است.

با تایپ ایستا، کامپایلر تماسی را رد نمی کند اگر بتوان تضمین کرد که در هنگام اجرای برنامه برای موجودیت my_aircraftشیء ارائه شده با جزء مربوطه متصل خواهد شد پایین_دستگاه_فرود. روش اصلی برای دریافت تضمین ساده است: با یک اظهارنامه اجباری my_aircraftکلاس پایه از نوع خود لازم است که چنین جزء را شامل شود. از این رو my_aircraftرا نمی توان به عنوان اعلام کرد هواپیما، از آنجایی که دومی ندارد پایین_دستگاه_فروددر این سطح؛ هلیکوپترها، حداقل در مثال ما، نمی دانند چگونه ارابه فرود را آزاد کنند. اگر یک موجودیت را به عنوان اعلام کنیم سطح، - کلاس حاوی مؤلفه مورد نیاز - همه چیز خوب خواهد بود.

تایپ پویا در سبک اسمال تاک مستلزم آن است که منتظر تماس باشید و در زمان اجرای آن، وجود مؤلفه مورد نظر را بررسی کنید. این رفتار برای نمونه‌های اولیه و پیشرفت‌های تجربی ممکن است، اما برای سیستم‌های صنعتی غیرقابل قبول است - در زمان پرواز خیلی دیر است که بپرسیم آیا ارابه فرود دارید یا خیر.

کوواریانس و پنهان شدن کودک

اگر دنیا ساده بود، بحث در مورد تایپ کردن می توانست به پایان برسد. ما اهداف و مزایای تایپ استاتیک را شناسایی کردیم، محدودیت‌هایی را که سیستم‌های نوع واقعی باید رعایت کنند، بررسی کردیم و تأیید کردیم که روش‌های تایپ پیشنهادی با معیارهای ما مطابقت دارند.

اما دنیا ساده نیست. ترکیب تایپ استاتیک با برخی از الزامات مهندسی نرم افزار، مشکلات پیچیده تری را نسبت به آنچه که به نظر می رسد ایجاد می کند. مشکلات ناشی از دو مکانیسم است: کوواریانس- تغییر انواع پارامتر هنگام تعریف مجدد، پنهان شدن نسل- توانایی یک کلاس نسل برای محدود کردن وضعیت صادرات اجزای ارثی.

کوواریانس

وقتی نوع آرگومان های یک جزء دوباره تعریف شود چه اتفاقی می افتد؟ این یک مشکل بزرگ است و ما قبلاً تعدادی از نمونه‌های آن را دیده‌ایم: دستگاه‌ها و چاپگرها، لیست‌های پیوندی منفرد و دوگانه و غیره (به بخش‌های 16.6، 16.7 مراجعه کنید).

در اینجا مثال دیگری برای کمک به روشن شدن ماهیت مشکل آورده شده است. و اگرچه دور از واقعیت و استعاره است، اما نزدیکی آن به طرح های برنامه ای آشکار است. علاوه بر این، هنگام تجزیه و تحلیل آن، اغلب به مشکلات ناشی از تمرین باز می گردیم.

تصور کنید یک تیم اسکی دانشگاه برای مسابقات قهرمانی آماده می شود. کلاس دخترشامل اسکی بازانی که عضوی از تیم بانوان هستند، پسر- اسکی بازان تعدادی از شرکت کنندگان هر دو تیم رتبه بندی شده اند که در مسابقات قبلی نتایج خوبی از خود نشان داده اند. این برای آنها مهم است، زیرا اکنون آنها ابتدا می دوند و نسبت به دیگران برتری پیدا می کنند. (این قانون، که به افراد ممتاز از قبل امتیاز می دهد، شاید همان چیزی است که اسلالوم و اسکی صحرایی را در نظر بسیاری از مردم جذاب می کند و استعاره خوبی از خود زندگی است.) بنابراین ما دو کلاس جدید داریم: RANKED_GIRLو RANKED_BOY.

برنج. 17.4.طبقه بندی اسکی بازان

تعدادی اتاق برای اسکان ورزشکاران رزرو شده است: فقط برای مردان، فقط برای دختران، فقط برای برندگان زن. برای نمایش این، از سلسله مراتب کلاس موازی استفاده می کنیم: اتاق, GIRL_ROOMو RANKED_GIRL_ROOM.

اینم طرح کلاس اسکی باز:


- هم اتاقی.
... سایر اجزای احتمالی حذف شده در این کلاس و کلاس های بعدی ...

ما به دو جزء علاقه مندیم: ویژگی هم اتاقیو رویه اشتراک گذاری، که این اسکی باز را با اسکی باز فعلی در یک اتاق قرار می دهد:


هنگام اعلام یک موجودیت دیگرمی توانید نوع را رها کنید اسکی بازبه نفع نوع ثابت مثل هم اتاقی(یا مانند فعلیبرای هم اتاقیو دیگرهمزمان). اما بیایید برای لحظه ای پین کردن تایپ را فراموش کنیم (بعداً به آنها خواهیم پرداخت) و مشکل کوواریانس را در شکل اصلی آن بررسی کنیم.

چگونه overriding نوع را معرفی کنیم؟ قوانین مستلزم اقامت جداگانه پسران و دختران، برندگان و سایر شرکت کنندگان است. برای حل این مشکل، هنگام تعریف مجدد، نوع کامپوننت را تغییر می دهیم هم اتاقی، همانطور که در زیر نشان داده شده است (از این پس، عناصر نادیده گرفته شده زیر خط کشیده می شوند).


- هم اتاقی.

به ترتیب، استدلال رویه را دوباره تعریف کنید اشتراک گذاری. یک نسخه کامل تر از کلاس اکنون به شکل زیر است:


- هم اتاقی.
- دیگری را به عنوان همسایه انتخاب کنید.

به طور مشابه، شما باید همه تولید شده از را تغییر دهید اسکی بازکلاس ها (ما در حال حاضر از نوع fixing استفاده نمی کنیم). در نتیجه، ما یک سلسله مراتب داریم:

برنج. 17.5.سلسله مراتب اعضا و تعریف مجدد

از آنجایی که وراثت یک تخصص است، قوانین نوع ایجاب می کند که هنگام نادیده گرفتن نتیجه یک جزء، در این مورد هم اتاقی، نوع جدید فرزند اصلی بود. همین امر در مورد بازتعریف نوع استدلال نیز صدق می کند. دیگرکارهای روزمره اشتراک گذاری. این استراتژی همانطور که می دانیم کوواریانس نامیده می شود که پیشوند "ko" نشان دهنده تغییر مشترک در انواع پارامتر و نتیجه است. استراتژی مخالف نامیده می شود مغایرت.

همه مثال‌های ما به طور قانع‌کننده‌ای ضرورت عملی کوواریانس را نشان می‌دهند.

[ایکس].عنصر لیست به صورت منفرد قابل پیوندباید با یک عنصر مشابه دیگر و نمونه مرتبط باشد BI_LINKقابل- با یک مشابه Covariant باید نادیده گرفته شود و استدلال وارد شود put_right.

[ایکس].هر زیربرنامه وارد LINKED_LISTبا آرگومان نوع قابل پیوندهنگام حرکت به TWO_WAY_LISTنیاز به استدلال خواهد داشت BI_LINKقابل.

[ایکس].روش set_alternateمی پذیرد DEVICE- استدلال در کلاس DEVICEو چاپگر-برهان - در کلاس چاپگر.

تعریف مجدد کوواریانس به ویژه محبوب است زیرا پنهان کردن اطلاعات منجر به ایجاد رویه های فرم می شود


- ویژگی را روی v تنظیم کنید.

برای کار با صفتنوع SOME_TYPE. البته چنین رویه‌هایی کوواریانت هستند، زیرا هر کلاسی که نوع یک ویژگی را تغییر می‌دهد باید آرگومان را بر این اساس دوباره تعریف کند. set_attrib. اگرچه نمونه های ارائه شده در یک طرح قرار می گیرند، کوواریانس بسیار گسترده تر است. برای مثال، رویه یا تابعی را در نظر بگیرید که الحاق لیست‌های پیوندی منفرد را انجام می‌دهد ( LINKED_LIST). آرگومان آن باید دوباره به عنوان یک لیست پیوندی دوگانه تعریف شود ( TWO_WAY_LIST). عملیات افزودن جهانی پسوند "+"می پذیرد NUMERIC- استدلال در کلاس NUMERIC, واقعی- در کلاس واقعیو عدد صحیح- در کلاس عدد صحیح. در سلسله مراتب خدمات تلفنی موازی، رویه شروع کنیددر کلاس PHONE_SERVICEممکن است استدلال مورد نیاز باشد نشانی، نشان دهنده آدرس مشترک، (برای صورتحساب)، در حالی که همان رویه در کلاس است CORPORATE_SERVICEنوع آرگومان مورد نیاز CORPORATE_ADDRESS.

برنج. 17.6.خدمات ارتباطی

در مورد راه حل متضاد چه می توان گفت؟ در مثال اسکی باز، به این معنی است که اگر، عبور به کلاس RANKED_GIRL، نوع نتیجه هم اتاقیبازتعریف شده به عنوان RANKED_GIRL، سپس به دلیل تناقض، نوع استدلال اشتراک گذاریمی توان برای تایپ دوباره تعریف کرد دختریا اسکی باز. تنها نوعی که تحت راه حل متضاد مجاز نیست RANKED_GIRL! آنقدر که بدترین سوء ظن را در والدین دختران برانگیزد.

سلسله مراتب موازی

برای اینکه سنگ تمام نگذارید، یک نمونه از نمونه را در نظر بگیرید اسکی بازبا دو سلسله مراتب موازی این به ما امکان می دهد موقعیتی را شبیه سازی کنیم که قبلاً در عمل با آن مواجه شده است: TWO_WAY_LIST > LINKED_LISTو BI_LINKABLE > LINKABLE; یا سلسله مراتب با خدمات تلفن PHONE_SERVICE.

بگذارید یک سلسله مراتب با یک کلاس وجود داشته باشد اتاق، که نسل آن است GIRL_ROOM(کلاس پسرحذف شده):

برنج. 17.7.اسکی بازان و اتاق ها

کلاس های اسکی باز ما در این سلسله مراتب موازی به جای هم اتاقیو اشتراک گذاریاجزای مشابهی خواهد داشت محل اقامت (محل اقامت) و تطبیق (محل):


توضیحات: "نوع جدید با سلسله مراتب موازی"
accommodate (r: ROOM) is ... نیازمند ... انجام

نادیده گرفتن کوواریانس در اینجا نیز مورد نیاز است: در کلاس دختر 1چگونه محل اقامتو آرگومان زیر روال تطبیقباید با نوع جایگزین شود GIRL_ROOM، در کلاس پسر 1- نوع BOY_ROOMو غیره. (به یاد داشته باشید، ما هنوز بدون سنجاق تایپ کار می کنیم.) مانند نسخه قبلی مثال، تضاد در اینجا بی فایده است.

بیراهه بودن چندشکلی

آیا نمونه های کافی برای تایید عملی بودن کوواریانس وجود ندارد؟ چرا کسی می تواند مغایرتی را در نظر بگیرد که با آنچه در عمل مورد نیاز است (به غیر از رفتار برخی از جوانان) در تضاد باشد؟ برای درک این موضوع، مشکلاتی را که هنگام ترکیب چندشکلی و استراتژی کوواریانس ایجاد می‌شود، در نظر بگیرید. ایجاد یک طرح خرابکاری دشوار نیست و ممکن است خودتان آن را ایجاد کرده باشید:


ایجاد b; ایجاد g;-- ایجاد اشیاء BOY و GIRL.

نتیجه آخرین تماس، که احتمالاً برای جوانان خوشایند بود، دقیقاً همان چیزی است که ما سعی می‌کردیم با نادیده گرفتن نوع از آن جلوگیری کنیم. صدا زدن اشتراک گذاریمنجر به این واقعیت می شود که شی پسر، معروف به بو به لطف چندشکلی یک نام مستعار دریافت کرد سنوع اسکی باز، همسایه شی می شود دخترمعروف به نام g. با این حال، تماس، اگرچه برخلاف قوانین هاستل است، اما در متن برنامه کاملاً صحیح است، زیرا اشتراک گذاریجزء صادراتی در ترکیب اسکی باز، آ دختر، نوع آرگومان g، سازگار با اسکی باز، نوع پارامتر رسمی اشتراک گذاری.

طرح سلسله مراتب موازی به همین سادگی است: جایگزین کنید اسکی بازبر اسکی 1، چالش اشتراک گذاری- در دسترس s.accommodate(gr)، جایی که گرم- نوع موجودیت GIRL_ROOM. نتیجه یکسان است.

با یک راه حل متضاد برای این مشکلات، وجود نخواهد داشت: تخصصی شدن هدف تماس (در مثال ما س) به تعمیم استدلال نیاز دارد. در نتیجه، تضاد منجر به یک مدل ریاضی ساده‌تر از مکانیسم می‌شود: وراثت - تعریف مجدد - چندشکلی. این واقعیت در تعدادی از مقالات نظری که این استراتژی را پیشنهاد می کنند، توضیح داده شده است. این استدلال چندان قانع کننده نیست، زیرا، همانطور که نمونه های ما و سایر نشریات نشان می دهد، تناقض وجود ندارد. استفاده عملی.

بنابراین، بدون تلاش برای کشیدن لباس متناقض بر تن کوواریانس، باید واقعیت کوواریانس را پذیرفت و به دنبال راه هایی برای از بین بردن آن بود. اثر ناخواسته.

پنهان شدن توسط نسل

قبل از جستجوی راه حلی برای مشکل کوواریانس، مکانیسم دیگری را در نظر بگیرید که در شرایط چندشکلی می تواند منجر به نقض نوع شود. پنهان کردن نسل توانایی یک کلاس برای صادر نکردن جزء دریافتی از والدین خود است.

برنج. 17.8.پنهان شدن توسط نسل

یک مثال معمولی جزء است add_vertex(افزودن راس) صادر شده توسط کلاس چند ضلعی، اما توسط نسل خود پنهان شده است مستطیل(با توجه به نقض احتمالی متغیر ثابت - کلاس می خواهد مستطیل باقی بماند):


مثال غیر برنامه نویسی: کلاس "Ostrich" روش "Fly" را که از والد "Bird" دریافت شده را پنهان می کند.

بیایید این طرح را برای لحظه ای در نظر بگیریم و بپرسیم که آیا ترکیبی از ارث و پنهان کاری مشروع خواهد بود؟ نقش مدل سازی پنهان کردن، مانند کوواریانس، با ترفندهایی که به دلیل چندشکلی امکان پذیر است، نقض می شود. و در اینجا ساختن یک مثال مخرب که اجازه می دهد علیرغم پنهان شدن مؤلفه، آن را فراخوانی کنید و یک راس به مستطیل اضافه کنید دشوار نیست:


خالق؛ - یک شیء مستطیل ایجاد کنید.
p:=r; -- تخصیص چند شکلی.

از آنجایی که شی rپنهان شدن در زیر ذات پکلاس چند ضلعی، آ add_vertexجزء صادر شده چند ضلعی، سپس فراخوانی آن توسط نهاد پدرست. در نتیجه اجرا، یک راس دیگر در مستطیل ظاهر می شود، به این معنی که یک شی نامعتبر ایجاد می شود.

صحت سیستم ها و کلاس ها

برای بحث در مورد مشکلات کوواریانس و اختفای نسل، به برخی اصطلاحات جدید نیاز داریم. تماس خواهیم گرفت کلاس صحیح (کلاس معتبر)سیستمی که سه قانون برای توصیف انواع ارائه شده در ابتدای سخنرانی را برآورده می کند. آنها را به یاد بیاورید: هر موجودیت نوع خاص خود را دارد. نوع استدلال واقعی باید با نوع استدلال رسمی سازگار باشد، وضعیت مشابه با انتساب است. جزء فراخوانی شده باید در کلاس خود اعلان شود و به کلاس حاوی فراخوانی صادر شود.

سیستم نامیده می شود سیستم صحیح (سیستم معتبر)، در صورتی که در حین اجرای آن تخلف نوع رخ ندهد.

در حالت ایده آل، هر دو مفهوم باید مطابقت داشته باشند. با این حال، قبلاً دیده‌ایم که یک سیستم طبقاتی صحیح تحت شرایط وراثت، کوواریانس و پنهان شدن توسط یک نسل ممکن است از نظر سیستم صحیح نباشد. بیایید این خطا را نام ببریم خطای اعتبار سیستم.

جنبه عملی

سادگی مسئله نوعی پارادوکس ایجاد می‌کند: یک مبتدی کنجکاو می‌تواند در عرض چند دقیقه یک مثال متقابل بسازد، در عمل واقعی خطاهای صحت کلاس سیستم‌ها هر روز رخ می‌دهد، اما نقض صحت سیستم، حتی در موارد بزرگ و چندگانه. پروژه های سال، بسیار نادر رخ می دهد.

با این حال، این اجازه نمی دهد که آنها را نادیده بگیریم، و بنابراین ما شروع به مطالعه سه راه ممکن برای حل این مشکل می کنیم.

در مرحله بعد، جنبه های بسیار ظریف و نه چندان آشکار رویکرد شی را لمس خواهیم کرد. اگر برای اولین بار است که این کتاب را می خوانید، ممکن است بخواهید از بخش های باقی مانده این سخنرانی صرف نظر کنید. اگر با فناوری OO تازه کار هستید، پس از مطالعه سخنرانی های 1-11 دوره "مبانی طراحی شی گرا"، که به روش شناسی وراثت اختصاص داده شده است، این مطالب را بهتر درک خواهید کرد، و به ویژه سخنرانی 6 دوره "مبانی" طراحی شی گرا» که به وراثت روش شناسی اختصاص دارد.

صحت سیستم ها: تقریب اول

بیایید ابتدا روی مسئله کوواریانس تمرکز کنیم که مهمتر از این دو است. ادبیات گسترده ای به این موضوع اختصاص داده شده است که راه حل های مختلفی را ارائه می دهد.

تضاد و عدم تغییر

Contravariance مشکلات نظری مرتبط با نقض صحت سیستم را از بین می برد. با این حال، این امر واقع گرایی نوع سیستم را از دست می دهد، به همین دلیل، نیازی به بررسی بیشتر این رویکرد نیست.

اصالت زبان C++ این است که از استراتژی استفاده می کند نوگرایی، از تغییر نوع آرگومان ها در زیر روال های لغو شده جلوگیری می کند! اگر C++ یک زبان با تایپ قوی بود، استفاده از سیستم نوع آن دشوار بود. ساده ترین راه حل برای مشکل در این زبان و همچنین دور زدن سایر محدودیت های C ++ (مثلاً عدم جهانی بودن محدود)، استفاده از ریخته گری - نوع ریخته گری است که به شما امکان می دهد مکانیسم تایپ موجود را کاملاً نادیده بگیرید. این راه حل جذاب به نظر نمی رسد. با این حال، توجه داشته باشید که تعدادی از پیشنهادات مورد بحث در زیر بر عدم تنوع تکیه می‌کنند، که معنای آن با معرفی مکانیسم‌های جدید برای کار با انواع به‌جای تعریف مجدد کوواریانت داده می‌شود.

استفاده از پارامترهای عمومی

جهانی بودن در قلب ایده جالبی است که برای اولین بار توسط فرانتس وبر ارائه شد. بیا کلاس اعلام کنیم اسکی 1، ژنریک سازی پارامترهای عمومی را به کلاس محدود می کند اتاق:


ویژگی کلاس SKIER1
accommodate (r: G) is ... نیازمند ... انجام اقامت:= r end

بعد کلاس دختر 1وارث خواهد بود اسکی 1و غیره. همین تکنیک، هر چند در نگاه اول عجیب به نظر برسد، در غیاب سلسله مراتب موازی قابل استفاده است: کلاس SKIER.

این رویکرد مشکل کوواریانس را حل می کند. هر گونه استفاده از کلاس باید پارامتر عمومی واقعی را مشخص کند اتاقیا GIRL_ROOM، به طوری که ترکیب اشتباه به سادگی غیر ممکن می شود. زبان بدون متغیر می شود و سیستم به دلیل پارامترهای عمومی نیازهای کوواریانس را به طور کامل برآورده می کند.

متأسفانه، این تکنیک به عنوان یک راه حل کلی غیرقابل قبول است، زیرا منجر به فهرست رو به رشدی از پارامترهای عمومی، یکی برای هر نوع استدلال کوواریانس ممکن می شود. بدتر از اون، افزودن یک زیربرنامه کوواریانت با آرگومانی که نوع آن در لیست نیست، نیاز به افزودن یک پارامتر کلاس عمومی دارد، و بنابراین، رابط کلاس را تغییر می دهد، تغییراتی را برای همه مشتریان کلاس ایجاد می کند، که غیرقابل قبول است.

متغیرها را تایپ کنید

تعدادی از نویسندگان، از جمله کیم بروس، دیوید شانگ و تونی سیمونز، راه حلی بر اساس متغیرهای نوع ارائه کرده اند که مقادیر آن ها نوع هستند. ایده آنها ساده است:

[ایکس].به جای نادیده گرفتن کوواریانت، اعلان‌های نوع را مجاز کنید که از متغیرهای نوع استفاده می‌کنند.

[ایکس].قوانین سازگاری نوع را برای مدیریت چنین متغیرهایی گسترش دهید.

[ایکس].امکان اختصاص متغیرهای نوع به عنوان مقادیر به انواع زبان را فراهم می کند.

خوانندگان می توانند ارائه مفصلی از این ایده ها را در تعدادی از مقالات در مورد این موضوع و همچنین در انتشارات Cardelli (Cardelli)، Castagna (Castagna)، وبر (Weber) و دیگران بیابند. ما با این مشکل برخورد نخواهیم کرد، و در اینجا دلیل آن است.

[ایکس].مکانیزم متغیر نوع به درستی در دسته بندی قرار می گیرد که اجازه می دهد یک نوع بدون مشخص کردن کامل آن استفاده شود. همین دسته شامل تطبیق پذیری و تثبیت تبلیغات می شود. این مکانیسم می تواند جایگزین مکانیسم های دیگر در این دسته شود. در ابتدا، این را می توان به نفع متغیرهای نوع تفسیر کرد، اما نتیجه می تواند فاجعه آمیز باشد، زیرا مشخص نیست که آیا این مکانیسم جامع می تواند همه وظایف را با سهولت و سادگی که در ذاتی سنجاق عمومی و نوع است انجام دهد یا خیر.

[ایکس].فرض کنید که یک مکانیسم متغیر نوع ایجاد شده است که می تواند بر مشکلات ترکیب کوواریانس و چندشکلی غلبه کند (هنوز مشکل پنهان شدن نسل را نادیده می گیرد). سپس توسعه دهنده کلاس نیاز خواهد داشت شهود فوق العادهتا از قبل تصمیم بگیرید که کدام یک از مؤلفه‌ها برای تعریف مجدد انواع در کلاس‌های مشتق شده در دسترس هستند و کدام‌ها نه. در زیر ما این مشکل را مورد بحث قرار خواهیم داد که در عمل ایجاد برنامه ها اتفاق می افتد و افسوس که در مورد کاربرد بسیاری از طرح های نظری تردید ایجاد می کند.

این ما را وادار می کند که به مکانیسم هایی که قبلاً در نظر گرفته شده است بازگردیم: جهانی بودن محدود و نامحدود، سنجاق نوع و البته وراثت.

با تکیه بر سنجاق نوع

ما یک راه حل تقریباً آماده برای مشکل کوواریانس را با نگاه کردن به مکانیسم اعلامیه های پین شده که برای ما شناخته شده است، پیدا خواهیم کرد.

هنگام تشریح کلاس ها اسکی بازو اسکی 1با استفاده از اعلان های ثابت برای خلاص شدن از شر بسیاری از تعریف های مجدد، نمی توانید از شما بازدید کند. سنجاق کردن یک مکانیسم کوواریانس معمولی است. مثال ما به این صورت است (همه تغییرات زیر خط کشیده شده اند):


سهم (سایر: مانند جاری) است ... نیاز ... انجام
جای دادن (ر: مانند اسکان) است ... نیازمند ... انجام دادن

حالا بچه ها می توانند کلاس را ترک کنند اسکی بازبدون تغییر، و اسکی 1آنها فقط باید این ویژگی را لغو کنند محل اقامت. موجودیت های پین شده: ویژگی هم اتاقیو آرگومان های زیر روال اشتراک گذاریو تطبیق- به طور خودکار تغییر خواهد کرد. این کار را تا حد زیادی ساده می کند و این واقعیت را تأیید می کند که در صورت عدم وجود لنگر (یا مکانیزم مشابه دیگر مانند متغیرهای نوع)، نوشتن یک محصول OO با تایپ واقعی غیرممکن است.

اما آیا توانستید تخلفات صحت سیستم را برطرف کنید؟ نه! می‌توانیم، مانند قبل، با انجام تکالیف چند شکلی که باعث نقض صحت سیستم می‌شود، از بررسی نوع پیشی بگیریم.

درست است، نسخه های اصلی نمونه ها رد خواهند شد. اجازه دهید:


ایجاد b;ایجاد g;-- ایجاد اشیاء BOY و GIRL.
s:=b; -- تخصیص چند شکلی.

بحث و جدل gمنتقل شده است اشتراک گذاری، اکنون نادرست است زیرا به یک شی از نوع نیاز دارد دوست دارد، و کلاس دختربا این نوع سازگار نیست، زیرا طبق قانون انواع ثابت هیچ نوع با آن سازگار نیست دوست داردجز برای خودش

با این حال، ما برای مدت طولانی خوشحال نمی شویم. از طرفی این قاعده می گوید دوست داردسازگار با نوع س. بنابراین، با استفاده از چندشکلی نه تنها از جسم س، بلکه پارامتر g، می توانیم دوباره سیستم بررسی نوع را دور بزنیم:


s: SKYER; ب: پسر g: دوست دارد actual_g:GIRL;
ایجاد b; create actual_g -- ایجاد اشیاء BOY و GIRL.
s:= actual_g; g:= s -- g را از طریق s به GIRL اضافه کنید.
s:= b -- انتساب چند شکلی.

در نتیجه تماس غیرقانونی انجام می شود.

راهی برای خروج وجود دارد. اگر ما در مورد استفاده از پین کردن اعلان به عنوان تنها مکانیسم کوواریانس جدی هستیم، می‌توانیم با منع کامل چندشکلی موجودیت پین‌شده از شر نقض صحت سیستمی خلاص شویم. این نیاز به تغییر در زبان دارد: یک کلمه کلیدی جدید معرفی کنید لنگر(ما فقط برای استفاده از آن در این بحث به این ساختار فرضی نیاز داریم):


اجازه دهید اعلامیه هایی مانند دوست داردفقط زمانی که ستعریف شده به صورت لنگر. بیایید قوانین سازگاری را تغییر دهیم تا مطمئن شویم: سو عناصر نوع دوست داردفقط می توانند (در تکالیف یا انتقال استدلال) به یکدیگر پیوست شوند.

با این رویکرد، امکان بازتعریف نوع آرگومان های زیر روال را از زبان حذف می کنیم. علاوه بر این، می‌توانیم تعریف مجدد نوع نتیجه را ممنوع کنیم، اما این ضروری نیست. البته توانایی نادیده گرفتن نوع ویژگی حفظ می شود. همهتعریف مجدد انواع آرگومان اکنون به طور ضمنی از طریق مکانیسم سنجاق ایجاد شده توسط کوواریانس انجام خواهد شد. جایی که با رویکرد قبلی کلاس Dمولفه ارثی را به صورت زیر لغو کرد:


در حالی که کلاس سی- والدین Dبه نظر می رسید


جایی که Yمکاتبه کرد ایکس، اکنون کامپوننت را دوباره تعریف می کنیم rبه این صورت خواهد بود:


در کلاس درس باقی می ماند Dنادیده گرفتن نوع your_anchor.

این راه حل برای مشکل کوواریانس - چندشکلی رویکرد نامیده می شود لنگر انداختن. درست تر است که بگوییم: "کوواریانس فقط از طریق پینینگ". ویژگی های رویکرد جذاب هستند:

[ایکس].سنجاق کردن بر اساس ایده جداسازی دقیق است کوواریانتو عناصر بالقوه چند شکلی (یا به طور خلاصه چندشکلی). همه نهادها به عنوان لنگریا مانند some_anchorکوواریانت؛ بقیه چند شکلی هستند. در هر یک از دو دسته، هرگونه پیوست مجاز است، اما هیچ موجودیت یا عبارتی وجود ندارد که مرز را نقض کند. برای مثال نمی توانید یک منبع چند شکلی را به یک هدف کوواریانت اختصاص دهید.

[ایکس].این راه حل ساده و ظریف حتی برای مبتدیان نیز به راحتی قابل توضیح است.

[ایکس].این امکان را به طور کامل از بین می برد که درستی سیستم در سیستم های ساخته شده کوواریانس نقض شود.

[ایکس].چارچوب مفهومی ذکر شده در بالا، از جمله مفاهیم جهانی بودن محدود و نامحدود را حفظ می کند. (در نتیجه، این راه حل، به نظر من، ترجیح داده می شود تا متغیرهای نوع جایگزین مکانیسم های کوواریانس و جهان شمول، طراحی شده برای حل مسائل مختلف عملی.)

[ایکس].این نیاز به یک تغییر جزئی در زبان دارد - اضافه کردن یک کلمه کلیدی منعکس شده در قانون مطابقت - و هیچ مشکل قابل ملاحظه ای در اجرای آن ندارد.

[ایکس].این واقع بینانه است (حداقل در تئوری): هر سیستمی که قبلاً ممکن بود را می توان با جایگزینی بازنویسی های کوواریانت با اعلام مجدد لنگر بازنویسی کرد. درست است که برخی از پیوست‌ها در نتیجه نامعتبر خواهند بود، اما با مواردی مطابقت دارند که می‌تواند منجر به نقض نوع شود، بنابراین باید با تلاش‌های انتساب جایگزین شوند و در زمان اجرا با آنها برخورد شود.

به نظر می رسد که بحث می تواند به همین جا ختم شود. پس چرا رویکرد Anching کاملاً مطابق میل ما نیست؟ اولاً ما هنوز به موضوع اختفای کودک نپرداخته ایم. علاوه بر این، دلیل اصلی ادامه بحث، مشکلی است که قبلاً در ذکر مختصر متغیرهای نوع بیان شد. تقسیم حوزه های نفوذ به بخش چندشکل و کوواریانس تا حدودی شبیه به نتیجه کنفرانس یالتا است. فرض بر این است که طراح کلاس شهود فوق‌العاده‌ای دارد، که می‌تواند برای هر موجودی که معرفی می‌کند، به‌ویژه برای هر استدلال، یکی از دو احتمال را یک‌بار برای همیشه انتخاب کند:

[ایکس].یک موجودیت به طور بالقوه چند شکلی است: اکنون یا بعداً (با عبور پارامترها یا با انتساب) می توان آن را به یک شی که نوع آن با نمونه اعلام شده متفاوت است متصل کرد. نوع موجودیت اصلی توسط هیچ یک از نوادگان کلاس قابل تغییر نیست.

[ایکس].یک موجودیت موضوعی است که از نوع overriding است، به این معنی که یا لنگر است یا خودش یک محور است.

اما چگونه یک توسعه دهنده می تواند همه اینها را پیش بینی کند؟ تمام جذابیت روش OO که به طرق مختلف در اصل Open-Closed بیان شده است، دقیقاً به دلیل امکان تغییراتی است که ما حق اعمال آنها را در کارهای قبلی انجام داده ایم و همچنین به این دلیل که توسعه دهنده راه حل های جهانی نهباید عقل بی نهایت داشته باشد، درک کند که چگونه محصول او می تواند با نیازهای آنها توسط فرزندان سازگار شود.

با این رویکرد، تعریف مجدد انواع و پنهان شدن توسط فرزندان نوعی "دریچه ایمنی" است که استفاده مجدد از یک کلاس موجود را که تقریباً برای اهداف ما مناسب است امکان پذیر می کند:

[ایکس].با توسل به تعریف مجدد نوع، می‌توانیم اعلان‌های کلاس مشتق‌شده را بدون تأثیرگذاری بر روی اصلی تغییر دهیم. در این مورد، یک راه حل صرفاً کوواریانس نیاز به ویرایش نسخه اصلی با تبدیل های توصیف شده دارد.

[ایکس].پنهان شدن توسط یک نسل از بسیاری از شکست ها هنگام ایجاد یک کلاس محافظت می کند. می توان پروژه ای را نقد کرد که در آن مستطیل, با استفاده از این واقعیت کهاز نوادگان است چند ضلعی، سعی می کند یک راس اضافه کند. در عوض، می‌توان ساختاری ارثی را پیشنهاد کرد که در آن ارقام با تعداد رئوس ثابت از بقیه جدا می‌شوند و مشکلی پیش نمی‌آید. با این حال، هنگام طراحی ساختارهای ارثی، همیشه ترجیح داده می شود که ساختارهایی را داشته باشند که ندارند استثناهای طبقه بندی. اما آیا می توان آنها را به طور کامل حذف کرد؟ در بحث محدودیت های صادراتی در یکی از سخنرانی های زیر، خواهیم دید که این امر به دو دلیل امکان پذیر نیست. اولین مورد وجود معیارهای طبقه بندی رقابتی است. ثانیا، این احتمال وجود دارد که توسعه دهنده راه حل ایده آل را پیدا نکند، حتی اگر وجود داشته باشد.

تحلیل جهانی

این بخش به توضیح رویکرد میانی اختصاص دارد. راه حل های عملی اصلی در سخنرانی 17 ارائه شده است.

هنگام بررسی گزینه پین ​​کردن، متوجه شدیم که ایده اصلی آن جداسازی مجموعه‌های موجودیت کوواریانت و چند شکلی است. بنابراین، اگر دو دستورالعمل از فرم را در نظر بگیریم


هر یک از آنها نمونه ای از کاربرد صحیح مکانیسم های مهم OO است: اولین - چند شکلی، دوم - تعریف مجدد نوع. مشکلات با ترکیب آنها برای یک موجودیت شروع می شود س. به همین ترتیب:


مشکلات از اتحاد دو اپراتور مستقل و کاملاً بی گناه شروع می شود.

تماس های اشتباه منجر به نقض تایپ می شود. در مثال اول، انتساب چند شکلی یک شی را متصل می کند پسربه اصل س، او چه می کند gاستدلال نامعتبر اشتراک گذاری، زیرا با یک شی مرتبط است دختر. در مثال دوم به نهاد rشی متصل شده است مستطیل، که منتفی است add_vertexاز قطعات صادر شده

در اینجا ایده یک راه حل جدید است: از قبل - به صورت ایستا، هنگام بررسی انواع توسط یک کامپایلر یا ابزارهای دیگر - ما تعریف می کنیم حروفچینیهر موجودیت، از جمله انواع اشیایی که موجودیت می تواند در زمان اجرا با آنها مرتبط شود. سپس، دوباره به صورت استاتیک، مطمئن می‌شویم که هر فراخوانی برای هر عنصر از نوع هدف و مجموعه‌های آرگومان صحیح است.

در مثال های ما، اپراتور s:=bنشان می دهد که کلاس پسرمتعلق به مجموعه انواع برای س(چون در نتیجه اجرای دستور create ایجاد بمتعلق به مجموعه انواع برای ب). دختر، به دلیل وجود دستورالعمل g ایجاد کنید، متعلق به مجموعه انواع برای g. اما پس از آن چالش اشتراک گذاریبرای هدف نامعتبر خواهد بود سنوع پسرو استدلال gنوع دختر. به همین ترتیب مستطیلدر نوع تنظیم شده برای است پ، که به دلیل انتساب چند شکلی است، با این حال، تماس add_vertexبرای پنوع مستطیلنامعتبر خواهد بود.

این مشاهدات ما را به تفکر در مورد خلقت سوق می دهد جهانی استرویکرد مبتنی بر قانون تایپ جدید:

قانون صحت سیستم

صدا زدن x.f(arg)سیستم صحیح است اگر و فقط اگر برای کلاس صحیح باشد ایکس، و ارگ، که هر نوع از مجموعه نوع مربوطه خود را دارند.

در این تعریف، فراخوانی اگر قانون فراخوانی مؤلفه را نقض نکند که می گوید: اگر سییک کلاس پایه مانند وجود دارد ایکس، جزء fباید صادر شود سی، و نوع ارگباید با نوع پارامتر رسمی سازگار باشد f. (به یاد داشته باشید، برای سادگی، ما فرض می کنیم که هر زیربرنامه فقط یک پارامتر دارد، اما گسترش قانون به تعداد دلخواه آرگومان سخت نیست.)

صحت سیستم یک فراخوانی به صحت کلاس کاهش می یابد، با این تفاوت که نه برای عناصر منفرد، بلکه برای هر جفت از مجموعه مجموعه ها بررسی می شود. در اینجا قوانین اساسی برای ایجاد مجموعه ای از انواع برای هر موجودیت وجود دارد:

1 برای هر موجودیت، مجموعه اولیه انواع خالی است.

2 با توجه به دستورالعمل دیگری از فرم ایجاد (SOME_TYPE) a، اضافه کردن SOME_TYPEبه مجموعه ای از انواع برای آ. (برای سادگی، هر دستورالعملی را فرض می کنیم ایجاد یکبا دستورالعمل جایگزین خواهد شد ایجاد (ATYPE) الف، جایی که یک نوع- نوع نهاد آ.)

3 مواجه شدن با تکلیف دیگری از فرم a:=b، به مجموعه انواع برای اضافه کنید آ ب.

4 اگر آیک پارامتر رسمی از زیربرنامه است، پس با تماس بعدی با پارامتر واقعی ب، به مجموعه انواع برای اضافه کنید آتمام عناصر مجموعه انواع برای ب.

5 ما مراحل (3) و (4) را تکرار می کنیم تا زمانی که مجموعه انواع دیگر تغییر نکند.

این فرمول بندی مکانیسم جهانی بودن را در نظر نمی گیرد، با این حال، می توان قاعده را در صورت لزوم بدون هیچ مشکل خاصی گسترش داد. مرحله (5) به دلیل امکان زنجیره واگذاری و انتقال (از ببه آ، از جانب جبه بو غیره.). به راحتی می توان فهمید که پس از تعداد محدودی از مراحل، این فرآیند متوقف می شود.

همانطور که ممکن است متوجه شده باشید، این قانون توالی دستورالعمل ها را در نظر نمی گیرد. چه زمانی


create(TYPE1) t; s:=t; ایجاد (TYPE2) t

به مجموعه ای از انواع برای سبه عنوان وارد کنید TYPE1، و TYPE2، با اينكه سبا توجه به ترتیب دستورات، فقط قادر به گرفتن مقادیر نوع اول است. در نظر گرفتن مکان دستورالعمل ها، کامپایلر را ملزم به تجزیه و تحلیل عمیق جریان دستورالعمل می کند، که منجر به افزایش بیش از حد سطح پیچیدگی الگوریتم می شود. در عوض، قوانین بدبینانه تری اعمال می شود: دنباله عملیات:


با وجود اینکه دنباله اجرای آنها منجر به نقض نوع نمی شود، سیستم نادرست اعلام می شود.

تجزیه و تحلیل جهانی سیستم (با جزئیات بیشتر) در فصل 22 تک نگاری ارائه شد. در عین حال هم مشکل کوواریانس و هم مشکل محدودیت صادرات در حین وراثت حل شد. با این حال، این رویکرد یک نقص عملی تاسف بار دارد، یعنی: قرار است بررسی شود سیستم به عنوان یک کلبه جای هر کلاس جداگانه قانون (4) کشنده است، که هنگام فراخوانی یک زیربرنامه کتابخانه، تمام تماس های احتمالی آن در کلاس های دیگر را در نظر می گیرد.

اگرچه الگوریتم‌های بعدی برای کار با کلاس‌های فردی در سال پیشنهاد شد، اما ارزش عملی آن‌ها را نمی‌توان مشخص کرد. این بدان معنی است که در یک محیط برنامه نویسی که از کامپایل افزایشی پشتیبانی می کند، لازم است که بررسی کل سیستم را سازماندهی کنیم. توصیه می شود چک کردن را به عنوان یک عنصر پردازش محلی (سریع) تغییرات ایجاد شده توسط کاربر در برخی از کلاس ها معرفی کنیم. اگرچه نمونه هایی از رویکرد جهانی شناخته شده است، برای مثال، برنامه نویسان C از این ابزار استفاده می کنند پرزبرای پیدا کردن تناقضات در سیستم که توسط کامپایلر شناسایی نمی شوند - همه اینها خیلی جذاب به نظر نمی رسند.

در نتیجه، تا آنجا که من می دانم، بررسی صحت سیستم توسط کسی اجرا نشده است. (یک دلیل دیگر برای این نتیجه ممکن است پیچیدگی قوانین اعتبارسنجی باشد.)

صحت کلاس شامل اعتبارسنجی محدود به کلاس است و بنابراین با کامپایل تدریجی امکان پذیر است. صحت سیستم مستلزم بررسی کلی کل سیستم است که با کامپایل تدریجی در تضاد است.

با این حال، علیرغم نام آن، در واقع می توان صحت سیستم را با استفاده از بررسی کلاس افزایشی (در طول یک کامپایلر معمولی) بررسی کرد. این سهم نهایی برای حل مشکل خواهد بود.

مراقب catcal های چند شکلی باشید!

قانون صحت سیستم بدبینانه است: به خاطر سادگی، ترکیبات کاملاً ایمن دستورالعمل ها را نیز رد می کند. اگرچه ممکن است متناقض به نظر برسد، اما ما آخرین نسخه راه حل را بر اساس آن می سازیم قانون حتی بدبینانه تر. طبیعتاً این سؤال را ایجاد می کند که نتیجه ما چقدر واقع بینانه خواهد بود.

بازگشت به یالتا

اصل راه حل Catcall (Catcall)، - معنای این مفهوم را بعداً توضیح خواهیم داد - در بازگشت به روح توافقات یالتا، تقسیم جهان به چند شکلی و کوواریانس (و همدم کوواریانس - پنهان فرزندان) اما بدون نیاز به داشتن خرد بی پایان.

مانند قبل، سؤال کوواریانس را به دو عملیات محدود می کنیم. در مثال اصلی ما، این یک تخصیص چند شکلی است: s:=bو فراخوانی زیربرنامه کوواریانت: s.share(g). با تحلیل اینکه چه کسی مقصر واقعی تخلفات است، بحث را حذف می کنیم gاز مظنونین هر نوع استدلال اسکی بازیا مشتق از آن، به دلیل چندشکلی مناسب ما نیست سو کوواریانس اشتراک گذاری. بنابراین، اگر به طور ایستا ماهیت را توصیف کنید دیگرچگونه اسکی بازو به صورت پویا به جسم متصل شوند اسکی باز، سپس تماس s.share (سایر)به طور ایستا تصور ایده آل بودن را ایجاد می کند، اما اگر به صورت چند شکلی اختصاص داده شود منجر به نقض نوع می شود سمعنی ب.

مشکل اساسی این است که ما سعی می کنیم استفاده کنیم سبه دو روش ناسازگار: به عنوان یک موجود چند شکلی و به عنوان هدف یک فراخوانی زیرروال کوواریانس. (در مثال دیگر ما، مشکل استفاده است پبه عنوان یک موجود چند شکلی و به عنوان هدف فراخوانی زیربرنامه یک کودک که مؤلفه را پنهان می کند. add_vertex.)

راه‌حل Catcall، مانند پینینگ، رادیکال است: استفاده از یک موجودیت را به‌عنوان چند شکلی و کوواریانت همزمان ممنوع می‌کند. مانند تجزیه جهانی، به طور ایستا تعیین می کند که کدام موجودیت ها می توانند چندشکلی باشند، اما سعی نمی کند با جستجوی مجموعه هایی از انواع ممکن برای موجودیت ها، خیلی باهوش باشد. در عوض، هر موجود چندشکل به اندازه‌ای مشکوک تلقی می‌شود که همبستگی با حلقه‌ای از افراد محترم، از جمله کوواریانس و پنهان شدن فرزندان، ممنوع است.

یک قانون و چند تعریف

قانون نوع برای راه حل Catcall یک فرمول ساده دارد:

Rule را برای Catcall تایپ کنید

catcal های چند شکلی نادرست هستند.

بر اساس تعاریف به همان اندازه ساده است. اول از همه، یک موجود چند شکلی:

تعریف: موجودیت چندشکلی

ذات ایکسنوع مرجع (غیر منبسط شده) در صورتی چندشکلی است که یکی از ویژگی های زیر را داشته باشد:

1 در تکلیف رخ می دهد x: = y، که در آن ذات yنوع متفاوتی دارد یا با بازگشت چند شکلی است.

2 در دستورالعمل های ایجاد یافت می شود ایجاد (OTHER_TYPE) x، جایی که OTHER_TYPEنوع مشخص شده در اظهارنامه نیست ایکس.

3 این یک استدلال رسمی برای یک برنامه فرعی است.

4 یک عملکرد خارجی است.

هدف از این تعریف این است که به هر موجودیتی که می تواند در طول اجرای برنامه به اشیاء با انواع مختلف متصل شود، وضعیت چندشکلی ("به طور بالقوه چند شکل") بدهد. این تعریف فقط برای انواع مرجع اعمال می شود، زیرا موجودیت های گسترش یافته به طور طبیعی نمی توانند چند شکلی باشند.

در مثال های ما، اسکی باز سو چند ضلعی پطبق قاعده (1) چندشکلی هستند. به اولی یک شی اختصاص داده می شود پسر ب، دوم - شی RECTANGLE r.

اگر به تعریف مجموعه ای از انواع نگاه کرده باشید، متوجه خواهید شد که تعریف موجودیت چندشکلی تا چه حد بدبینانه تر است و آزمایش آن آسان تر است. بدون تلاش برای یافتن تمام انواع پویای موجود یک موجود، به این سوال کلی بسنده می کنیم: آیا یک موجودیت معین می تواند چندشکلی باشد یا خیر؟ شگفت انگیزترین قانون (3) است که طبق آن چند شکلیشمارش می کند هر پارامتر رسمی(مگر اینکه نوع آن بسط داده شده باشد، مانند اعداد صحیح و غیره). ما حتی با تجزیه و تحلیل تماس ها زحمت نمی دهیم. اگر زیربرنامه دارای آرگومان باشد، در اختیار کلاینت است، به این معنی که نمی توانید به نوع مشخص شده در اعلان اعتماد کنید. این قانون ارتباط نزدیکی با استفاده مجدد- هدف فناوری شی، - جایی که هر کلاسی می تواند به طور بالقوه در کتابخانه گنجانده شود و بارها توسط مشتریان مختلف فراخوانی شود.

ویژگی مشخصه این قانون این است که به هیچ گونه چک جهانی نیاز ندارد. برای شناسایی چندشکلی یک موجودیت، کافی است متن خود کلاس را مشاهده کنید. اگر برای همه درخواست ها (ویژگی ها یا توابع) اطلاعاتی در مورد وضعیت چندشکلی آنها ذخیره شود، حتی متون اجداد نیز نیازی به مطالعه ندارند. برخلاف یافتن مجموعه‌هایی از انواع، می‌توانید موجودیت‌های چندشکل را با بررسی کلاس به کلاس در طول کامپایل افزایشی کشف کنید.

تماس‌ها، مانند موجودیت‌ها، می‌توانند چند شکلی باشند:

تعریف: فراخوانی چند شکلی

تماسی چند شکلی است اگر هدف آن چند شکلی باشد.

هر دو فراخوانی در مثال های ما چند شکلی هستند: s.share(g)به دلیل پلی مورفیسم س, p.add_vertex(...)به دلیل پلی مورفیسم پ. طبق تعریف، فقط تماس های واجد شرایط می توانند چند شکلی باشند. (یک تماس غیرمجاز f(...)نوعی واجد شرایط Current.f(...)، ما اصل موضوع را تغییر نمی دهیم، زیرا جاری، که هیچ چیز را نمی توان به آن نسبت داد، یک شی چند شکلی نیست.)

در مرحله بعد، ما به مفهوم Catcall بر اساس مفهوم CAT نیاز داریم. (CAT مخفف Changing Availability یا Type است). یک زیربرنامه یک زیربرنامه CAT است اگر تعریف مجدد آن توسط یک فرزند منجر به یکی از دو نوع تغییری شود که مشاهده کرده‌ایم به طور بالقوه خطرناک هستند: تغییر نوع آرگومان (به صورت کوواریانس) یا پنهان کردن یک مؤلفه قبلاً صادر شده.

تعریف: روتین های CAT

یک روال، روتین CAT نامیده می شود که تعریف مجدد آن وضعیت صادرات یا نوع هر یک از آرگومان های آن را تغییر دهد.

این ویژگی مجدداً امکان بررسی افزایشی را می‌دهد: هرگونه تعریف مجدد از نوع آرگومان یا وضعیت صادرات، رویه یا عملکرد را به یک زیربرنامه CAT تبدیل می‌کند. اینجاست که مفهوم Catcall مطرح می شود: فراخوانی یک زیربرنامه CAT که می تواند اشتباه باشد.

تعریف: Catcall

در صورتی که برخی از تعریف مجدد روال به دلیل تغییر در وضعیت صادرات یا نوع آرگومان، باعث شکست آن شود، یک تماس Catcall نامیده می شود.

طبقه بندی که ما ایجاد کردیم به ما امکان می دهد گروه های خاصی از تماس ها را تشخیص دهیم: چند شکلی و catcalls. فراخوانی چند شکلی قدرت بیانی به رویکرد شی می دهد، catcals به شما امکان می دهد تا انواع را دوباره تعریف کنید و صادرات را محدود کنید. با استفاده از اصطلاحاتی که قبلاً در این فصل معرفی شد، می توان گفت که فراخوانی چند شکلی گسترش می یابد مفید بودن، catcals - قابلیت استفاده.

چالش ها اشتراک گذاریو add_vertex، که در مثال های ما در نظر گرفته شده است، تماس های گربه هستند. مورد اول یک تعریف مجدد کوواریانت از آرگومان خود انجام می دهد. مورد دوم توسط کلاس صادر می شود مستطیل، اما توسط کلاس پنهان شده است چند ضلعی. هر دو تماس نیز چند شکلی هستند، و به همین دلیل ارائه می‌شوند مثال کامل catcals چند شکلی آنها طبق قانون نوع Catcall اشتباه هستند.

مقطع تحصیلی

قبل از اینکه همه چیزهایی را که در مورد کوواریانس و پنهان کردن کودک آموخته ایم جمع بندی کنیم، بیایید یادآوری کنیم که نقض صحت سیستم ها واقعاً نادر است. مهمترین خصوصیات تایپ استاتیک OO در ابتدای سخنرانی خلاصه شد. این آرایه چشمگیر از مکانیسم‌های تایپ، همراه با اعتبارسنجی مبتنی بر کلاس، راه را برای یک روش امن و انعطاف‌پذیر برای ساخت نرم‌افزار هموار می‌کند.

ما سه راه حل برای مشکل کوواریانس دیده ایم که دو تای آن محدودیت های صادراتی را نیز شامل می شود. کدامیک صحیح می باشد؟

هیچ پاسخ قطعی برای این سوال وجود ندارد. عواقب تعامل موذیانه بین تایپ OO و چندشکلی به خوبی موضوعات مورد بحث در سخنرانی‌های قبلی درک نشده است. در سال‌های اخیر، انتشارات متعددی در این زمینه منتشر شده است که در کتاب‌شناسی در انتهای سخنرانی به آنها اشاره شده است. علاوه بر این، امیدوارم در این سخنرانی توانسته باشم عناصر راه حل نهایی را ارائه دهم یا حداقل به آن نزدیک شده باشم.

تجزیه و تحلیل جهانی به دلیل غیرعملی به نظر می رسد بررسی کاملکل سیستم با این حال، به ما کمک کرد تا مشکل را بهتر درک کنیم.

راه حل پینینگ بسیار جذاب است. این ساده، شهودی، آسان برای پیاده سازی است. هر چه بیشتر باید از عدم امکان پشتیبانی در آن تعدادی از الزامات کلیدی روش OO که در اصل باز-بسته منعکس شده است متأسف باشیم. اگر ما واقعاً شهود بزرگی داشتیم، پین کردن راه‌حل عالی خواهد بود، اما کدام توسعه‌دهنده جرات دارد این را ادعا کند، یا حتی بیشتر از آن، اعتراف کند که نویسندگان کلاس‌های کتابخانه‌ای که در پروژه او به ارث رسیده‌اند چنین شهودی داشتند؟

اگر مجبور به ترک فیکساسیون شویم، به نظر می رسد راه حل Catcall مناسب ترین است، که به راحتی توضیح داده شده و در عمل قابل اجرا است. بدبینی او نباید مانع از ترکیب مفید اپراتورها شود. در موردی که یک catcall چند شکلی توسط یک عبارت «مشروع» تولید می‌شود، همیشه می‌توان آن را با معرفی تلاش برای انتساب پذیرفت. بنابراین می توان تعدادی چک را به زمان اجرای برنامه منتقل کرد. با این حال، تعداد چنین مواردی باید بسیار کم باشد.

برای شفاف سازی باید توجه داشته باشم که در زمان نگارش این مطلب راهکار Catcall اجرا نشده است. تا زمانی که کامپایلر با بررسی قاعده نوع Catcall تطبیق داده شود و با موفقیت در سیستم‌های نمایشی بزرگ و کوچک اعمال شود، خیلی زود است که بگوییم آخرین کلمه در مورد مشکل تطبیق تایپ استاتیک با چندشکلی، همراه با کوواریانس و پنهان‌سازی نسل گفته شده است.

انطباق کامل

در پایان بحث کوواریانس، درک اینکه چگونه می توان یک روش کلی را برای یک مسئله نسبتاً کلی به کار برد، مفید است. این روش در نتیجه نظریه Catcall ظاهر شد، اما می تواند در چارچوب نسخه اصلی زبان بدون معرفی قوانین جدید استفاده شود.

اجازه دهید دو لیست منطبق وجود داشته باشد، جایی که اولی اسکی بازان را مشخص می کند و دومی هم اتاقی را برای اسکی باز از لیست اول مشخص می کند. ما می خواهیم رویه مناسبی را برای قرار دادن انجام دهیم اشتراک گذاری، فقط در صورتی که توسط قوانین توصیف نوع مجاز باشد که به دختران با دختران اجازه می دهد، دختران را با جایزه دختران جایزه می دهند و غیره. مشکلاتی از این دست رایج است.

ممکن است راه حل ساده ای بر اساس بحث و تکلیف قبلی وجود داشته باشد. تابع جهانی را در نظر بگیرید نصب شده(به تایید):


برازنده (سایر: GENERAL): مانند دیگر است
- شی فعلی (Current)، اگر نوع آن با نوع شی مطابقت داشته باشد،
-- متصل به دیگری، در غیر این صورت باطل است.
if other /= Void و سپس conforms_to (دیگر) سپس

عملکرد نصب شدهشی فعلی را برمی گرداند، اما به عنوان موجودی از نوع متصل به آرگومان شناخته می شود. اگر نوع شی فعلی با نوع شیء متصل به آرگومان مطابقت نداشته باشد، برمی گردد خالی. به نقش تلاش برای انتساب توجه کنید. تابع از کامپوننت استفاده می کند مطابق بااز کلاس عمومی، که نوع سازگاری یک جفت شی را تعیین می کند.

جایگزینی مطابق بابه جزء دیگر عمومیبا نام همان نوعیک تابع به ما می دهد کامل_مناسب (انطباق کامل) که برمی گردد خالیاگر نوع هر دو شیء یکسان نباشد.

عملکرد نصب شده- راه حل ساده ای برای مشکل تطبیق اسکی بازان بدون نقض قوانین توصیف انواع به ما می دهد. بنابراین، در کد کلاس اسکی بازمی توانیم یک رویه جدید معرفی کنیم و به جای آن از آن استفاده کنیم اشتراک گذاری، (این دومی را می توان یک رویه پنهان ساخت).


- در صورت وجود، موارد دیگر را به عنوان همسایه شماره انتخاب کنید.
-- gender_ascertained - جنسیت اختصاص داده شده است
gender_ascertained_other: مانند فعلی
gender_ascertained_other:= other .fitted(Current)
if gender_ascertained_other /= باطل است
اشتراک گذاری (جنس_تعیین شده_سایر)
"نتیجه گیری: هم نشینی با دیگران امکان پذیر نیست"

برای دیگرنوع دلخواه اسکی باز(نه فقط مانند فعلی) نسخه را تعریف کنید جنسیت_تعیین شده_سایر، که دارای یک نوع اختصاص داده شده است جاری. تابع به ما کمک می کند تا هویت انواع را تضمین کنیم کامل_مناسب.

اگر دو لیست موازی از اسکی بازان وجود داشته باشد که محل اقامت برنامه ریزی شده را نشان می دهند:


occupant1, occupant2: LIST

سازماندهی یک حلقه با اجرای یک فراخوان در هر مرحله امکان پذیر است:


occupant1.item.safe_share(occupant2.item)

تطبیق عناصر لیست اگر و تنها در صورتی که انواع آنها کاملاً سازگار باشد.

مفاهیم کلیدی

[ایکس].تایپ استاتیک کلید اطمینان، خوانایی و کارایی است.

[ایکس].برای واقع بینانه بودن، تایپ استاتیک به ترکیبی از مکانیسم ها نیاز دارد: ادعاها، وراثت چندگانه، تلاش برای تخصیص، عمومیت محدود و نامحدود، اعلامیه های لنگر. سیستم نوع نباید اجازه تله (ریخته گری نوع) را بدهد.

[ایکس].قوانین سرانگشتی برای اعلام مجدد باید امکان تعریف مجدد کوواریانت را فراهم کند. انواع نتیجه و آرگومان لغو شده باید با موارد اصلی سازگار باشد.

[ایکس].کوواریانس، و همچنین توانایی کودک برای پنهان کردن یک جزء صادر شده توسط یک اجداد، همراه با چند شکلی، یک مشکل نادر اما بسیار جدی نقض نوع ایجاد می کند.

[ایکس].از این تخلفات می توان با استفاده از موارد زیر جلوگیری کرد: تجزیه و تحلیل سراسری (که غیرعملی است)، محدود کردن کوواریانس به انواع ثابت (که مغایر با اصل باز-بسته است)، راه حل Catcall، که مانع از فراخوانی یک زیربرنامه با کوواریانس یا پنهان کردن یک هدف چند شکلی می شود. کودک.

یادداشت های کتابشناختی

تعدادی از مطالب از این سخنرانی در گزارش های انجمن های OOPSLA 95 و TOOLS PACIFIC 95 ارائه شده است و همچنین در . تعدادی از مطالب بررسی از مقاله قرض گرفته شده است.

مفهوم استنتاج نوع خودکار در معرفی شد، جایی که الگوریتم استنتاج نوع زبان تابعی ML توضیح داده شده است. رابطه بین چندشکلی و بررسی نوع در کاوش شده است.

تکنیک‌های بهبود کارایی کد در زبان‌های تایپ شده پویا در زمینه زبان Self را می‌توان در .

لوکا کاردلی و پیتر وگنر مقاله‌ای نظری در مورد انواع زبان‌های برنامه‌نویسی نوشتند که تأثیر زیادی بر متخصصان داشت. این کار، که بر اساس حساب لامبدا ساخته شده است (نگاه کنید به)، به عنوان پایه ای برای بسیاری از مطالعات بیشتر عمل کرد. قبل از آن مقاله بنیادی دیگری توسط کاردلی منتشر شد.

کتابچه راهنمای ISE شامل مقدمه ای بر مشکلات کاربرد مشترک چندشکلی، کوواریانس و پنهان شدن نسل است. عدم تحلیل مناسب در چاپ اول این کتاب منجر به تعدادی بحث انتقادی شد (اولین آنها نظرات فیلیپ الینک در کار کارشناسی "De la Conception-Programmation par Objets"، Memoire de licence، Universite Libre de بود. بروکسل (بلژیک)، 1988)، بیان شده در آثار و . مقاله کوک چندین مثال در رابطه با مسئله کوواریانس ارائه می دهد و سعی در حل آن دارد. راه حلی بر اساس پارامترهای نوع برای موجودیت های کوواریانس در TOOLS EUROPE 1992 توسط فرانتس وبر پیشنهاد شد. تعاریف دقیقی از مفاهیم صحت سیستم، و همچنین صحت کلاس، در ارائه شده است، و راه حلی با استفاده از تجزیه و تحلیل کامل سیستم نیز در آنجا پیشنهاد شده است. راه حل Catcall برای اولین بار در ارائه شد. همچنین ببینید .

راه حل رفع مشکل در سخنرانی من در سمینار TOOLS EUROPE 1994 ارائه شد. اما در آن زمان من نیازی به لنگر-تبلیغات و محدودیت‌های سازگاری مرتبط. پل دوبوآ و امیرام یهودایی به سرعت اشاره کردند که مشکل کوواریانس در این شرایط همچنان باقی است. آنها، و همچنین راینهارت بود، کارل هاینز سیلا، کیم والدن و جیمز مک کیم، نظرات بسیاری را ارائه کردند که در کاری که منجر به نوشتن این سخنرانی شد، اهمیت اساسی داشتند.

حجم زیادی از ادبیات به مسائل کوواریانس اختصاص داده شده است. در و شما هم کتابشناسی گسترده و هم مروری بر جنبه های ریاضی مسئله پیدا خواهید کرد. برای فهرستی از پیوندها به مطالب آنلاین در نظریه نوع OOP و صفحات وب نویسندگان آنها، به صفحه لورن دامی مراجعه کنید. مفاهیم کوواریانس و تضاد از نظریه مقوله وام گرفته شده اند. ما ظاهر آنها را در زمینه تایپ برنامه مدیون لوکا کاردلی هستیم که در اوایل دهه 80 شروع به استفاده از آنها در سخنرانی های خود کرد، اما تا اواخر دهه 80 به آنها در چاپ متوسل نشد.

تکنیک های مبتنی بر متغیرهای نوع در , , توضیح داده شده است.

Contravariance در زبان ساتر اجرا شد. توضیحات در آورده شده است.

اگرچه گزینه های میانی امکان پذیر است، دو رویکرد اصلی در اینجا ارائه شده است:

  • تایپ پویا: صبر کنید تا هر تماس کامل شود و سپس تصمیم بگیرید.
  • تایپ استاتیک: با توجه به مجموعه ای از قوانین، از متن منبع مشخص کنید که آیا نقض نوع در طول اجرا امکان پذیر است یا خیر. سیستم در صورتی اجرا می شود که قوانین تضمین کند که هیچ خطایی وجود ندارد.

توضیح این اصطلاحات آسان است: تایپ پویابررسی نوع در زمان اجرا (به صورت پویا)، در حالی که تایپ استاتیکبررسی روی متن به صورت ایستا (قبل از اجرا) انجام می شود.

تایپ استاتیکشامل بررسی خودکار است که معمولاً مسئولیت آن بر عهده کامپایلر است. در نتیجه ما یک تعریف ساده داریم:

تعریف: زبان تایپ ایستا

یک زبان OO به صورت ایستا تایپ می‌شود اگر با مجموعه‌ای از قوانین ثابت همراه باشد که توسط کامپایلر بررسی می‌شود و اطمینان حاصل می‌کند که اجرای سیستم منجر به نقض نوع نمی‌شود.

عبارت " قویتایپ کردن" ( قوی). این با ماهیت اولتیماتوم تعریف مطابقت دارد و مستلزم عدم وجود کامل نقض نوع است. ممکن و ضعیف (ضعیف) تشکیل می دهد تایپ استاتیک، که در آن قوانین برخی تخلفات را بدون حذف کامل آنها حذف می کند. از این نظر، برخی از زبان‌های OO به صورت ایستا ضعیف تایپ می‌شوند. ما برای قوی ترین تایپ مبارزه خواهیم کرد.

در پویا زبان های تایپ شده، که بدون تایپ شناخته می شوند، هیچ اعلان نوع ندارند و هر مقداری را می توان در زمان اجرا به موجودیت ها پیوست. بررسی نوع استاتیک در آنها امکان پذیر نیست.

قوانین تایپ

نماد OO ما به صورت ایستا تایپ شده است. قوانین نوع او در سخنرانی های قبلی معرفی شد و به سه شرط ساده خلاصه می شود.

  • هنگام اعلان هر موجودیت یا تابع، نوع آن باید مشخص شود، به عنوان مثال، اکانت: ACCOUNT. هر زیربرنامه دارای 0 یا بیشتر آرگومان رسمی است که نوع آنها باید مشخص شود، به عنوان مثال: put (x: G; i: INTEGER).
  • در هر تخصیص x:= y، و در هر فراخوانی فرعی که در آن y آرگومان واقعی آرگومان رسمی x است، نوع منبع y باید با نوع هدف x سازگار باشد. تعریف سازگاری بر اساس وراثت است: B اگر از نسل A باشد با A سازگار است - با قوانینی برای پارامترهای عمومی تکمیل شده است (به "مقدمه ای بر وراثت" مراجعه کنید).
  • فراخوانی به x.f(arg) مستلزم آن است که f جزء کلاس پایه از نوع هدف x باشد و f باید به کلاسی که فراخوانی در آن ظاهر می شود صادر شود (به 14.3 مراجعه کنید).

واقع گرایی

اگرچه تعریف یک زبان تایپ ایستا کاملاً دقیق است، اما کافی نیست - معیارهای غیررسمی هنگام ایجاد قوانین تایپ مورد نیاز است. بیایید دو مورد شدید را در نظر بگیریم.

  • زبان کاملا درست، که در آن هر سیستم صحیح نحوی نیز از نظر نوع صحیح است. قوانین اعلام نوع مورد نیاز نیست. چنین زبان هایی وجود دارند (به نماد لهستانی برای جمع و تفریق اعداد صحیح فکر کنید). متأسفانه، هیچ زبان جهانی واقعی این معیار را برآورده نمی کند.
  • زبان کاملا اشتباه، که با استفاده از هر زبان موجود و اضافه کردن یک قانون تایپ که ایجاد می کند آسان است هرسیستم نادرست است طبق تعریف، این زبان تایپ می شود: از آنجایی که هیچ سیستمی وجود ندارد که با قوانین مطابقت داشته باشد، هیچ سیستمی باعث نقض نوع نمی شود.

می توان گفت که زبان های نوع اول مناسب، ولی بلا استفاده، دومی ممکن است مفید باشد، اما مناسب نیست.

در عمل، آنچه مورد نیاز است یک سیستم نوع است که در عین حال مفید و مفید باشد: آنقدر قدرتمند که نیازهای محاسبات را برآورده کند و به اندازه کافی راحت باشد که ما را مجبور به رفتن به سمت پیچیدگی برای ارضای قوانین تایپ نکند.

خواهیم گفت که زبان واقع بیناگر در عمل قابل استفاده و مفید باشد. بر خلاف تعریف تایپ استاتیکپاسخی قاطع به این سوال می دهد: آیا زبان X به صورت ایستا تایپ شده است"، تعریف رئالیسم تا حدی ذهنی است.

در این سخنرانی، مطمئن خواهیم شد که نمادی که پیشنهاد می کنیم واقع بینانه است.

بدبینی

تایپ استاتیکطبیعتاً به یک سیاست "بدبینانه" منجر می شود. تلاشی برای تضمین آن همه محاسبات منجر به شکست نمی شود، رد می کند محاسباتی که می تواند بدون خطا به پایان برسد.

یک زبان منظم، غیر هدف و پاسکال مانند با انواع مختلف REAL و INTEGER در نظر بگیرید. هنگام توصیف n: INTEGER; r: عملگر واقعی n:= r به دلیل نقض قوانین رد خواهد شد. بنابراین، کامپایلر تمام عبارات زیر را رد می کند:

n:= 0.0 [A] n:= 1.0 [B] n:= -3.67 [C] n:= 3.67 - 3.67 [D]

اگر به آنها اجازه اجرا بدهیم، خواهیم دید که [A] همیشه کار خواهد کرد، زیرا هر سیستم عددی نمایش دقیقی از عدد واقعی 0.0 دارد که به طور واضح به 0 اعداد صحیح ترجمه می شود. [B] نیز تقریباً مطمئناً کار خواهد کرد. نتیجه عمل [C] واضح نیست (آیا می خواهیم با گرد کردن یا دور انداختن قسمت کسری به نتیجه برسیم؟). [D] کار را انجام خواهد داد، همانطور که اپراتور انجام می دهد:

ifn^2< 0 then n:= 3.67 end [E]

جایی که تخصیص غیرقابل دسترس وارد می شود (n ^ 2 مربع عدد n است). پس از جایگزینی n^2 با n، فقط یک سری از اجراها نتیجه صحیح را می دهد. اختصاص n یک مقدار واقعی بزرگ که نمی تواند به عنوان یک عدد صحیح نمایش داده شود منجر به شکست می شود.

AT زبان های تایپ شدههمه این مثال‌ها (در حال کار کردن، کار نکردن، گاهی اوقات کار کردن) بی‌رحمانه به عنوان نقض قوانین توصیف انواع تلقی می‌شوند و توسط هر کامپایلری رد می‌شوند.

سوال این نیست ما خواهیم کردچه بدبین باشیم و چه در واقع، چقدرما می توانیم بدبین باشیم. بازگشت به الزام واقع‌گرایی: اگر قواعد نوع آنقدر بدبینانه باشد که از سادگی نوشتن محاسبات جلوگیری کند، آنها را رد می‌کنیم. اما اگر دستیابی به ایمنی نوع با کاهش اندک قدرت بیان حاصل شود، آنها را می پذیریم. به عنوان مثال، در یک محیط توسعه که توابع گرد و کوتاه را برای گرد کردن و استخراج یک قسمت صحیح ارائه می‌کند، عملگر n:= r به درستی نادرست در نظر گرفته می‌شود، زیرا شما را مجبور می‌کند به‌جای استفاده از عدد صحیح، تبدیل واقعی به عدد صحیح را به صراحت بنویسید. تبدیل های پیش فرض مبهم

تایپ استاتیک: چگونه و چرا

اگرچه فواید تایپ استاتیکبدیهی است، خوب است که دوباره در مورد آنها صحبت کنیم.

مزایای

دلایل استفاده تایپ استاتیکدر فناوری شی، ما در ابتدای سخنرانی لیست کردیم. این قابلیت اطمینان، سهولت درک و کارایی است.

قابلیت اطمینانبه دلیل تشخیص خطاهایی که در غیر این صورت فقط در حین کار و فقط در برخی موارد می توانند خود را نشان دهند. اولین مورد از قوانین، که موجودیت ها را مجبور می کند و همچنین توابع را اعلام کنند، افزونگی را در متن برنامه معرفی می کند، که به کامپایلر اجازه می دهد تا با استفاده از دو قانون دیگر، ناسازگاری بین استفاده مورد نظر و واقعی موجودیت ها، مؤلفه ها و اصطلاحات.

تشخیص زودهنگام خطاها نیز مهم است زیرا هرچه بیشتر در یافتن آنها تاخیر داشته باشیم، هزینه تصحیح بیشتر خواهد شد. این ویژگی که به طور مستقیم برای همه برنامه نویسان حرفه ای قابل درک است، از نظر کمی توسط آثار شناخته شده بوهم تأیید شده است. وابستگی هزینه اصلاح به زمان یافتن خطاها در نموداری که بر اساس تعدادی از پروژه های صنعتی بزرگ و آزمایش های انجام شده با یک پروژه کوچک کنترل شده ساخته شده است نشان داده شده است:


برنج. 17.1.

خوانایییا سهولت درک(خوانایی) مزایای خود را دارد. در تمام مثال‌های این کتاب، ظاهر یک نوع بر روی یک موجودیت، اطلاعاتی در مورد هدف آن به خواننده می‌دهد. خوانایی در مرحله نگهداری بسیار مهم است.

سرانجام، بهره وریمی تواند موفقیت یا شکست فناوری شی را در عمل تعیین کند. در غیاب تایپ استاتیک x.f (arg) می‌تواند هر مقدار زمان برای اجرا طول بکشد. دلیل این امر این است که در زمان اجرا، اگر f در کلاس پایه هدف x یافت نشد، جستجو در فرزندان آن ادامه می‌یابد که راهی مطمئن به سمت ناکارآمدی است. می توانید با بهبود جستجوی یک جزء در سلسله مراتب مشکل را کاهش دهید. نویسندگان زبان Self تلاش زیادی کرده اند تا بهترین کد را برای یک زبان تایپ شده پویا تولید کنند. اما دقیقا تایپ استاتیکبه چنین محصول OO اجازه نزدیک شدن یا برابری با نرم افزارهای سنتی را داد.

کلید به تایپ استاتیکاین ایده قبلاً بیان شده است که کامپایلری که کد ساختار x.f (arg) را تولید می کند، نوع x را می داند. به دلیل چندشکلی، هیچ راهی برای تعیین منحصر به فرد نسخه مناسب مولفه f وجود ندارد. اما این اعلان مجموعه انواع ممکن را محدود می‌کند و به کامپایلر اجازه می‌دهد تا جدولی بسازد که دسترسی به f صحیح را با حداقل سربار فراهم می‌کند. با ثابت محدوددشواری دسترسی بهینه سازی های اضافی انجام شده است اتصال استاتیکو جایگزینی (در خطی)- همچنین توسط تایپ استاتیک، حذف هزینه ها به طور کامل در صورت لزوم.

آرگومان هایی برای تایپ پویا

با وجود همه اینها تایپ پویاطرفداران خود را به ویژه در بین برنامه نویسان Smalltalk از دست نمی دهد. استدلال‌های آنها اساساً مبتنی بر واقع‌گرایی است که در بالا مورد بحث قرار گرفت. آنها مطمئن هستند که تایپ استاتیکآنها را بیش از حد محدود می کند و آنها را از بیان آزادانه ایده های خلاقانه خود باز می دارد و گاهی اوقات آن را "کمربند عفت" می نامد.

می توان با این استدلال موافق بود، اما فقط برای زبان های تایپ ایستا که تعدادی از ویژگی ها را پشتیبانی نمی کنند. شایان ذکر است که تمام مفاهیم مرتبط با مفهوم نوع و معرفی شده در سخنرانی های قبلی ضروری است - رد هر یک از آنها با محدودیت های جدی همراه است و معرفی آنها برعکس به اعمال ما انعطاف می بخشد و می دهد. ما فرصتی برای لذت بردن کامل از عملی بودن داریم. تایپ استاتیک.

تیپ سازی: مولفه های موفقیت

مکانیسم های واقع بینانه چیست؟ تایپ استاتیک? همه آنها در سخنرانی های قبلی معرفی شدند و بنابراین تنها یادآوری مختصری از آنها باقی مانده است. شمارش مشترک آنها انسجام و قدرت انجمن آنها را نشان می دهد.

سیستم نوع ما کاملاً بر اساس مفهوم است کلاس. حتی انواع اولیه مانند INTEGER کلاس هستند، بنابراین برای توصیف انواع از پیش تعریف شده نیازی به قوانین خاصی نداریم. (این جایی است که نماد ما با زبان‌های ترکیبی مانند Object Pascal، Java و C++ متفاوت است، جایی که سیستم نوع زبان‌های قدیمی با فناوری شی مبتنی بر کلاس ترکیب می‌شود.)

انواع گسترش یافتهبا اجازه دادن به انواعی که مقادیر آنها نشان دهنده اشیاء هستند و همچنین انواعی که مقادیر آنها نشان دهنده مراجع هستند، انعطاف بیشتری به ما می دهد.

کلمه تعیین کننده در ایجاد یک سیستم نوع انعطاف پذیر متعلق به وراثتو مفهوم مرتبط سازگاری. این بر محدودیت اصلی زبان‌های کلاسیک تایپ‌شده، مانند پاسکال و آدا، غلبه می‌کند، که در آن عملگر x:= y مستلزم این است که نوع x و y یکسان باشند. این قانون بسیار سخت‌گیرانه است: استفاده از موجودیت‌هایی را که می‌توانند اشیایی از انواع مرتبط را نشان دهند (SAVINGS_ACCOUNT و CHECKING_ACCOUNT) ممنوع می‌کند. در ارث فقط نیاز داریم سازگاری نوع y با نوع x، به عنوان مثال، x از نوع ACCOUNT است، y SAVINGS_ACCOUNT است، و کلاس دوم از نسل اول است.

در عمل، یک زبان تایپ ایستا نیاز به پشتیبانی دارد ارث چندگانه. اتهامات شناخته شده تایپ استاتیکبه این دلیل که امکان تفسیرهای مختلف از اشیاء را نمی دهد. بنابراین، شیء DOCUMENT (سند) را می توان از طریق شبکه منتقل کرد، و بنابراین به وجود اجزای مرتبط با نوع MESSAGE (پیام) نیاز دارد. اما این انتقاد فقط برای زبان های محدود صادق است ارث واحد.


برنج. 17.2.

تطبیق پذیریلازم است، برای مثال، برای توصیف ساختارهای داده کانتینری انعطاف پذیر اما ایمن (به عنوان مثال فهرست کلاس [G] ...). بدون این مکانیسم تایپ استاتیکنیاز به اعلام کلاس های مختلف برای لیست هایی با انواع عناصر مختلف دارد.

در برخی موارد، تطبیق پذیری لازم است محدود کردن، که به شما امکان می دهد از عملیاتی استفاده کنید که فقط برای موجودیت های یک نوع عمومی اعمال می شود. اگر کلاس عمومی SORTABLE_LIST از مرتب‌سازی پشتیبانی می‌کند، به موجودیت‌هایی از نوع G نیاز دارد که G یک پارامتر عمومی است تا عملیات مقایسه داشته باشند. این با مرتبط کردن کلاسی با G که یک محدودیت عمومی را تعریف می کند به دست می آید - COMPARABLE:

کلاس SORTABLE_LIST ...

هر پارامتر عمومی SORTABLE_LIST واقعی باید از نوادگان کلاس COMPARABLE باشد که مؤلفه لازم را دارد.

مکانیسم ضروری دیگر این است تلاش برای تعیین تکلیف- دسترسی به اشیایی را که نرم افزار کنترل نمی کند، سازماندهی می کند. اگر y یک شی پایگاه داده یا یک شی است که از طریق شبکه به دست می آید، اپراتور x ?= y اگر y از نوع سازگار باشد، x را به مقدار y تنظیم می کند، یا اگر نباشد، x مقدار Void را می دهد. .

بیانیهبه عنوان بخشی از ایده Design by Contract، با کلاس‌ها و اجزای آن‌ها در قالب پیش‌شرط‌ها، پس‌شرط‌ها و متغیرهای کلاس، امکان توصیف محدودیت‌های معنایی را فراهم می‌کند که تحت پوشش قرار نمی‌گیرند. مشخصات نوع. زبان‌هایی مانند پاسکال و آدا دارای انواع محدوده‌ای هستند که می‌توانند برای مثال مقادیر موجودیت را بین 10 تا 20 محدود کنند، اما شما نمی‌توانید i را با همیشه دو برابر j منفی کنید. ثابت‌های کلاس به کمک می‌آیند، که به گونه‌ای طراحی شده‌اند که محدودیت‌های معرفی‌شده را به دقت منعکس کنند، مهم نیست که چقدر پیچیده هستند.

تبلیغات پین شدهبرای جلوگیری از تکرار کد در عمل مورد نیاز است. اعلام می کند y: مانند x، شما این تضمین را دریافت می کنید که y پس از هر نوع اعلان مکرر x در فرزند تغییر خواهد کرد. در غیاب این مکانیسم، توسعه‌دهندگان مرتباً دوباره اعلام می‌کنند و سعی می‌کنند انواع مختلف را ثابت نگه دارند.

اعلان های چسبنده یک مورد خاص از آخرین مکانیسم زبانی است که ما به آن نیاز داریم - کوواریانس، که بعداً به تفصیل مورد بحث قرار خواهد گرفت.

هنگام توسعه سیستم های نرم افزاری، در واقع، یک ویژگی دیگر مورد نیاز است که در خود محیط توسعه ذاتی است - کامپایل مجدد تدریجی سریع. وقتی سیستمی را می نویسید یا تغییر می دهید، می خواهید تأثیر تغییرات را در اسرع وقت ببینید. در تایپ استاتیکباید به کامپایلر زمان بدهید تا انواع را دوباره بررسی کند. روال های کامپایل سنتی مستلزم کامپایل مجدد کل سیستم (و مجلس او، و این روند می تواند به طرز دردناکی طولانی باشد، به خصوص با انتقال به سیستم های مقیاس بزرگ. این پدیده به بحثی به نفع تبدیل شده است تفسیر کردنسیستم‌هایی مانند محیط‌های اولیه Lisp یا Smalltalk که سیستم را بدون پردازش یا بدون بررسی نوع اجرا می‌کردند. حالا این بحث فراموش شده است. یک کامپایلر مدرن خوب تشخیص می دهد که کد از آخرین کامپایل چگونه تغییر کرده است و فقط تغییراتی را که پیدا می کند پردازش می کند.

"آیا نوزاد تیپ شده است"؟

هدف ما - سخت گیرانه تایپ استاتیک. به همین دلیل است که ما باید از هرگونه خلاء در «بازی با قوانین» خودداری کنیم، حداقل در صورت وجود آنها را به دقت شناسایی کنیم.

رایج ترین حفره در استاتیک زبان های تایپ شدهوجود دگرگونی هایی است که نوع موجودیت را تغییر می دهد. در زبان C و زبان های مشتق آن، آنها را "نوع ریخته گری" یا ریخته گری ( cast ) می نامند. علامت (OTHER_TYPE) x نشان می‌دهد که x توسط کامپایلر از نوع OTHER_TYPE تلقی می‌شود، مشروط به برخی محدودیت‌ها در انواع ممکن.

مکانیسم‌هایی مانند این محدودیت‌های بررسی نوع را دور می‌زنند. Casting در برنامه نویسی C، از جمله گویش ANSI C رایج است. حتی در C++، نوع ریخته گری، در حالی که کمتر رایج است، رایج و شاید ضروری باقی می ماند.

برای رعایت قوانین تایپ استاتیکاگر در هر زمان بتوان آنها را با ریخته گری دور زد چندان آسان نیست.

تایپ و لینک دادن

اگرچه به عنوان خواننده این کتاب، مطمئناً تایپ استاتیک را از استاتیک تشخیص خواهید داد الزام آورخوب، افرادی هستند که نمی توانند این کار را انجام دهند. این ممکن است تا حدی به دلیل تأثیر زبان اسمال تاک باشد که طرفدار آن است رویکرد پویابه هر دو مشکل و قادر به ایجاد این تصور غلط است که آنها راه حل یکسانی دارند. (ما در این کتاب استدلال می کنیم که ترکیب تایپ استاتیک و پیوند پویا برای ایجاد برنامه های قوی و انعطاف پذیر مطلوب است.)

تایپ و صحافی هر دو با معناشناسی ساختار پایه x.f (arg) سروکار دارند، اما به دو سوال متفاوت پاسخ می‌دهند:

تایپ و لینک دادن

  • یک سوال در مورد تایپ: چه زمانی باید مطمئن شویم که در زمان اجرا عملیاتی مطابق با f وجود خواهد داشت که برای شیء متصل به موجودیت x (با پارامتر arg) قابل اعمال است؟
  • سوال پیوند دادن: چه زمانی باید بدانیم که یک تماس معین چه عملیاتی را آغاز می کند؟

تایپ کردن به سوال در دسترس بودن پاسخ می دهد حداقل یکیعملیات، binding مسئول انتخاب است لازم است.

در چارچوب رویکرد شی:

  • مشکل تایپ مربوط به پلی مورفیسم: از زمان x در زمان اجرامی تواند اشیایی از چندین نوع مختلف را نشان دهد، باید مطمئن باشیم که عملیات نشان دهنده f , در دسترسدر هر یک از این موارد؛
  • مشکل الزام آور ایجاد شده است اعلامیه های مکرر: از آنجایی که یک کلاس می تواند اجزای ارثی را تغییر دهد، ممکن است دو یا چند عملیات وجود داشته باشند که ادعا می کنند f را در یک فراخوانی معین نشان می دهند.

هر دو مشکل را می توان هم به صورت پویا و هم به صورت استاتیک حل کرد. زبان های موجود هر چهار راه حل را ارائه می دهند.

و صحافی پویا در نماد ارائه شده در این کتاب تجسم یافته است.

به ویژگی زبان C ++ توجه کنید که از تایپ استاتیک پشتیبانی می کند، اگرچه به دلیل وجود نوع ریخته گری سخت نیست. پیوند استاتیک(پیش‌فرض)، پیوند پویا هنگام تعیین صریح مجازی ( مجازی) تبلیغات

دلیل انتخاب تایپ استاتیکو اتصال پویا واضح است. سوال اول این است: "چه زمانی از وجود اجزا مطلع خواهیم شد؟" - یک پاسخ ثابت را پیشنهاد می کند: هر چه زودتر بهتر"، که به معنی: در زمان کامپایل. سوال دوم، "از کدام مؤلفه استفاده شود؟" یک پاسخ پویا را نشان می دهد: " یکی که شما نیاز دارید"، - متناظر نوع پویاشی تعریف شده در زمان اجرا این تنها راه حل قابل قبول است اگر اتصال استاتیک و دینامیکی نتایج متفاوتی ایجاد کند.

در تایپ استاتیکاگر بتوان تضمین کرد که موجودیت my_aircraft در زمان اجرا به موجودیت my_aircraft متصل می شود، کامپایلر تماس را رد نمی کند. تکنیک اصلی برای دریافت ضمانت‌ها ساده است: اعلام اجباری my_aircraft مستلزم آن است که کلاس پایه نوع آن شامل چنین جزء باشد. بنابراین my_aircraft را نمی توان به عنوان AIRCRAFT اعلان کرد، زیرا هواپیمای دوم دارای ارابه فرود پایین تر در آن سطح نیست. هلیکوپترها، حداقل در مثال ما، نمی دانند چگونه ارابه فرود را آزاد کنند. اگر یک موجودیت را به عنوان اعلام کنیم سطح، - کلاس حاوی مؤلفه مورد نیاز - همه چیز خوب خواهد بود.

تایپ پویادر سبک اسمال تاک از شما می خواهد که منتظر تماس باشید و در زمان اجرای آن وجود کامپوننت مورد نظر را بررسی کنید. این رفتار برای نمونه‌های اولیه و پیشرفت‌های تجربی ممکن است، اما برای سیستم‌های صنعتی غیرقابل قبول است - در زمان پرواز خیلی دیر است که بپرسیم آیا ارابه فرود دارید یا خیر.

این مقاله در مورد تفاوت بین زبان های تایپ ایستا و تایپ پویا صحبت می کند، مفاهیم تایپ "قوی" و "ضعیف" را مورد بحث قرار می دهد و قدرت سیستم های تایپ را با هم مقایسه می کند. زبانهای مختلف. اخیراً حرکت واضحی به سمت سیستم‌های تایپ قوی‌تر و قدرتمندتر در برنامه‌نویسی صورت گرفته است، بنابراین درک آنچه گفته می‌شود هنگام صحبت در مورد انواع و تایپ مهم است.



نوع مجموعه ای از مقادیر ممکن است. یک عدد صحیح می تواند دارای مقادیر 0، 1، 2، 3 و غیره باشد. بولی می تواند درست یا نادرست باشد. شما می توانید نوع خود را ایجاد کنید، به عنوان مثال، نوع "DayFive"، که در آن مقادیر "give" و "5" امکان پذیر است و هیچ چیز دیگری. این رشته یا عدد نیست، یک نوع جدید و جداگانه است.


زبان های تایپ ایستا انواع متغیرها را محدود می کنند: برای مثال یک زبان برنامه نویسی ممکن است بداند که x یک عدد صحیح است. در این صورت برنامه نویس از انجام x = true منع می شود، کد نادرست خواهد بود. کامپایلر از کامپایل آن خودداری می کند، بنابراین ما حتی نمی توانیم این کد را اجرا کنیم. زبان دیگری که به صورت ایستا تایپ می‌شود ممکن است قابلیت‌های بیانی متفاوتی داشته باشد و هیچ سیستم نوع محبوبی نمی‌تواند نوع پنج بالا ما را بیان کند (اما بسیاری می‌توانند ایده‌های پیچیده‌تر دیگری را بیان کنند).


زبان های تایپ شده پویا مقادیر را با انواع برچسب گذاری می کنند: زبان می داند که 1 یک عدد صحیح است، 2 یک عدد صحیح است، اما نمی تواند بداند که x همیشه دارای یک عدد صحیح است.


زمان اجرا زبان این برچسب ها را در زمان های مختلف بررسی می کند. اگر بخواهیم دو مقدار را اضافه کنیم، می تواند بررسی کند که آیا آنها اعداد، رشته ها یا آرایه هستند. سپس این مقادیر را اضافه می کند، آنها را به هم می چسباند یا بسته به نوع آن خطا می دهد.

زبان های تایپ ایستا

زبان های ایستا در زمان کامپایل، قبل از اجرای برنامه، انواع برنامه را بررسی می کنند. هر برنامه ای که در آن تایپ ها قوانین زبان را زیر پا بگذارند، نادرست تلقی می شود. به عنوان مثال، بیشتر زبان های ثابت عبارت "a" + 1 را رد می کنند (C یک استثنا از این قانون است). کامپایلر می داند که "a" یک رشته و 1 یک عدد صحیح است و + فقط زمانی کار می کند که قسمت های چپ و راست از یک نوع باشند. بنابراین او نیازی به اجرای برنامه ندارد تا بفهمد مشکلی وجود دارد. هر عبارت در یک زبان تایپ ایستا از نوع خاصی است که بدون اجرای کد قابل تعیین است.


بسیاری از زبان های تایپ ایستا نیاز به علامت گذاری نوع دارند. تابع در Java public int add(int x, int y) دو عدد صحیح می گیرد و عدد صحیح سوم را برمی گرداند. سایر زبان‌های تایپ‌شده استاتیک ممکن است به طور خودکار نوع را تعیین کنند. همان تابع جمع در Haskell به این صورت است: x y = x + y را اضافه کنید. ما انواع را به زبان نمی گوییم، اما خودش می تواند آنها را تعریف کند زیرا می داند که + فقط روی اعداد کار می کند، بنابراین x و y باید اعداد باشند، بنابراین تابع add دو عدد را به عنوان آرگومان می گیرد.


این ماهیت "ایستا" سیستم نوع را کاهش نمی دهد. سیستم نوع Haskell به ایستا بودن، سختگیر بودن و قدرتمند بودن مشهور است و در همه این جبهه ها Haskell از جاوا جلوتر است.

زبان های تایپ پویا

زبان‌هایی که به صورت پویا تایپ می‌شوند نیازی به تعیین نوع ندارند، اما آن‌ها نیز یک نوع را تعریف نمی‌کنند. انواع متغیرها تا زمانی که مقادیر مشخصی در راه اندازی نداشته باشند شناخته نمی شوند. به عنوان مثال، یک تابع در پایتون


def f(x, y): x + y را برگردانید

می‌تواند دو عدد صحیح، رشته‌ها، لیست‌ها و غیره اضافه کند و تا زمانی که برنامه را اجرا نکنیم نمی‌توانیم دقیقاً بفهمیم چه اتفاقی می‌افتد. شاید f در نقطه ای با دو رشته و با دو عدد در نقطه دیگر فراخوانی شود. در این حالت، x و y حاوی مقادیری از انواع مختلف در زمان‌های مختلف خواهند بود. بنابراین، مقادیر در زبان‌های پویا دارای یک نوع هستند، اما متغیرها و توابع ندارند. مقدار 1 قطعا یک عدد صحیح است، اما x و y می توانند هر چیزی باشند.

مقایسه

اکثر زبان‌های پویا در صورت استفاده نادرست از انواع، با خطا مواجه می‌شوند (جاوا اسکریپت یک استثناء بدنام است؛ سعی می‌کند برای هر عبارتی مقداری را برگرداند، حتی زمانی که معنی ندارد). هنگام استفاده از زبان های تایپ شده پویا، حتی یک خطای ساده "a" + 1 می تواند در محیط تولید رخ دهد. زبان های استاتیک از چنین خطاهایی جلوگیری می کنند، اما البته میزان پیشگیری به قدرت سیستم نوع بستگی دارد.


زبان های ایستا و پویا بر اساس ایده های اساسی متفاوت در مورد صحت برنامه ساخته شده اند. در یک زبان پویا، "a" + 1 یک برنامه معتبر است: کد اجرا می شود و در زمان اجرا خطایی رخ می دهد. با این حال، در بیشتر زبان‌های تایپ‌شده استاتیک، عبارت "a" + 1 است یک برنامه نیست: کامپایل نمی شود و اجرا نمی شود. این یک کد معتبر نیست، درست مانند یک دسته از کاراکترهای تصادفی!&%^@*&%^@* یک کد معتبر نیست. این مفهوم اضافی درستی و نادرستی در زبان های پویا معادلی ندارد.

تایپ قوی و ضعیف

مفاهیم "قوی" و "ضعیف" بسیار مبهم هستند. در اینجا چند نمونه از کاربرد آنها آورده شده است:

    گاهی «قوی» به معنای «ایستا» است.
    این ساده است، اما بهتر است از اصطلاح "ایستا" استفاده کنید زیرا اکثر مردم از آن استفاده می کنند و آن را درک می کنند.

    گاهی اوقات "قوی" به معنای "تبدیل نوع ضمنی نیست".
    به عنوان مثال، جاوا اسکریپت به شما امکان می دهد "a" + 1 بنویسید که می توان آن را "تایپ ضعیف" نامید. اما تقریباً همه زبان ها سطحی از تبدیل ضمنی را ارائه می دهند که به شما امکان می دهد به طور خودکار از اعداد صحیح به اعداد ممیز شناور مانند 1 + 1.1 تبدیل کنید. در واقعیت، اکثر مردم از کلمه "قوی" برای تعیین مرز بین تحول قابل قبول و غیرقابل قبول استفاده می کنند. هیچ مرز پذیرفته شده ای وجود ندارد، همه آنها نادرست هستند و به نظر یک شخص خاص بستگی دارند.

    گاهی اوقات "قوی" به این معنی است که دور زدن قوانین سختگیرانه تایپ در زبان غیرممکن است.

  • گاهی اوقات "قوی" به معنای ایمن برای حافظه است.
    C مثالی از یک زبان ناامن حافظه است. اگر xs آرایه‌ای از چهار عدد باشد، C با خوشحالی xs یا xs را اجرا می‌کند و مقداری از حافظه را بلافاصله بعد از xs برمی‌گرداند.

بایستیم در اینجا آمده است که چگونه برخی از زبان ها این تعاریف را برآورده می کنند. همانطور که می بینید، فقط هاسکل از همه جهات به طور مداوم "قوی" است. بیشتر زبان ها چندان واضح نیستند.



("When as" در ستون "Implicit Conversions" به این معنی است که تقسیم بین قوی و ضعیف بستگی به این دارد که چه نوع تبدیلی را قابل قبول بدانیم).


غالباً اصطلاحات "قوی" و "ضعیف" به ترکیب نامشخصی از تعاریف مختلف بالا و تعاریف دیگری که در اینجا نشان داده نشده اند اشاره دارد. این همه سردرگمی باعث می شود که کلمات "قوی" و "ضعیف" عملاً بی معنی شوند. هنگامی که می خواهید از این اصطلاحات استفاده کنید، بهتر است توضیح دهید که دقیقاً منظور چیست. به عنوان مثال، می توانید بگویید "جاوا اسکریپت وقتی یک رشته به عدد اضافه می شود مقداری را برمی گرداند، اما پایتون یک خطا را برمی گرداند." در این صورت، ما انرژی خود را برای رسیدن به توافق بر سر معانی متعدد کلمه "قوی" هدر نمی دهیم. یا حتی بدتر: ما به دلیل اصطلاحات با سوء تفاهمات حل نشده مواجه می شویم.


در بیشتر موارد، اصطلاحات "قوی" و "ضعیف" در اینترنت، نظرات مبهم و بد تعریف افراد خاص است. از آنها برای اطلاق یک زبان به عنوان "بد" یا "خوب" استفاده می شود و این نظر به اصطلاح فنی تبدیل می شود.



تایپ قوی: سیستم تایپی که من آن را دوست دارم و با آن احساس راحتی می کنم.

تایپ ضعیف: سیستم تایپی که من را آزار می دهد یا با آن راحت نیستم.

تایپ تدریجی

آیا می توان انواع استاتیک را به زبان های پویا اضافه کرد؟ در برخی موارد، بله. در برخی دیگر دشوار یا غیرممکن است. واضح ترین مشکل مربوط به eval و سایر ویژگی های زبان پویا مشابه است. با انجام 1 + eval("2") در پایتون 3 به دست می آید. اما 1 + eval(read_from_the_network()) چه نتیجه ای دارد؟ این بستگی به آنچه در شبکه در زمان اجرا وجود دارد دارد. اگر عددی به دست آوردیم، عبارت صحیح است. اگر یک رشته، پس نه. هیچ راهی برای دانستن قبل از اجرا وجود ندارد، بنابراین تجزیه نوع به صورت ایستا غیرممکن است.


یک راه حل رضایت بخش در عمل این است که عبارت eval() را روی نوع Any تنظیم کنید، که مانند Object در برخی از زبان های برنامه نویسی شی گرا یا interface() در Go است: نوعی است که هر مقداری را برآورده می کند.


مقادیر نوع Any با هیچ چیز محدود نمی شود، بنابراین هیچ راهی برای سیستم نوع وجود ندارد که به ما در کد eval کمک کند. زبان هایی که دارای سیستم eval و type هستند باید با هر بار استفاده از eval از ایمنی نوع صرف نظر کنند.


برخی از زبان ها تایپ اختیاری یا تدریجی دارند: آنها به طور پیش فرض پویا هستند، اما اجازه می دهند برخی از حاشیه نویسی های ثابت اضافه شوند. پایتون اخیراً انواع اختیاری را اضافه کرده است. TypeScript یک افزونه به جاوا اسکریپت است که انواع اختیاری دارد. Flow تجزیه و تحلیل استاتیک کدهای قدیمی جاوا اسکریپت را انجام می دهد.


این زبان ها برخی از مزایای تایپ استاتیک را ارائه می دهند، اما هرگز تضمین مطلقی را که زبان های واقعاً ایستا انجام می دهند، ارائه نمی دهند. برخی از توابع به صورت استاتیک و برخی به صورت پویا تایپ می شوند. برنامه نویس همیشه باید تفاوت را بداند و مراقب باشد.

کامپایل کدهای تایپ شده استاتیک

هنگامی که کدهای تایپ شده به صورت ایستا کامپایل می شوند، ابتدا نحو بررسی می شود، مانند هر کامپایلری. سپس انواع بررسی می شود. این بدان معنی است که یک زبان ثابت می تواند ابتدا از یک خطای نحوی شکایت کند و پس از رفع آن از 100 خطای تایپی شکایت کند. رفع خطای نحوی آن 100 خطای تایپی را ایجاد نکرد. کامپایلر به سادگی هیچ راهی برای تشخیص خطاهای نوع تا زمانی که نحو تصحیح نشده بود نداشت.


کامپایلرهای زبان استاتیک معمولاً می توانند کد سریع تری نسبت به کامپایلرهای زبان پویا تولید کنند. برای مثال، اگر کامپایلر بداند که تابع add اعداد صحیح را می پذیرد، می تواند از دستورالعمل ADD بومی CPU استفاده کند. یک زبان پویا در زمان اجرا تایپ می‌کند، و بسته به انواع، یکی از توابع اضافه را انتخاب می‌کند (افزودن اعداد صحیح یا شناور، یا به هم پیوستن رشته‌ها یا شاید لیست‌ها؟) یا باید تصمیم بگیرد که خطا رخ داده است و انواع آن‌ها وجود ندارد. همخوانی داشتن. همه این بررسی ها زمان می برد. زبان‌های پویا از ترفندهای مختلفی برای بهینه‌سازی استفاده می‌کنند، مانند کامپایل JIT (Just-in-Time) که در آن کد در زمان اجرا پس از به‌دست‌آمدن تمام اطلاعات لازم در مورد انواع، دوباره کامپایل می‌شود. با این حال، هیچ زبان پویا نمی تواند با سرعت کدهای ثابت نوشته شده در زبانی مانند Rust برابری کند.

استدلال برای انواع استاتیک و دینامیک

طرفداران سیستم نوع استاتیک اشاره می کنند که بدون سیستم نوع، خطاهای ساده می تواند منجر به مشکلاتی در تولید شود. این البته درست است. هر کسی که از زبان پویا استفاده کرده باشد، دست اول این را تجربه کرده است.


طرفداران زبان‌های پویا اشاره می‌کنند که به نظر می‌رسد زبان‌های پویا راحت‌تر کدنویسی شوند. این قطعاً برای برخی از انواع کدهایی که هر از گاهی می نویسیم، مانند آن کد ارزشی، صادق است. این یک تصمیم بحث برانگیز برای کار معمولی است و در اینجا منطقی است که کلمه مبهم "آسان" را به خاطر بیاوریم. ریچ هیکی در مورد کلمه "آسان" و ارتباط آن با کلمه "ساده" کار بسیار خوبی انجام داد. پس از تماشای این گزارش متوجه خواهید شد که استفاده صحیح از کلمه «آسان» کار آسانی نیست. مراقب «سبکی» باشید.


مزایا و معایب سیستم‌های نوع استاتیک در مقابل پویا هنوز به خوبی درک نشده‌اند، اما قطعاً به زبان و مشکل خاصی که حل می‌شود بستگی دارد.


جاوا اسکریپت سعی می کند حتی اگر به معنای تبدیل غیرمعنا باشد (مانند "a" + 1 که "a1" را می دهد) ادامه دهد. از طرف دیگر پایتون سعی می کند محافظه کارانه عمل کند و اغلب خطاها را برمی گرداند، مانند مورد "a" + 1.


رویکردهای مختلفی با سطوح مختلفامنیت، اما پایتون و جاوا اسکریپت هر دو زبان هایی هستند که به صورت پویا تایپ می شوند.



Haskell به شما اجازه نمی دهد ابتدا یک عدد صحیح و یک شناور بدون تبدیل صریح اضافه کنید. C و Haskell هر دو با وجود این تفاوت‌های بزرگ به‌صورت استاتیک تایپ می‌شوند.


زبان های پویا و ایستا تنوع زیادی دارند. هر جمله غیرمجاز مانند "زبان های ایستا در مورد X بهتر از زبان های پویا هستند" تقریباً بی معنی است. این ممکن است برای زبان‌های خاص صادق باشد، اما بهتر است بگوییم «هسکل بهتر از پایتون است، وقتی صحبت از X می‌شود».

انواع سیستم های نوع استاتیک

بیایید نگاهی به دو مثال معروف از زبان های تایپ ایستا بیندازیم: Go و Haskell. هیچ نوع عمومی در سیستم نوع Go وجود ندارد، انواع با "پارامتر" از انواع دیگر. به عنوان مثال، می توانید نوع خود را برای لیست های MyList ایجاد کنید، که می تواند هر داده ای را که ما نیاز داریم ذخیره کند. ما می خواهیم بتوانیم MyList از اعداد صحیح، MyList از رشته ها و غیره را بدون تغییر ایجاد کنیم. منبعلیست من. کامپایلر باید مراقب تایپ باشد: اگر MyList از اعداد صحیح وجود داشته باشد و ما تصادفاً یک رشته به آن اضافه کنیم، کامپایلر باید برنامه را رد کند.


Go به طور خاص به گونه ای طراحی شده بود که تعریف انواعی مانند MyList غیرممکن بود. بهترین کاری که می توانید انجام دهید این است که یک MyList از "اینترفیس های خالی" ایجاد کنید: MyList می تواند شامل اشیا باشد، اما کامپایلر نوع آنها را نمی داند. هنگامی که ما اشیاء را از MyList بازیابی می کنیم، باید به کامپایلر بگوییم که چه نوع هستند. اگر بگوییم "در حال واکشی یک رشته هستم"، اما در واقع مقدار یک عدد است، خطای زمان اجرا وجود خواهد داشت، همانطور که در مورد زبان های پویا وجود دارد.


Go همچنین فاقد بسیاری از ویژگی های دیگر موجود در زبان های تایپ ایستا امروزی (یا حتی برخی از سیستم های دهه 1970) است. سازندگان Go دلایل خاص خود را برای این تصمیمات داشتند، اما نظرات افراد خارجی در این مورد گاهی اوقات می تواند تند به نظر برسد.


حالا بیایید با Haskell مقایسه کنیم که سیستم تایپ بسیار قدرتمندی دارد. اگر نوع روی MyList تنظیم شده باشد، نوع "فهرست اعداد" به سادگی MyList Integer است. Haskell از اضافه کردن تصادفی یک رشته به لیست جلوگیری می کند و اطمینان حاصل می کند که عنصری از لیست را در یک متغیر رشته قرار نمی دهیم.


Haskell می تواند ایده های بسیار پیچیده تری را مستقیماً با انواع بیان کند. به عنوان مثال، Num a => MyList a به معنای "MyList از مقادیری است که از یک نوع عدد هستند". این می تواند لیستی از اعداد صحیح، شناور یا اعداد اعشاریبا دقت ثابت، اما قطعا هرگز لیستی از رشته ها نخواهد بود که در زمان کامپایل بررسی می شود.


می توانید یک تابع افزودن بنویسید که با هر نوع عددی کار می کند. این تابع دارای نوع Num a => (a -> a -> a) خواهد بود. به این معنی:

  • a می تواند هر نوع عددی باشد (Num a =>).
  • تابع دو آرگومان از نوع a را می گیرد و نوع a را برمی گرداند (a -> a -> a).

آخرین نمونه. اگر نوع تابع String -> String باشد، یک رشته می گیرد و یک رشته را برمی گرداند. اما اگر یک String -> IO String باشد، مقداری I/O را نیز انجام می دهد. این می تواند دسترسی به دیسک، دسترسی به شبکه، خواندن از ترمینال و غیره باشد.


اگر تابعی دارای یک نوع باشد خیر IO، سپس می دانیم که هیچ عملیات I/O را انجام نمی دهد. به عنوان مثال، در یک برنامه وب، شما می توانید تشخیص دهید که آیا یک تابع، پایگاه داده را تنها با نگاه کردن به نوع آن تغییر می دهد یا خیر. هیچ زبان پویا و تقریباً هیچ زبان ثابتی نمی تواند این کار را انجام دهد. این یکی از ویژگی های زبان هایی با قوی ترین سیستم تایپ است.


در بیشتر زبان‌ها، برای یافتن چیزی که پایگاه داده را تغییر می‌دهد، باید با تابع و همه توابعی که از آنجا فراخوانی می‌شوند و غیره سر و کار داشته باشیم. این یک فرآیند خسته کننده است و به راحتی می توان اشتباه کرد. و سیستم نوع Haskell می تواند به سادگی و با اطمینان به این سوال پاسخ دهد.


این قدرت را با Go مقایسه کنید، که نمی تواند ایده ساده MyList را بیان کند، چه رسد به "عملکردی که دو آرگومان را می گیرد، هر دو عددی و از یک نوع، و I/O را انجام می دهد".


رویکرد Go نوشتن ابزارهای برنامه نویسی Go را آسان می کند (به ویژه اجرای کامپایلر می تواند ساده باشد). به علاوه، مفاهیم کمتری برای یادگیری وجود دارد. اینکه چگونه این مزایا با محدودیت های قابل توجه مقایسه می شوند یک موضوع ذهنی است. با این حال، نمی توان استدلال کرد که یادگیری Haskell سخت تر از Go است، و سیستم نوع Haskell بسیار قدرتمندتر است، و Haskell می تواند از کامپایل شدن بسیاری از انواع باگ ها جلوگیری کند.


Go و Haskell زبان های متفاوتی هستند که گروه بندی آنها در یک کلاس از "زبان های ثابت" ممکن است گمراه کننده باشد، حتی اگر این اصطلاح به درستی استفاده شود. از نظر مزایای امنیتی عملی، Go به زبان‌های پویا نزدیک‌تر از Haskell است.


از سوی دیگر، برخی از زبان های پویا ایمن تر از برخی از زبان های ایستا هستند. (به طور کلی پایتون بسیار ایمن تر از C در نظر گرفته می شود.) وقتی می‌خواهید در مورد زبان‌های ایستا یا پویا به‌عنوان گروه تعمیم دهید، فراموش نکنید تعداد زیادیتفاوت بین زبان ها

نمونه های خاص از تفاوت در قابلیت های سیستم های نوع

در سیستم های نوع قوی تر، می توانید محدودیت ها را در سطوح کوچکتر مشخص کنید. در اینجا چند نمونه آورده شده است، اما اگر نحو واضح نیست، تلفن را از آنها قطع نکنید.


در Go، می توانید بگویید "عملکرد add دو عدد صحیح می گیرد و یک عدد صحیح برمی گرداند".


func add(x int، y int) int (بازگشت x + y)

در Haskell، می توانید بگویید "یک تابع طول می کشد هرنوع عددی و تعدادی از همان نوع را برمی‌گرداند:


f:: Num a => a -> a -> a اضافه x y = x + y

در ادریس می توانید بگویید "تابع دو عدد صحیح می گیرد" و یک عدد صحیح برمی گرداند، اما آرگومان اول باید کمتر از آرگومان دوم باشد.


افزودن: (x: Nat) -> (y: Nat) -> (کوچکتر خودکار: LT x y) -> Nat اضافه x y = x + y

اگر بخواهید تابع add 2 1 را فراخوانی کنید، جایی که آرگومان اول بزرگتر از دومی است، کامپایلر برنامه را رد می کند. در زمان کامپایل. نوشتن برنامه ای که آرگومان اول بزرگتر از دومی باشد غیرممکن است. زبان کمیاب این قابلیت را دارد. در اکثر زبان‌ها، این بررسی در زمان اجرا اتفاق می‌افتد: چیزی شبیه به x >= y: raise SomeError() می‌نویسیم.


هیچ معادلی در Haskell برای نوع در مثال ادریس بالا وجود ندارد و در Go هیچ معادلی با مثال Haskell یا مثال ادریس وجود ندارد. در نتیجه، ادریس می‌تواند از بسیاری از باگ‌هایی که Haskell نمی‌تواند جلوگیری کند، و Haskell می‌تواند از بسیاری از باگ‌هایی که Go متوجه آن‌ها نمی‌شود جلوگیری کند. در هر دو مورد، شما نیاز دارید ویژگی های اضافیسیستم های تایپ که زبان را پیچیده تر می کند.

تایپ سیستم های برخی از زبان های ایستا

در اینجا فهرستی تقریبی از انواع سیستم های برخی از زبان ها به ترتیب افزایش قدرت آورده شده است. این فهرست به شما یک ایده کلی از قدرت سیستم ها می دهد، لازم نیست آن را به عنوان یک حقیقت مطلق در نظر بگیرید. زبان های جمع آوری شده در یک گروه می توانند بسیار متفاوت از یکدیگر باشند. هر نوع سیستمی ویژگی های خاص خود را دارد و بیشتر آنها بسیار پیچیده هستند.

  • C (1972)، برو (2009): این سیستم ها بدون پشتیبانی از انواع ژنریک اصلا قدرتمند نیستند. نمی توان نوع MyList را به معنای «فهرست اعداد صحیح»، «لیست رشته ها» و غیره تنظیم کرد. در عوض، شما باید یک "فهرست از مقادیر بدون علامت" را انجام دهید. برنامه نویس باید هر بار که یک رشته از لیست بازیابی می شود، به صورت دستی بگوید "This is a list of strings" و این می تواند منجر به خطای زمان اجرا شود.
  • جاوا (1995)، سی شارپ (2000): هر دو زبان از کلیات پشتیبانی می کنند، بنابراین می توانید MyList را بگویید و لیستی از رشته هایی که کامپایلر درباره آنها می داند و می تواند بر خلاف قوانین نوع اعمال کند، دریافت کنید. عناصر موجود در لیست از نوع String خواهند بود، کامپایلر قوانین را هنگام کامپایل کردن طبق معمول اعمال می کند، بنابراین احتمال خطاهای زمان اجرا کمتر است.
  • هاسکل (1990)، رست (2010)، سویفت (2014): همه این زبان ها دارای چندین ویژگی پیشرفته از جمله انواع عمومی، انواع داده های جبری (ADT) و کلاس های نوع یا چیزی مشابه (به ترتیب انواع کلاس، صفات و پروتکل ها) هستند. Rust و Swift از Haskell محبوب تر هستند و توسط سازمان های بزرگ (به ترتیب Mozilla و Apple) تبلیغ می شوند.
  • آگدا (2007)، ادریس (2011): این زبان‌ها از انواع وابسته پشتیبانی می‌کنند و به شما امکان می‌دهند تا انواعی مانند "یک تابع که دو عدد صحیح x و y را می گیرد، جایی که y بزرگتر از x است" ایجاد کنید. حتی محدودیت "y بزرگتر از x" نیز در زمان کامپایل اعمال می شود. وقتی اجرا می‌شود، مهم نیست چه اتفاقی می‌افتد، y هرگز کمتر یا مساوی x نخواهد بود. ویژگی های بسیار ظریف اما مهم سیستم را می توان به صورت ایستا در این زبان ها بررسی کرد. تعداد بسیار کمی از برنامه نویسان آنها را مطالعه می کنند، اما آنها بسیار مشتاق این زبان هستند.

حرکت واضحی به سمت سیستم‌های نوع قوی‌تر وجود دارد، به‌ویژه زمانی که با محبوبیت زبان‌ها سنجیده می‌شود و نه این واقعیت که زبان‌ها وجود دارند. یک استثنای قابل توجه Go است، که توضیح می دهد که چرا بسیاری از طرفداران زبان های ایستا آن را یک گام به عقب می دانند.


گروه دو (جاوا و سی شارپ) زبان‌های رایج، بالغ و پرکاربرد هستند.


گروه سه با حمایت زیادی از موزیلا (Rust) و اپل (Swift) در آستانه ورود به جریان اصلی است.


گروه چهار (ادریس و آگدا) از جریان اصلی دور هستند، اما ممکن است در طول زمان تغییر کند. زبان های گروه سه ده سال پیش با جریان اصلی فاصله داشتند.

این مقاله حاوی حداقل چیزهایی است که فقط باید در مورد تایپ بدانید تا تایپ پویا را بد، Lisp را یک زبان تایپ نشده و C را زبانی با شدت تایپ نخوانید.

نسخه کامل است توصیف همراه با جزئیاتانواع تایپ، چاشنی نمونه کد، لینک به زبان های برنامه نویسی محبوب و تصاویر نمایشی.

توصیه می کنم ابتدا نسخه کوتاه مقاله و سپس در صورت تمایل نسخه کامل آن را مطالعه کنید.

نسخه کوتاه

زبان های برنامه نویسی تایپ معمولاً به دو اردوی بزرگ تقسیم می شوند - تایپ شده و تایپ نشده (تایپ نشده). اولی شامل C، Python، Scala، PHP و Lua است، در حالی که دومی شامل زبان اسمبلی، Forth و Brainfuck است.

از آنجایی که "تایپ بدون تایپ" ذاتاً به اندازه چوب پنبه ساده است، به هیچ نوع دیگری تقسیم نمی شود. اما زبان های تایپ شده به چند دسته دیگر همپوشانی تقسیم می شوند:

  • تایپ استاتیک / پویا استاتیک با این واقعیت تعیین می شود که انواع نهایی متغیرها و توابع در زمان کامپایل تنظیم می شوند. آن ها در حال حاضر کامپایلر 100% مطمئن است که کدام نوع کجاست. در تایپ پویا، همه انواع در زمان اجرا تعیین می شوند.

    مثال ها:
    استاتیک: C، جاوا، سی شارپ؛
    پویا: پایتون، جاوا اسکریپت، روبی.

  • تایپ قوی / ضعیف (همچنین گاهی اوقات قوی / شل نامیده می شود). تایپ قوی با این واقعیت متمایز می شود که زبان اجازه اختلاط انواع مختلف در عبارات را نمی دهد و تبدیل های ضمنی خودکار را انجام نمی دهد، برای مثال، نمی توانید مجموعه ای را از یک رشته کم کنید. زبان های ضعیف تایپ شده بسیاری از تبدیل های ضمنی را به صورت خودکار انجام می دهند، حتی اگر از دست دادن دقت یا تبدیل مبهم باشد.

    مثال ها:
    قوی: جاوا، پایتون، هسکل، لیسپ؛
    ضعیف: C، جاوا اسکریپت، ویژوال بیسیک، PHP.

  • تایپ صریح / ضمنی زبان‌های تایپ صریح از این جهت متفاوت هستند که نوع متغیرها / توابع / آرگومان‌های جدید باید به صراحت تنظیم شوند. بر این اساس، زبان‌های دارای تایپ ضمنی این وظیفه را به کامپایلر/مفسر منتقل می‌کنند.

    مثال ها:
    صریح: C++، D، C#
    ضمنی: PHP، Lua، JavaScript

همچنین باید توجه داشت که همه این دسته‌ها با هم تلاقی می‌کنند، به عنوان مثال، زبان C دارای تایپ واضح ضعیف استاتیک است و زبان پایتون- ضمنی قوی پویا.

با این وجود، هیچ زبانی با تایپ ثابت و پویا به طور همزمان وجود ندارد. اگرچه به آینده نگاه می کنم، می گویم که من اینجا دراز می کشم - آنها واقعاً وجود دارند، اما بعداً در مورد آن بیشتر می شود.

نسخه دقیق

اگر نسخه کوتاه برای شما کافی نیست، خوب است. جای تعجب نیست که مفصل نوشتم؟ نکته اصلی این است که در نسخه کوتاه، به سادگی غیرممکن بود که همه اطلاعات مفید و جالب را در نظر بگیریم، و جزئیات آن احتمالاً برای همه طولانی خواهد بود که آن را بدون زحمت بخوانند.

تایپ بدون تایپ

در زبان های برنامه نویسی تایپ نشده، همه موجودیت ها فقط دنباله ای از بیت ها با طول های مختلف در نظر گرفته می شوند.

تایپ بدون تایپ معمولاً در زبان‌های سطح پایین (زبان اسمبلر، Forth) و باطنی (Brainfuck، HQ9، Piet) ذاتی است. با این حال، در کنار معایب آن، مزایایی نیز دارد.

مزایای
  • به شما امکان می‌دهد در سطح بسیار پایین بنویسید و کامپایلر/مفسر با هیچ نوع بررسی تداخلی نخواهد داشت. شما در انجام هر عملیاتی بر روی هر نوع داده آزاد هستید.
  • کد به دست آمده معمولا کارآمدتر است.
  • شفافیت دستورالعمل ها با دانش زبان، معمولاً شکی وجود ندارد که این یا آن کد چیست.
معایب
  • پیچیدگی. اغلب نیاز به نمایش مقادیر پیچیده مانند لیست ها، رشته ها یا ساختارها وجود دارد. این ممکن است باعث ناراحتی شود.
  • بدون چک هر اقدام بی‌معنا، مانند کم کردن اشاره‌گر به آرایه از یک کاراکتر، کاملاً طبیعی تلقی می‌شود که مملو از خطاهای ظریف است.
  • سطح پایین انتزاع کار با هر نوع داده پیچیده هیچ تفاوتی با کار با اعداد ندارد که البته مشکلات زیادی را ایجاد خواهد کرد.
تایپ بدون تایپ قوی؟

بله، این وجود دارد. به عنوان مثال، در زبان اسمبلی (برای معماری x86 / x86-64، من دیگران را نمی شناسم) اگر بخواهید داده ها را از ثبات rax (64 بیت) در ثبات cx بارگذاری کنید، نمی توانید برنامه ای را جمع آوری کنید (16). بیت).

mov cx, eax ; خطای زمان مونتاژ

پس معلوم می شود که اسمبلر هنوز تایپ دارد؟ فکر می کنم این بررسی ها کافی نیست. و البته نظر شما فقط به شما بستگی دارد.

تایپ استاتیک و پویا

اصلی‌ترین چیزی که تایپ استاتیک (استاتیک) را از پویا (دینامیک) متمایز می‌کند این است که تمام بررسی‌های نوع در زمان کامپایل انجام می‌شود و نه در زمان اجرا.

برخی از مردم ممکن است فکر کنند که تایپ استاتیک بسیار محدود کننده است (در واقع اینطور است، اما مدتهاست که با کمک برخی تکنیک ها از شر آن خلاص شده است). برای برخی، زبان‌های تایپ پویا با آتش بازی می‌کنند، اما چه ویژگی‌هایی آن‌ها را متمایز می‌کند؟ آیا هر دو گونه شانسی برای وجود دارند؟ اگر نه، چرا این همه زبان تایپ شده به صورت ایستا و پویا وجود دارد؟

بیایید آن را بفهمیم.

مزایای تایپ استاتیک
  • بررسی تایپ فقط یک بار در زمان کامپایل انجام می شود. و این بدان معنی است که ما نیازی به این نداریم که دائماً بفهمیم که آیا می خواهیم یک عدد را بر یک رشته تقسیم کنیم (و خطا ایجاد کنیم یا تبدیل انجام دهیم).
  • سرعت اجرا. از نکته قبل واضح است که زبان های تایپ ایستا تقریباً همیشه سریعتر از تایپ های پویا هستند.
  • تحت برخی شرایط اضافی، به شما امکان می دهد خطاهای بالقوه را در مرحله تدوین شناسایی کنید.
مزایای تایپ پویا
  • سهولت ایجاد مجموعه های جهانی - انبوهی از همه چیز و همه چیز (به ندرت چنین نیازی ایجاد می شود، اما زمانی که ایجاد می شود تایپ پویا کمک می کند).
  • سهولت در توصیف الگوریتم های تعمیم یافته (به عنوان مثال، مرتب سازی آرایه، که نه تنها در لیستی از اعداد صحیح، بلکه در لیستی از اعداد واقعی و حتی در لیستی از رشته ها نیز کار می کند).
  • یادگیری آسان - زبان های تایپ پویا معمولاً برای شروع برنامه نویسی بسیار خوب هستند.

برنامه نویسی عمومی

خوب، مهم ترین استدلال برای تایپ پویا، راحتی توصیف الگوریتم های عمومی است. بیایید یک مشکل را تصور کنیم - ما به یک تابع جستجو برای چندین آرایه (یا لیست) نیاز داریم - آرایه ای از اعداد صحیح، آرایه ای از واقعی ها و آرایه ای از کاراکترها.

چگونه می خواهیم آن را حل کنیم؟ بیایید آن را به 3 زبان مختلف حل کنیم: یکی با تایپ پویا و دو تا با تایپ ایستا.

من یکی از ساده ترین الگوریتم های جستجو را انتخاب می کنم - شمارش. تابع عنصر جستجو شده، آرایه (یا لیست) خود را دریافت می کند و شاخص عنصر را برمی گرداند، یا اگر عنصر پیدا نشد - (-1).

راه حل دینامیک (پایتون):

Def find(required_element, list): برای (شاخص، عنصر) در enumerate(list): اگر عنصر == عنصر_نیازمند: بازگشت شاخص بازگشت (-1)

همانطور که می بینید، همه چیز ساده است و هیچ مشکلی با این واقعیت وجود ندارد که لیست می تواند شامل اعداد زوج، لیست های زوج باشد، حتی اگر هیچ آرایه دیگری وجود نداشته باشد. خیلی خوب. بیایید جلوتر برویم - همان مشکل را در C حل کنید!

محلول استاتیک (C):

int بدون علامت find_int(int require_element، آرایه int، اندازه int بدون علامت) ( for (unsigned int i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

خوب، هر تابع به طور جداگانه شبیه به نسخه پایتون است، اما چرا سه وجود دارد؟ آیا برنامه نویسی استاتیک از بین رفته است؟

بله و خیر. چندین تکنیک برنامه نویسی وجود دارد که اکنون یکی از آنها را بررسی می کنیم. نام آن برنامه نویسی عمومی است و زبان سی پلاس پلاس به خوبی از آن پشتیبانی می کند. بیایید نگاهی به نسخه جدید بیندازیم:

راه حل ایستا (برنامه نویسی عمومی، C++):

قالب int بدون علامت (T require_element، std:: vector آرایه) ( برای (int بدون علامت i = 0؛ i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

خوب! خیلی پیچیده تر از نسخه پایتون به نظر نمی رسد، و نوشتن زیادی لازم نیست. علاوه بر این، ما پیاده سازی را برای همه آرایه ها دریافت کردیم، نه فقط 3 مورد نیاز برای حل مشکل!

به نظر می رسد این نسخه دقیقاً همان چیزی است که ما نیاز داریم - ما هم از مزایای تایپ استاتیک و هم برخی از مزایای پویا برخورداریم.

خیلی خوب است که حتی ممکن است، اما می تواند حتی بهتر هم باشد. اولا، برنامه نویسی عمومی می تواند راحت تر و زیباتر باشد (به عنوان مثال، در Haskell). ثانیاً، علاوه بر برنامه‌نویسی عمومی، می‌توانید از چندشکلی (نتیجه بدتر)، اضافه بار تابع (به طور مشابه) یا ماکروها نیز استفاده کنید.

استاتیک در دینامیک

همچنین لازم به ذکر است که بسیاری از زبان های استاتیک امکان تایپ پویا را می دهند، به عنوان مثال:

  • سی شارپ از شبه نوع پویا پشتیبانی می کند.
  • F# از قند نحوی به شکل عملگر ? پشتیبانی می کند که می تواند برای تقلید از تایپ پویا استفاده شود.
  • Haskell - تایپ پویا توسط ماژول Data.Dynamic ارائه می شود.
  • دلفی - از طریق یک نوع خاص Variant.

همچنین، برخی از زبان‌های تایپ شده پویا به شما امکان می‌دهند از تایپ استاتیک استفاده کنید:

  • Common Lisp - اعلان های نوع.
  • پرل - از نسخه 5.6، نسبتاً محدود است.

تایپ قوی و ضعیف

زبان های با تایپ قوی اجازه اختلاط موجودیت های انواع مختلف در عبارات را نمی دهند و هیچ تبدیل خودکاری را انجام نمی دهند. به آنها "زبان های قوی تایپ" نیز می گویند. اصطلاح انگلیسی برای این تایپ قوی است.

برعکس، زبان های ضعیف تایپ شده، برنامه نویس را تشویق می کنند تا انواع مختلف را در یک عبارت ترکیب کند و خود کامپایلر همه چیز را به یک نوع تبدیل می کند. به آنها "زبان های تایپ ضعیف" نیز می گویند. اصطلاح انگلیسی برای این تایپ ضعیف است.

تایپ ضعیف اغلب با تایپ پویا اشتباه گرفته می شود که کاملا اشتباه است. یک زبان تایپ شده پویا می تواند هم ضعیف و هم قوی باشد.

با این حال، افراد کمی به سختگیری تایپ اهمیت می دهند. اغلب گفته می شود که اگر زبان به صورت ایستا تایپ شده باشد، می توانید تعداد زیادی از آنها را بگیرید خطاهای احتمالیهنگام تدوین به شما دروغ می گویند!

زبان باید تایپ قوی نیز داشته باشد. در واقع، اگر کامپایلر به جای گزارش یک خطا، صرفاً یک رشته را به یک عدد اضافه کند، یا حتی بدتر، رشته دیگری را از یک آرایه کم کند، چه فایده ای برای ما دارد که همه «بررسی» انواع در مرحله کامپایل باشد؟ درست است - تایپ استاتیک ضعیف حتی بدتر از تایپ پویا قوی است! (خب این نظر منه)

پس چرا تایپ ضعیف اصلاً مزیتی ندارد؟ شاید اینطور به نظر برسد، اما علیرغم اینکه من طرفدار قوی تایپ قوی هستم، باید قبول کنم که تایپ ضعیف مزایایی هم دارد.

میخوای بدونی کدومشون؟

مزایای تایپ قوی
  • قابلیت اطمینان - به جای رفتار نادرست، یک استثنا یا خطای کامپایل دریافت خواهید کرد.
  • سرعت - به جای تبدیل های ضمنی، که می تواند بسیار گران باشد، با تایپ قوی، باید آنها را به صراحت بنویسید، که باعث می شود برنامه نویس حداقل متوجه شود که این قطعه کد می تواند کند باشد.
  • درک نحوه عملکرد برنامه - دوباره، به جای ریختن نوع ضمنی، برنامه نویس همه چیز را خودش می نویسد، به این معنی که تقریباً می فهمد که مقایسه یک رشته و یک عدد به خودی خود اتفاق نمی افتد و نه با جادو.
  • یقین - وقتی تبدیل‌ها را با دست می‌نویسید، دقیقاً می‌دانید که چه چیزی و به چه چیزی تبدیل می‌کنید. همچنین، همیشه خواهید فهمید که چنین تبدیل هایی می تواند منجر به از دست دادن دقت و نتایج نادرست شود.
مزایای تایپ ضعیف
  • راحتی استفاده از عبارات ترکیبی (مثلاً از اعداد صحیح و واقعی).
  • انتزاع از تایپ و تمرکز بر روی کار.
  • مختصر بودن رکورد.

بسیار خوب، ما متوجه شدیم، معلوم است که تایپ ضعیف نیز مزایایی دارد! آیا راهی برای انتقال مزایای تایپ ضعیف به تایپ قوی وجود دارد؟

معلوم است که حتی دو نفر هستند.

ریخته‌گری از نوع ضمنی، در موقعیت‌های بدون ابهام و بدون از دست دادن داده‌ها

عجب ... پاراگراف بسیار طولانی است. اجازه دهید بیشتر آن را به "تبدیل ضمنی محدود" مخفف کنم، بنابراین وضعیت بدون ابهام و از دست دادن داده به چه معناست؟

وضعیت بدون ابهام، تبدیل یا عملیاتی است که ماهیت آن بلافاصله روشن می شود. به عنوان مثال، جمع دو عدد یک وضعیت بدون ابهام است. اما تبدیل یک عدد به یک آرایه اینطور نیست (شاید آرایه ای از یک عنصر ایجاد شود، شاید آرایه ای با چنین طولی که به طور پیش فرض با عناصر پر شده باشد، و شاید یک عدد به یک رشته و سپس به یک آرایه تبدیل شود. از شخصیت ها).

از دست دادن داده ها حتی ساده تر است. اگر عدد واقعی 3.5 را به یک عدد صحیح تبدیل کنیم، مقداری از داده ها را از دست خواهیم داد (در واقع، این عملیات نیز مبهم است - چگونه گرد کردن انجام می شود؟ رو به بالا؟ پایین؟ رها کردن قسمت کسری؟).

تبدیل در شرایط مبهم و تبدیل با از دست دادن داده بسیار بسیار بد است. هیچ چیز بدتر از این در برنامه نویسی وجود ندارد.

اگر باور ندارید، زبان PL/I را یاد بگیرید یا حتی مشخصات آن را جستجو کنید. دارای قوانین تبدیل بین تمام انواع داده ها است! این فقط جهنم است!

خوب، اجازه دهید در مورد تبدیل ضمنی محدود به یاد داشته باشیم. آیا چنین زبان هایی وجود دارد؟ بله، به عنوان مثال در پاسکال می توانید یک عدد صحیح را به یک عدد واقعی تبدیل کنید، اما نه برعکس. همچنین مکانیسم های مشابهی در C#، Groovy و Common Lisp وجود دارد.

خوب، من گفتم راه دیگری برای به دست آوردن چند مزیت تایپ ضعیف در یک زبان قوی وجود دارد. و بله وجود دارد و چندشکلی سازنده نامیده می شود.

من آن را با استفاده از زبان فوق العاده Haskell به عنوان مثال توضیح خواهم داد.

سازنده‌های چند شکلی در نتیجه مشاهده این موضوع به وجود آمدند که تبدیل‌های ضمنی ایمن اغلب هنگام استفاده از حروف عددی مورد نیاز است.

به عنوان مثال، در عبارت pi + 1، شما نمی خواهید pi + 1.0 یا pi + float(1) بنویسید. من می خواهم فقط pi + 1 بنویسم!

و این در Haskell انجام می شود، به لطف این واقعیت که 1 تحت اللفظی ندارد نوع خاص. نه کل است، نه واقعی و نه پیچیده. این فقط یک عدد است!

در نتیجه، هنگام نوشتن یک تابع ساده مجموع x y، که تمام اعداد را از x به y ضرب می کند (با افزایش 1)، چندین نسخه را به طور همزمان دریافت می کنیم - مجموع برای اعداد صحیح، مجموع برای واقعی، مجموع برای گویا، مجموع برای مختلط. اعداد و حتی مجموع تمام آن انواع عددی که خودتان تعریف کرده اید.

البته این تکنیک فقط در صورت استفاده از عبارات مختلط با حروف عددی صرفه جویی می کند و این فقط نوک کوه یخ است.

بنابراین، می‌توان گفت که بهترین راه، ایجاد تعادل در آستانه، بین تایپ قوی و ضعیف خواهد بود. اما تا کنون، هیچ زبانی تعادل کاملی ندارد، بنابراین من بیشتر به سمت زبان‌های با تایپ قوی (مانند Haskell، Java، C#، Python) تمایل دارم تا زبان‌های با تایپ ضعیف (مانند C، JavaScript، Lua، PHP). .

تایپ صریح و ضمنی

یک زبان صریح تایپ شده از برنامه نویس می خواهد که انواع متغیرها و توابعی را که اعلام می کند مشخص کند. اصطلاح انگلیسی برای این کار تایپ صریح است.

از سوی دیگر، یک زبان به طور ضمنی تایپ شده، شما را دعوت می کند تا انواع را فراموش کنید و کار استنتاج نوع را به کامپایلر یا مفسر بسپارید. اصطلاح انگلیسی برای این، تایپ ضمنی است.

در ابتدا ممکن است تصور شود که تایپ ضمنی معادل تایپ پویا است و تایپ صریح معادل تایپ استاتیک است، اما بعداً خواهیم دید که اینطور نیست.

آیا برای هر نوع مزیت هایی وجود دارد و دوباره، آیا ترکیبی از آنها وجود دارد و آیا زبان هایی وجود دارد که از هر دو روش پشتیبانی کند؟

مزایای تایپ صریح
  • داشتن یک امضا برای هر تابع (به عنوان مثال، int add(int، int)) تعیین اینکه تابع چه کاری انجام می دهد را آسان می کند.
  • برنامه نویس فوراً می نویسد که چه نوع مقادیری را می توان در یک متغیر خاص ذخیره کرد که نیاز به یادآوری آن را از بین می برد.
مزایای تایپ ضمنی
  • خلاصه - def add(x, y) به وضوح کوتاهتر از int add(int x, int y) است.
  • مقاومت در برابر تغییر. به عنوان مثال، اگر در یک تابع، متغیر موقت از همان نوع آرگومان ورودی بود، در زبانی که صریحاً تایپ می شود، زمانی که نوع آرگومان ورودی تغییر می کند، نوع متغیر موقت نیز باید تغییر کند.

خب، به نظر می‌رسد که هر دو رویکرد هم مزایا و هم معایب دارند (و چه کسی انتظار دیگری داشت؟)، پس بیایید به دنبال راه‌هایی برای ترکیب این دو رویکرد باشیم!

تایپ صریح با انتخاب

زبان هایی با تایپ ضمنی به صورت پیش فرض و امکان تعیین نوع مقادیر در صورت لزوم وجود دارد. کامپایلر به طور خودکار نوع واقعی عبارت را استنتاج می کند. یکی از این زبان ها هاسکل است، اجازه دهید یک مثال ساده برای شما بیاورم:

بدون افزودن نوع صریح (x, y) = x + y -- نوع صریح افزودن:: (عدد صحیح، عدد صحیح) -> افزودن عدد صحیح (x, y) = x + y

توجه: من عمداً از یک تابع uncurried استفاده کردم و همچنین عمداً یک امضای خصوصی به جای اضافه کردن عمومی تر نوشتم:: (Num a) -> a -> a -> a, زیرا می خواستم این ایده را بدون توضیح نحو هسکل نشان دهم.

هوم همانطور که می بینیم بسیار زیبا و کوتاه است. ورودی تابع فقط 18 کاراکتر در هر خط، با احتساب فاصله!

با این حال، استنتاج نوع خودکار بسیار مشکل است، و حتی در زبان جالبی مانند Haskell، گاهی اوقات با شکست مواجه می شود. (به عنوان مثال محدودیت تک شکلی است)

آیا زبان هایی با تایپ صریح به طور پیش فرض و تایپ ضمنی بر حسب ضرورت وجود دارد؟ کن
مطمئنا.

تایپ ضمنی با انتخاب

استاندارد جدید زبان C++ به نام C++11 (که قبلاً C++0x نامیده می شد)، کلیدواژه خودکار را معرفی کرد که به کامپایلر اجازه می دهد تا نوع را از متن استنتاج کند:

بیایید مقایسه کنیم: // مشخصات نوع دستی unsigned int a = 5; بدون علامت int b = a + 3; // استنتاج نوع خودکار بدون علامت int a = 5; خودکار b = a + 3;

بد نیست. اما رکورد چندان کاهش نیافته است. بیایید یک مثال با تکرارکننده ها ببینیم (اگر متوجه نشدید، نترسید، نکته اصلی این است که رکورد به دلیل خروجی خودکار بسیار کاهش می یابد):

// تایپ دستی std::vector vec = randomVector(30); برای (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // نوع خودکار استنتاج auto vec = randomVector (سی)؛ برای (auto it = vec.cbegin(); ...) (...)

وای! در اینجا مخفف آن است. بسیار خوب، اما آیا می توان کاری را با روح Haskell انجام داد، جایی که نوع بازگشت به انواع آرگومان ها بستگی دارد؟

باز هم، به لطف کلمه کلیدی decltype در ترکیب با خودکار، پاسخ مثبت است:

// نوع دستی int divide(int x, int y) ( ... ) // Automatic type deduction auto divide(int x, int y) -> decltype(x / y) ( ... )

این شکل از نشانه گذاری ممکن است عالی به نظر نرسد، اما وقتی با کلیات (الگوها / ژنریک ها) ترکیب می شود، تایپ ضمنی یا استنتاج نوع خودکار معجزه می کند.

برخی از زبان های برنامه نویسی بر اساس این طبقه بندی

فهرست کوتاهی از زبان‌های پرطرفدار ارائه می‌دهم و نحوه دسته‌بندی آنها را در هر دسته «تایپ» می‌نویسم.

جاوا اسکریپت - پویا / ضعیف / ضمنی Ruby - پویا / قوی / پایتون ضمنی - پویا / قوی / ضمنی جاوا - استاتیک / قوی / واضح PHP - پویا / ضعیف / ضمنی C - استاتیک / ضعیف / واضح C ++ - ایستا / نیمه قوی / پرل صریح - پویا / ضعیف / هدف ضمنی-C - استاتیک / ضعیف / واضح سی شارپ - ایستا / قوی / هاسکل آشکار - استاتیک / قوی / ضمنی مشترک - پویا / قوی / ضمنی

شاید جایی اشتباه کردم مخصوصاً با CL و PHP و Obj-C اگر نظر دیگری در مورد زبان دارید در نظرات بنویسید.

تایپ - تکالیف موجودات اطلاعاتی را تایپ کنید.

رایج ترین انواع داده های اولیه عبارتند از:

  • عددی
  • نمادین
  • منطقی

وظایف اصلی سیستم نوع داده:

  • امنیت
    هر عملیات برای به دست آوردن آرگومان هایی دقیقاً از انواعی که برای آن در نظر گرفته شده است بررسی می شود.
  • بهینه سازی
    بر اساس نوع، روش ذخیره سازی کارآمد و الگوریتم هایی برای پردازش آن انتخاب می شود.
  • مستندات
    بر مقاصد برنامه نویس تاکید شده است.
  • انتزاع - مفهوم - برداشت
    استفاده از انواع داده های سطح بالا به برنامه نویس اجازه می دهد تا مقادیر را به عنوان موجودیت های سطح بالا به جای مجموعه ای از بیت ها در نظر بگیرد.

طبقه بندی

طبقه بندی های زیادی برای تایپ های زبان برنامه نویسی وجود دارد، اما اصلی ترین آنها تنها 3 طبقه بندی هستند:

تایپ استاتیک / پویا

Static - تخصیص و بررسی نوع در زمان کامپایل انجام می شود. انواع داده ها با متغیرها مرتبط هستند، نه مقادیر خاص. تایپ استاتیک به شما امکان می دهد تا خطاهای تایپی را که در شاخه های منطق برنامه به ندرت استفاده می شود در مرحله کامپایل پیدا کنید.

تایپ پویا برعکس تایپ استاتیک است. در تایپ پویا، همه انواع در زمان اجرا تعیین می شوند.

تایپ پویا امکان نرم‌افزار انعطاف‌پذیرتر را فراهم می‌کند، البته به قیمت خطاهای تایپی بیشتر. تست واحد در توسعه اهمیت ویژه ای دارد نرم افزاردر زبان های برنامه نویسی با تایپ پویا، زیرا این تنها راه برای یافتن خطاهای تایپی است که در شاخه های منطق برنامه به ندرت استفاده می شود.

تایپ پویا

VarluckyNumber = 777; var siteName = "Tyapk"; // دلالت بر یک عدد، نوشتن یک رشته var wrongNumber = "999";

تایپ استاتیک

اجازه دهید luckyNumber: عدد = 777; let siteName: string = "Tyapk"; // خطا می دهد let wrongNumber: number = "999";

  • استاتیک: جاوا، سی شارپ، TypeScript.
  • پویا: پایتون، روبی، جاوا اسکریپت.

تایپ صریح / ضمنی

زبان‌های تایپ صریح از این جهت متفاوت هستند که نوع متغیرها / توابع / آرگومان‌های جدید باید به صراحت تنظیم شوند. بر این اساس، زبان‌های دارای تایپ ضمنی این وظیفه را به کامپایلر/مفسر منتقل می‌کنند. تایپ صریح برعکس تایپ ضمنی است.

تایپ صریح برای هر متغیری که استفاده می شود نیاز به یک اعلان نوع صریح دارد. این نوع تایپ یک مورد خاص از تایپ استاتیک است، زیرا نوع هر متغیر در زمان کامپایل تعیین می شود.

تایپ ضمنی

اجازه دهید stringVar = "777" + 99; // دریافت "77799"

تایپ صریح (زبان ساختگی شبیه JS)

اجازه دهید wrongStringVar = "777" + 99; // یک خطا ایجاد می کند let stringVar = "777" + String(99); // دریافت "77799"

تایپ قوی/غیر دقیق

تایپ قوی/ضعیف نیز نامیده می شود. با تایپ قوی، انواع "یک بار برای همیشه" اختصاص داده می شوند، با تایپ غیر دقیق می توانند در طول اجرای برنامه تغییر کنند.

زبان های با تایپ قوی اجازه تغییر در نوع داده یک متغیر را نمی دهند و فقط تبدیل نوع داده صریح را مجاز می دانند. تایپ قوی با این واقعیت متمایز می شود که زبان اجازه اختلاط انواع مختلف در عبارات را نمی دهد و تبدیل های ضمنی خودکار را انجام نمی دهد، به عنوان مثال، نمی توانید یک عدد را از یک رشته کم کنید. زبان های ضعیف تایپ شده بسیاری از تبدیل های ضمنی را به صورت خودکار انجام می دهند، حتی اگر از دست دادن دقت یا تبدیل مبهم باشد.

تایپ قوی (زبان ساختگی شبیه JS)

بگذارید اشتباه تعداد = 777; wrongNumber = عدد اشتباه + "99"; // یک خطایی دریافت کنید که یک رشته به متغیر عددی اضافه شده است wrongNumber let trueNumber = 777 + Number("99"); // 876 را دریافت کنید

تایپ آزاد (همانطور که در js است)

بگذارید اشتباه تعداد = 777; wrongNumber = عدد اشتباه + "99"; // رشته "77799" را دریافت کرد

  • دقیق: جاوا، پایتون، هسکل، لیسپ.
  • غیر دقیق: C، جاوا اسکریپت، ویژوال بیسیک، PHP.
اشتراک گذاری