به دومین پست و ویدیو آموزش TypeScript از کانال یوتیوب میکروفرانت اند خوش آمدید. در ویدیو قبلی که در این پست هم آمده توضیح دادیم که TypeScript چیست و چرا به آن نیاز داریم همچنین تاریخچه و زمینههای شکل گیری آن را مفصل بررسی کردیم.
مدارگرد اقلیمی مریخ در مسیر حرکت خودش به دلیل ناسازگاری واحدهای اندازه گیری بین مولفه هایش نابود شد و یک پروژه عظیم فضایی را به فنا داد.، زیرا یک مولفه آن که ساخت شرکت لاکهید بود از واحد پوند نیرو برای اندازه گیری حرکت استفاده میکرد و سایر مولفههایش که توسط ناسا ساخته شده بود از واحد نیوتون ثانیه استفاده میکردند. یک فاجعه تمام عیار!
در این دوره آموزشی خواهیم دید که Type Checker چگونه از بروز چنین فاجعههایی جلوگیری میکند و به شما امکان نوشتن برنامههایی با صحت و دقت بالا میدهد. به شکل کلی Type اثبات میکند که کد شما آنگونه که از آن انتظار میرود عمل می کند. اگرچه این دوره با عنوان آموزش TypeScript معرفی شده است و از این زبان برای مثالها استفاده میشود اما هدف این است که مستقل از زبانهای برنامه نویسی با مفاهیم کلی برنامه نویسی مبتنی بر Type مانند ADT، lambda، generics و monad و توابع صحبت کنیم.
در سالهای اخیر تلاشهای زیادی در زمینه Type System انجام شده است. زبانهایی مانند Elm یا Idris و Haskell مطرح شده اند. در کنار آن برای زبانهای دینامیک هم ابزارهایی برای ارائه چک زمان کامپایل مطرح شدهاند. به عنوان مثلا در پایتون از Type hint استفاده میشود و TypeScript قدرت زیادی به اکوسیستم جاوا اسکریپت اضافه کرده است.
در پایینترین سطح سخت افزار و زبان ماشین، دستورات، دادهها و عملیاتها به شکل بیت تفسیر میشوند و فرقی بین کد و دیتا وجود ندارد و سیستم به راحتی میتواند به دلیل اشکال در هر کدام از آنها، با مشکل مواجه شود. این مشکل میتواند به صورت کرش برنامه و یا آسیب پذیری های امنیتی، که مهاجم سعی میکند دادههای ورودی خود را به عنوان دستور اجرا کند، خود را نشان دهد.
به عنوان مثال دستور eval در جاوا اسکریپت را در نظر بگیرید. این دستور مقدار رشته ای را به عنوان ورودی میگیرد و اجرا میکند. اگر در دادهها اشکالی وجود داشته باشد منجر به خطاهای زمان اجرا میشود.
در کنار تفاوت داده و کد، ما بایستی بتوانیم مقادیر را هم تفسیر کنیم. به عنوان مثال دنباله ۱۶ بیتی 1100001010100011 هم میتواند عدد بدون علامت 49827 باشد، هم میتواند عدد علامت دار -15709 و یا یک کاراکتر یونیکد باشد. پس بایستی یک لایه دیگر داشته باشیم که این دادهها را برای ما با معنی کند.
تایپ به این دادهها معنی میدهد و در کنار آن امکان کنترل بیشتری روی مقادیر قابل قبول آن، سطوح دسترسی و قابلیت تغییر مقدار را ارایه میدهد تا بتوان جلوی بسیاری از خطاهای زمان اجرا را بگیریم.
هدف کلی این دوره آموزشی برنامه نویسی مبتنی بر Type است لذا قبل از شروع تعریف مشخصی از این واژگان را ارایه میکنیم.
یک Type در واقع یک طبقهبندی داده است که معنی و مقادیر قابل قبول یک داده و همچنین عملیاتی که بر روی آن قابل انجام است را مشخص میکند. این Type میتواند در زمان کامپایل و یا زمان اجرا به منظور بررسی صحت و کنترل دسترسی دادهها چک شود.
یک Type Systemمجموعه از قواعدی است که به المانهای برنامه نویسی اختصاص داده میشود و ارزیابی میگردد. این المانها میتوانند متغیرها، توابع و یا سایر ساختارهای سطح بالای برنامه نویسی مانند کلاسها باشند. تایپ میتواند به صورت صریح توسط دستورات زبانهای برنامه نویسی مشخص شود و یا از طریق استنتاج به صورت ضمنی استنباط گردد.
به شکل کلی سورس کد ما توسط کامپایلر یا مفسر به دستورات زبان ماشین و یا دستورالعملهای یک runtime مشخص تبدیل میشود. runtime میتواند خودش یک کامپیوتر فیزیکی باشد که در این صورت دستورالعملهای CPU خواهد بود و یا یک ماشین مجازی باشد که در این صورت نیز دستورالعملهای خاص آن ماشین مجازی است.
در فرایند Type Checking پایبندی برنامه نوشته شده به قواعد تعیین شده بررسی میشود که معمولا توسط کامپایلر در زمان کامپایل و یا توسط runtime در زمان اجرا انجام میشود. مولفهای که این کار را انجام میدهد را Type Checker مینامیم.
در این دوره هدف این است که کمتر به مباحث ریاضی Type System بپردازیم و در عوض بر روی برنامه نویسی مبتنی Type متمرکز شویم. با این وجود ذکر این نکته ضروری است که بدانیم نظریه Type System بسیار از نظر علمی مستحکم و قابل اتکا است و رابطه زیادی با مفاهیم اثبات در ریاضیات گسسته دارد. به عنوان مثال و مطالعه بیشتر میتوانید Proof as Program را مشاهده کنید.
بدیهی است که Type System کمک شایانی به برنامه نویسی میکند اما به شکل کلی مزایای Type System را میتوان در 5 گروه دسته بندی کنیم که در ادامه هریک از آنها را بررسی میکنیم.
صحت کد به این معنی است که کد ما بر اساس آنچه که برایش مشخص کردهایم عمل کند و خروجی قابل انتظاری را بدون بروز خطا یا کرش در زمان اجرا تولید کند. Type به ما اجازه میدهد سختگیری بیشتری بر روی اجرای کد داشته باشیم. مثلا اگر برای تابعی انتظار داریم مقدار رشتهای ارسال شود، امکان ارسال چیزی غیر از آن وجود نداشته باشد. این اتفاق در زمان کامپایل و پیش از اجرای کد قابل پیشگیری است.
تغییر ناپذیری به این معنی است که اگر مقدار ما به اصطلاح در State خوبی است، دیگر اجازه تغییر نداشته باشد. مگر اساسا State جدیدی ایجاد کنیم. البته چالشی که در این صورت وجود دارد ایجاد کپیهای فراوان از یک متغیر است. در برخی از زبانهای functional به شکل کلی همه چیز تغییر ناپذیر و ثابت است. از نظر حافظه تفاوتی بین متغیر immutable و mutable وجود ندارد و این Type System است که شرایط تغییر ناپذیری را برای کامپایلر مهیا میکند.
کپسوله سازی در واقع توانایی مخفی کردن دادههای داخلی یک تابع، کلاس یا ماژول از دید سایر مولفههای سیستم است.
همه میدانیم که کد بیش از آنکه نوشته شود خوانده میشود. استفاده از Type کد را بسیار خواناتر و تمیزتر میکند. وقتی در تعریف یک تابع آیتمهای ورودی و خروجی نوعشان صریحا مشخص باشد به راحتی میتوان کارکرد و هدف تابع را فهمید. در بسیاری از زبانها شما بایستی بیشتر به مستندات متوصل شوید تا خود کد.
بدون شکستن کد به واحدهای کوچکتر و متصل کردن آنها به هم نمیتوان نرمافزارهای بزرگ و پیچیده را مدیریت کرد. مکانیزمهای مبتنی بر تایپ امکان نوشتن کدهای ماژولار و مستقلی را میدهند که میتوانند با هم ارتباط داشته باشند. برنامه نویسی مبتنی بر Generic ویژگی های فراوانی برای این کار به ما ارایه میدهد.
برای هرکدام از موارد فوق مثالهایی در ویدیو مطرح شده است.
امروزه اغلب زبانهای برنامهنویسی و runtime ها امکاناتی برای برنامهنویسی مبتنی بر Type و کار با آن را ارایه میکنند. تفاوت عمده آنها در زمان چک کردن و شدت سختگیری آنهاست. به شکل کلی میتوان این امکانات را از دو منظر بررسی کرد:
در زبانهای Dynamic type هیچ محدودیتی در زمان کامپایل در نظر گرفته نمیشود. احتمالا عبارت duck typing را هم شنیده باشید که از آنجا آمده است که اگر کسی مثل اردک راه میرود و صدای اردک دارد میتوان به آن اردک گفت. در زبانهای دینامیک این فرض استوار است و کد میتواند آزادانه همه کار کند و همه چک و تیکها به زمان اجرا موکول میشود.
از دیگر سو در زبانهای static تمام چک و تیکها در زمان کامپایل انجام میشود و ایراد در نوعها موجب بروز خطای کامپایلری میشود و برنامه قابل اجرا نیست و این مهمترین ویژکی و مزیت Static Typing است.
زبان javascript یک زبان دینامیک و typescript یک زبان استاتیک است. در واقع تایپ اسکریپت به این منظور ساخته شد که استاتیک تایپ چک را به جاوا اسکریپت اضافه کند و جلوی برخی از خطاهای زمان اجرا در فاز کامپایل بگیرد.
این سوال را در نظر بگیرید. آیا شیر با سفیدی برابر است؟ اگر بخواهیم Strong جواب بدهیم باید بگویم خیر. شیر یک مایع است و نمیتوان آن را با رنگ مقایسه کرد. اما اگر بخواهیم Weak جواب بدهیم خواهیم گفت. بله چون رنگ شیر سفید میتوان آن را با سفیدی برابر دانست. در حالت اول ما باید شیر را اول به رنگ تبدیل و بعد مقایسه کنیم اما در حالت دوم به این تبدیل نیازی نیست.
در سیستمهای Weak تبدیل نوع عموما به شکل ضمنی توسط خود کامپایلر انجام میپذیرد مثلا شما میتوانید عددی را با رشته مقایسه کنید. در این سناریو بسته به نوع زبان ممکن است ابتدا عدد به رشته یا رشته به عدد تبدیل شود و بعد با هم مقایسه میشوند. این امر به ظاهر ممکن است خوب باشد اما در برخی سناریوها تبدیل های ناخواسته ممکن است موجب بروز خطا و باگ در برنامه شود. اما در سیستم های Strong معمولا تا شما به صورت صریح اعلام نکنید چنین تبدیلی اتفاق نمیافتد.
یکی از ویژگی های مهم کامپایلرهای مدرن Type Inference یا استتناج نوع به شکل خودکار اتفاق میافتد. به عنوان مثال وقتی متغیری تعریف و مقدار 50 را در آن ذخیره میکنید، کامپایلر متوجه میشود که نوع مقدار عددی است و دیگر نیازی نیست صریحا آن را مشخص کنید. اگر چه اعلان آن خوانایی کد را بالا میبرد.
این مطلب برداشتی از فصل اول کتاب Programming with type نوشته Vlad Riscutia است که منبع اصلی این دوره آموزشی است.
برای مشاهده این دوره به صورت کامل می توانید به پلی لیست آموزش برنامه نویسی typescript در کانال یوتیوب میکروفرانتاند مراجعه کنید.