از آن جایی که طرح مثال یکی از بهترین راههای یادگیری است، در این مقاله قصد داریم به صورت عملی و با مثال به توضیح مفاهیم زبان ارلنگ بپردازیم.
مثال مورد نظر نوشتن ابزاری برای محاسبه تعداد رخدادهای یک کلمه در فایلهای ذخیره شده در یک پوشه است. سعی داریم که این مثال را به صورت مرحله به مرحله توضیح دهیم و در نهایت آن را اجرا کنیم. نکته مهم در این مثال استفاده از امکانات مختلف زبانی در ارلنگ صرفا جهت آموزش آنهاست، بدون در نظر گفتن ملاحظات اجرایی یا امنیتی آنها.
قسمت اول
اولین کار تعریف یک ماژول است. هر ماژول، مجموعهای از توابع است که باید در یک فایل ذخیره شود. نام ماژول و اسم فایل باید یکی باشند. همچنین هر ماژول میتواند تعیین کند که چه توابعی از آن عمومی و قابل استفاده از بیرون هستند. ما نام utility را برای ماژول خود انتخاب میکنیم و با اسم utility.erl آن را ذخیره میکنیم و اجازه میدهیم تابع count/2 از بیرون آن قابل دسترسی باشد. همچنین عدد ۲ در انتهای تعریف تابع تعداد پارامترهای ورودی تابع را مشخص میکند.
part 1: utility module code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L3-L4
قسمت دوم
تابع count/2 دو مقدار را به عنوان پارامترهای ورودی میگیرد که یکی آدرس پوشه مورد نظر و دیگری کلمهای است که قصد داریم تعداد رخدادهایش را بشماریم. در این تابع ابتدا متغیری تعریف میکنیم تا مقدار اولیه شمارنده را – که صفر است – در آن قرار دهیم. نکته مهم این است که در ارلنگ متغیرها immutable هستند، یعنی تنها یک بار میتوانند مقدار بگیرند و آن مقدار دیگر قابل تغییر نیست.
سپس فرآیندی (process) ایجاد خواهیم کرد که مسئول شمارش و گزارشدهی تعداد رخدادهاست. رابطه تابع و فرآیند در ارلنگ مانند رابطه کلاس و شی در زبانهای شیگرا است. بدین صورت که ما از توابع میتوانیم فرآیند تولید کنیم و این کار را با استفاده از تابع spawn انجام میدهیم. فرآیندها در ارلنگ حجم بسیار کمی دارند به صورتی که یک فرآیند تازه تولید شده تنها حدود ۵۱۲ بایت فضا از حافظه میگیرد. فرایندها میتوانند همزمان (concurrent) اجرا شوند و هیچ یک دخالتی در کار دیگر فرایندها ندارد؛ یعنی هیچ فرآیندی از بیرون نمیتواند به state درونی یک فرایند دیگر دسترسی داشته باشد. نداشتن state مشترک و قابل دسترس برای هر فرآیند، امکان اجرای همزمان فرایندها بی نیاز از lock را فراهم میآورد. البته این به معنای ناتوانی فرایندها از گفتگو با یکدیگر نیست. ارتباط میان فرایندها با مکانیزمی به نام message passing صورت میگیرد؛ یعنی فرایندها میتوانند به یکدیگر پیام بفرستند.
پس از ایجاد فرآیند شمارشگر، با استفاده از تابع register/2 آن را ثبت میکنیم تا از هر جایی بتوانیم برایش پیام ارسال کنیم. در نهایت تابع count_on_dir/2 را با آرگومانهای آدرس پوشه و کلمه مورد نظر فراخوانی میکنیم.
part 2: count function code:https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L8-L12
قسمت سه
در این قسمت تابع counter/1 را تعر<یف میکنیم. به محض ورود به این تابع، با استفاده از کلمه کلیدی receive یک بلوک باز میکنیم که منتظر دریافت پیام است. پیامهای ارسال شده به این فرآیند با استفاده از ویژگی pattern matching با یکی از الگوهای نوشته شده تطبق داده میشوند. اگر پیام stat دریافت شود، فرآیند ما مقدار کنونی شمارنده را نمایش داده و دوباره تابع counter/1 را به صورت tail recursive فراخوانی میکند تا اجرای آن متوقف نشود. اگر پیام exit دریافت شود، مقدار کنونی شمارنده نمایش داده میشود و شمارنده متوقف میشود. در نهایت هر پیامی غیر از stat و exit دریافت شود، آن پیام درون متغیر Count قرار میگیرد و ما آن را با مقدار قبلی شمارنده جمع میکنیم و دوباره تابع counter/2 را با مقدار جدید فراخوانی میکنیم تا زنده بماند و منتظر پیامهای جدید باشد.
part 3: counter function code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L16-L21
قسمت چهار
در قسمت دو از تابع count_on_dir/2 استفاده کردیم و حال میخواهیم در این قسمت آن را تعریف کنیم. این تابع با استفاده از list_files/1 لیستی از فایلهای درون یک پوشه را بدست میآورد. حال تابع count_of_files/2 با لیست بدست آمده و کلمه مورد نظر فراخوانی میکنیم.
part 4: count_on_dir function code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L25-L27
قسمت پنج
تابع count_on_files/2 باید با استفاده حلقهای مانند حلقه for که در اکثر زبانها وجود دارد، بر روی فهرست فایلهای ورودی عملیات شمارش را انجام دهد. اما چنین عملیاتی در زبانهای تابعی مانند ارلنگ با استفاده از خود توابع و به صورت tail recursion انجام میشود. از طرف دیگر توابع در ارلنگ پیش از اجرا بر روی پارامترهای ورودی خود عملیات pattern matching انجام میدهند و در صورت تطبیق الگو اجرا میشوند. از این رو ما میتوانیم یک تابع را با الگوهای مختلف ورودی بنویسیم. حال با استفاده از این ویژگیها ما حلقهای ایجاد میکنیم و تابع do_count/2 را برای تک تک فایلها در فرآیندهای مجزا فراخوانی میکنیم. با فراخوانی آنها در فرآیندهای مجزا، عملیات شمارش رخداد کلمه مورد نظر کاملا به صورت همزمان انجام شده و باعث استفاده از تمام ظرفیت هستههای پردازنده مرکزی میشود.
part 5: count_on_files code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L31-L34
قسمت شش
کار تابع do_count/2 بسیار ساده است. این تابع با استفاده از کتابخانه استاندارد زبان ارلنگ فایل خواسته شده را در حالت read باز کرده و با استفاده از تابع read_file/2 محتویات آن را بررسی میکند.http://www.salam-donya.com/wp-admin/post-new.php#
part 6: do_count function code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L38-L40
قسمت هفت
تابع read_file/2 نیز با استفاده از کتابخانه استاندارد زبان ارلنگ و همچنین با بهرگیری از ویژگی tail recursion فایل باز شده را خط به خط میخواند و به تابع read_line/2 ارسال میکند تا اینکه با خواندن کلمه eof به انتهای فایل باز شده برسد و آن را ببندد.
part 7: read_file function code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L44-L50
قسمت هشت
در نهایت تابع read_line/2 با انجام عملیات regular expression بر روی خط ورودی میتواند تعداد رخدادهای کلمه مورد نظر را پیدا کند که این کار را با استفاده از توابع کتابخانه استاندارد زبان انجام میدهد. در صورت پیدا کردن رخداد کلمه در خط ورودی، تعداد آن با استفاده از تابع length شمرده میشود و عدد به دست آمده در قالب یک پیام برای فرآیند counter که پیش از این تولید و ثبت شده بود با استفاده از عملگر علامت تعجب «!» ارسال میشود.
part 8: read_line function code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L54-L58
قسمت نهم
در قسمت چهار درون تابع count_on_dir/2 از تابعی به نام list_files/1 استفاده کردیم که در این قسمت آن را تعریف میکنیم. در این تابع نیز با بهرهگیری از کتابخانه استاندار ارلنگ فایلهای پوشه خواسته شده را بدست میآوریم تا مثال خود را کامل کرده باشیم.
part 9: list_files function code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-erl-L62-L71
اجرای شمارشگر
برای اجرای این کد نیاز به سیستمی داریم که ارلنگ روی آن نصب شده باشد. با استفاده از package manager سیستمعامل خود بهراحتی میتوانید آن را نصب کنید. سپس با استفاده از دستور erl در ترمینال، کنسول تعاملی ارلنگ را باز کنید. سپس با استفاده از تابع c ماژول خود را کامپایل کنید. حالا میتوانید تابع خود را فراخوانی کنید و در هر لحظهای که میخواهید با ارسال پیام stat به فرآیند شمارندهای که ایجاد کردهاید از میزان رخدادهای کلمه خواسته شده با خبر شوید یا با استفاده از پیام exit شمارنده خود را متوقف کنید.
final part: compile and run code: https://gist.github.com/hamidreza-s/78c8b3a7a997821bd234#file-utility-sh-L1-L6
سخن پایانی
شاید ویژگیهای تابعی ارلنگ، یا مدل همزمانی آن برای توسعهدهندگان زبانهای دیگر کمی سخت به نظر بیاید، اما این سختی ناشی از عادت ما به زبانهای رویهای یا شیگرایی است که از دوران مدرسه به ما آموختهاند. با در نظر گرفتن دستاوردهای چنین زبانهایی میتوان بهسادگی از سختی نسبی و موقت آنها گذشت و از آنها لذت برد.