مسیرها
/
Elixir
Elixir
/
تمرین‌ها
/
نقطه‌های رقصان
نقطه‌های رقصان

نقطه‌های رقصان

تمرین یادگیری

مقدمه

use

«ماکرو»ی use به ما اجازه می‌دهد که ماژول خود را به‌سرعت با قابلیت‌هایی گسترش دهیم که ماژول دیگری فراهم می‌کند. وقتی ماژولی را use می‌کنیم، آن ماژول می‌تواند کدی را به ماژول ما تزریق کند؛ برای مثال می‌تواند توابعی تعریف کند، ماژول‌های دیگر را import یا alias کند، یا ویژگی‌های ماژول را تنظیم کند.

اگر تا به حال فایل‌های تست برخی از تمرین‌های Elixir را اینجا در Exercism نگاه کرده باشید، به احتمال زیاد متوجه شده‌اید که همه‌ی آن‌ها با use ExUnit.Case شروع می‌شوند. همین یک خط کد است که ماکروهای test و assert را در ماژول تست در دسترس قرار می‌دهد.

defmodule LasagnaTest do
  use ExUnit.Case

  test "expected minutes in oven" do
    assert Lasagna.expected_minutes_in_oven() === 40
  end
end

ماکروی __using__/1

اینکه دقیقاً وقتی ماژولی را use می‌کنید چه اتفاقی می‌افتد را ماکروی __using__/1 آن ماژول تعیین می‌کند. این ماکرو یک آرگومان می‌گیرد، یعنی یک لیست کلیدواژه‌ای همراه با گزینه‌ها، و یک عبارت نقل‌قول‌شده برمی‌گرداند. کد موجود در این عبارت نقل‌قول‌شده هنگام فراخوانی use در ماژول ما درج می‌شود.

defmodule ExUnit.Case do
  defmacro __using__(opts) do
    # some real-life ExUnit code omitted here
    quote do
      import ExUnit.Assertions
      import ExUnit.Case, only: [describe: 2, test: 1, test: 2, test: 3]
    end
  end
end

گزینه‌ها را می‌توان هنگام فراخوانی use به‌عنوان آرگومان دوم ارائه داد، مثلاً use ExUnit.Case, async: true. اگر به‌صراحت داده نشوند، مقدار پیش‌فرضشان یک لیست خالی است.

رفتارها

«رفتارها» به ما اجازه می‌دهند که «رابط‌هایی» (مجموعه‌هایی از توابع و ماکروها) را در یک ماژول رفتار تعریف کنیم که بعداً می‌توانند توسط ماژول‌های فراخوان بازگشتی مختلف پیاده‌سازی شوند. به لطف رابط مشترک، می‌توان از آن ماژول‌های فراخوان بازگشتی به‌جای یکدیگر استفاده کرد.

Note

به املای بریتانیایی «behaviours» توجه کنید.

تعریف رفتارها

برای تعریف یک رفتار، باید ماژول جدیدی بسازیم و فهرستی از توابعی را مشخص کنیم که بخشی از رابط موردنظر هستند. هر تابع باید با استفاده از ویژگی ماژول @callback تعریف شود. نحوه‌ی نگارش آن دقیقاً مانند یک مشخصات نوع تابع (@spec) است. باید اسم تابع، فهرستی از انواع آرگومان‌ها، و همه‌ی انواع بازگشتی ممکن را مشخص کنیم.

defmodule Countable do
  @callback count(collection :: any) :: pos_integer
end

پیاده‌سازی رفتارها

برای افزودن یک رفتار موجود به ماژول خود (ساخت یک ماژول فراخوان بازگشتی) از ویژگی ماژول @behaviour استفاده می‌کنیم. مقدار آن باید اسم ماژول رفتاری باشد که در حال افزودن آن هستیم.

سپس باید همه‌ی توابع (فراخوان‌های بازگشتی) موردنیاز آن ماژول رفتاری را تعریف کنیم. اگر در حال پیاده‌سازی رفتار شخص دیگری باشیم، مانند رفتارهای داخلی Access یا GenServer در Elixir، فهرست همه‌ی فراخوان‌های بازگشتی آن رفتار را در مستندات hexdocs.pm پیدا می‌کنیم.

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

برای مشخص‌کردن اینکه هر تابع از کدام رفتار می‌آید، باید ویژگی ماژول @impl را پیش از هر تابع به کار ببریم. مقدار آن باید اسم ماژول رفتاری باشد که این فراخوان بازگشتی را تعریف می‌کند.

defmodule BookCollection do
  @behaviour Countable

  defstruct [:list, :owner]

  @impl Countable
  def count(collection) do
    Enum.count(collection.list)
  end

  def mark_as_read(collection, book) do
    # other function unrelated to the Countable behaviour
  end
end

پیاده‌سازی‌های پیش‌فرض فراخوان بازگشتی

هنگام تعریف یک رفتار، امکان ارائه‌ی یک پیاده‌سازی پیش‌فرض برای یک فراخوان بازگشتی وجود دارد. این پیاده‌سازی باید در عبارت نقل‌قول‌شده‌ی ماکروی __using__/1 تعریف شود. برای اینکه کاربران ماژول رفتاری بتوانند پیاده‌سازی پیش‌فرض را بازنویسی کنند، ماکروی defoverridable/1 را پس از پیاده‌سازی تابع فراخوانی کنید. این ماکرو یک لیست کلیدواژه‌ای می‌پذیرد که اسم توابع به‌عنوان کلید و تعداد آرگومان‌های توابع به‌عنوان مقدار در آن قرار دارند.

defmodule Countable do
  @callback count(collection :: any) :: pos_integer

  defmacro __using__(_) do
    quote do
      @behaviour Countable
      def count(collection), do: Enum.count(collection)
      defoverridable count: 1
    end
  end
end

توجه داشته باشید که تعریف توابع درون __using__/1 برای هر هدفی جز تعریف پیاده‌سازی‌های پیش‌فرض فراخوان بازگشتی توصیه نمی‌شود، اما همیشه می‌توانید توابع را در ماژولی دیگر تعریف کنید و آن‌ها را در ماکروی __using__/1 import کنید.

دستورالعمل‌ها

دوست شما، هنرمندی نوپا، با ایده‌ی یک پروژه به سراغتان آمد. بیایید خلاقیت بصری او را با تخصص فنی شما ترکیب کنیم. وقت آن رسیده که در هنر مولد دستی ببرید!

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

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

1. رفتار انیمیشن را تعریف کنید

هر ماژول انیمیشن باید دو callback را پیاده‌سازی کند: init/1 و handle_frame/3. آن‌ها را در ماژول Animation تعریف کنید.

callback یعنی init/1. باید یک آرگومان از نوع opts بگیرد و یک تاپل {:ok, opts} یا تاپل {:error, error} بازگرداند. پیاده‌سازی‌های این callback بررسی می‌کنند که آیا گزینه‌های داده‌شده برای این نوع خاص از انیمیشن معتبرند یا نه.

callback یعنی handle_frame/3 را تعریف کنید. باید سه آرگومان بگیرد: نقطه، شماره‌ی فریم و گزینه‌ها. باید همیشه یک نقطه بازگرداند. پیاده‌سازی‌های این callback ویژگی‌های نقطه را بر اساس شماره‌ی فریم جاری و گزینه‌های انیمیشن تغییر می‌دهند.

2. یک پیاده‌سازی پیش‌فرض برای callback یعنی init/1 فراهم کنید

رفتار Animation باید طوری باشد که با فراخوانی use DancingDots.Animation به‌راحتی در ماژول‌های دیگر جای بگیرد.

برای این کار، ماکروی __using__ را در ماژول Animation پیاده‌سازی کنید تا ماژول Animation را به‌عنوان رفتار ماژول دیگر تعیین کند. همچنین باید یک پیاده‌سازی پیش‌فرض از callback یعنی init/1 فراهم کند. پیاده‌سازی پیش‌فرض init/1 باید گزینه‌های داده‌شده را بدون تغییر بازگرداند.

defmodule MyCustomAnimation do
  use DancingDots.Animation
end

MyCustomAnimation.init(some_option: true)
# => {:ok, [some_option: true]}

3. انیمیشن Flicker را پیاده‌سازی کنید

با استفاده از رفتار Animation یک انیمیشن چشمک‌زن پیاده‌سازی کنید.

باید از callback پیش‌فرض init/1 استفاده کند، چون هیچ گزینه‌ای نمی‌گیرد.

callback یعنی handle_frame/3 را پیاده‌سازی کنید که یک فریم را مدیریت می‌کند. اگر شماره‌ی فریم مضربی از چهار باشد، تابع باید نقطه را با نصف کدری اولیه‌اش بازگرداند. در فریم‌های دیگر باید نقطه را بدون تغییر بازگرداند.

فریم‌ها از 1 شمرده می‌شوند. نقطه‌ای که به handle_frame/3 داده می‌شود همیشه نقطه در حالت اولیه‌ی خودش است، نه در حالتی که از فریم قبلی به جا مانده.

dot = %DancingDots.Dot{x: 100, y: 100, radius: 24, opacity: 1}

DancingDots.Flicker.handle_frame(dot, 1, [])
# => %DancingDots.Dot{opacity: 1, radius: 24, x: 100, y: 100}

DancingDots.Flicker.handle_frame(dot, 4, [])
# => %DancingDots.Dot{opacity: 0.5, radius: 24, x: 100, y: 100}

4. انیمیشن Zoom را پیاده‌سازی کنید

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

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

callback یعنی init/1 را پیاده‌سازی کنید. باید بررسی کند که گزینه‌های داده‌شده یک لیست کلیدواژه‌ای با کلید :velocity هستند. مقدار سرعت باید یک عدد باشد. اگر عدد نباشد، خطای "The :velocity option is required, and its value must be a number. Got: #{inspect(velocity)}" را بازگردانید.

callback یعنی handle_frame/3 را پیاده‌سازی کنید. باید نقطه را با شعاعی بازگرداند که به اندازه‌ی شماره‌ی فریم جاری منهای یک، ضربدر سرعت، افزایش یافته است.

فریم‌ها از 1 شمرده می‌شوند. نقطه‌ای که به handle_frame/3 داده می‌شود همیشه نقطه در حالت اولیه‌ی خودش است، نه در حالتی که از فریم قبلی به جا مانده.

DancingDots.Zoom.init(velocity: nil)
# => {:error, "The :velocity option is required, and its value must be a number. Got: nil"}

dot = %DancingDots.Dot{x: 100, y: 100, radius: 24, opacity: 1}

DancingDots.Zoom.handle_frame(dot, 1, velocity: 10)
# => %DancingDots.Dot{radius: 24, opacity: 1, x: 100, y: 100}

DancingDots.Zoom.handle_frame(dot, 2, velocity: 10)
# => %DancingDots.Dot{radius: 34, opacity: 1, x: 100, y: 100}
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Elixir Exercism

آماده‌اید نقطه‌های رقصان را شروع کنید؟

در Exercism ثبت‌نام کنید تا Elixir را همراه با 58 مفهوم168 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.