Do you want to pick up from where you left of?
Take me there

মডিউল

অভিজ্ঞতা থেকে আমরা জানি যে এক ফাইল ও স্কোপে সমস্ত ফাংশন রাখা ঠিক না। এই অধ্যায়ে আমরা আলোচনা করব কিভাবে কিছু ফাংশনকে একত্রিত করা যায় এবং এরপর একটি বিশেষ ম্যাপ, স্ট্রাক্ট নিয়ে কথা বলব যা আমাদের কোড সংগঠনকে আরও সুগঠিত রাখতে সাহায্য করবে।

মডিউল

মডিউল হল সর্বোত্তম উপায় আমাদের ফাংশনকে নেইমস্পেসে সুগঠিত করে রাখার। ফাংশন গ্রুপিং ছাড়াও তারা “নামসহ” এবং প্রাইভেট ফাংশন বানাতে দেয় যা আমরা পূর্ববর্তী অধ্যায়ে শিখেছি।

একটি বেসিক উদাহরণ দেখা যাক-

defmodule Example do
  def greeting(name) do
    "Hello #{name}."
  end
end

iex> Example.greeting "Sean"
"Hello Sean."

এলিক্সিরে মডিউল নেস্টিং করা সম্ভব-

defmodule Example.Greetings do
  def morning(name) do
    "Good morning #{name}."
  end

  def evening(name) do
    "Good night #{name}."
  end
end

iex> Example.Greetings.morning "Sean"
"Good morning Sean."

মডিউল আট্রিবিউট

মডিউল আট্রিবিউট এলিক্সিরের সবচেয়ে বেশী ব্যবহৃত কনস্ট্যান্ট। একটি উদাহরণ দেখা যাক-

defmodule Example do
  @greeting "Hello"

  def greeting(name) do
    ~s(#{@greeting} #{name}.)
  end
end

উল্লেখ্য এলিক্সির এর কিছু রিজার্ভড আট্রিবিউট রয়েছে। এর মধ্যে তিনটি কমন আট্রিবিউট হল-

স্ট্রাক্ট

স্ট্রাক্ট হচ্ছে বিশেষ এক প্রকার ম্যাপ যার রয়েছে পূর্ববর্ণীত কী এবং ডিফল্ট ভ্যালু। একটি স্ট্রাক্ট অবশ্যই ব্যবহৃত হয় মডিউলের ভেতর এবং ওই মডিউলের নামই হয়ে থাকে স্ট্রাক্টের নাম। অনেক ক্ষেত্রেই দেখা পাওয়া যায় এমন মডিউলের যার কেবল মাত্র একটি মাত্র সদস্যই রয়েছে যা হল এর স্ট্রাক্ট।

defstruct দিয়ে আমরা স্ট্রাক্ট ডিফাইন করে থাকি, সাথে বলে দেই এর ফিল্ড সমূহ (কী-ওয়ার্ড লিস্টের মাধ্যমে) এবং ডিফল্ট ভ্যালুসমূহ-

defmodule Example.User do
  defstruct name: "Sean", roles: []
end

এবার কিছু স্ট্রাক্ট তৈরি করা যাক-

iex> %Example.User{}
%Example.User<name: "Sean", roles: [], ...>

iex> %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [], ...>

iex> %Example.User{name: "Steve", roles: [:manager]}
%Example.User<name: "Steve", roles: [:manager]>

স্ট্রাক্টকে আমরা ঠিক ম্যাপের মতই আপডেট করতে পারি-

iex> steve = %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [...], ...>
iex> sean = %{steve | name: "Sean"}
%Example.User<name: "Sean", roles: [...], ...>

স্ট্রাক্টকে আপনি ম্যাপের সাথে ম্যাচ করাতে পারেন-

iex> %{name: "Sean"} = sean
%Example.User<name: "Sean", roles: [...], ...>

কম্পোজিশান

আমরা স্ট্রাক্ট ও মডিউল তৈরি করতে পারি। এবার আমরা দেখব কিভাবে কম্পোজিশানের মাধ্যমে আমরা বিদ্যমান ফাংশনালিটি যোগ করতে পারি। এলিক্সিরে তা বেশ কয়েকভাবে করা যায়-

alias

alias দিয়ে আমরা মডিউল নাম আলিয়াসিং করতে পারি। যা এলিক্সিরে অনেক ব্যবহৃত হয়।

defmodule Sayings.Greetings do
  def basic(name), do: "Hi, #{name}"
end

defmodule Example do
  alias Sayings.Greetings

  def greeting(name), do: Greetings.basic(name)
end

# অ্যালিয়াস ছাড়া

defmodule Example do
  def greeting(name), do: Sayings.Greetings.basic(name)
end

যদি দুটি অ্যালিয়াস একই নামের হয় অথবা আমরা কোন অ্যালিয়াসকে ভিন্ন নাম দিতে চাই তাহলে আমরা :as অপশন ব্যবহার করব।

defmodule Example do
  alias Sayings.Greetings, as: Hi

  def print_message(name), do: Hi.basic(name)
end

একাধিক মডিউলকে একসাথে অ্যালিয়াস করা যায় যেমন-

defmodule Example do
  alias Sayings.{Greetings, Farewells}
end

import

অ্যালিয়াস না করে যদি আমরা সরাসরি ফাংশন ও ম্যাক্রো ইম্পোর্ট করতে চাই তাহলে import/1 ব্যবহার করব।

iex> last([1, 2, 3])
** (CompileError) iex:9: undefined function last/1
iex> import List
nil
iex> last([1, 2, 3])
3

ফিল্টারিং

ইম্পোর্ট করলে সমস্ত ফাংশন ও ম্যাক্রো চলে আসে, কিন্তু আমরা :only:except এর মাধ্যমে ইম্পোর্টকৃত ফাংশন অথবা ম্যাক্রোর উপর ফিল্টার করতে পারি-

বিশেষ কিছু ফাংশন ও ম্যাক্রো ইম্পোর্ট করতে হলে তাদের নাম/অ্যারিটি যুগলকে :only:except কে জানিয়ে দিতে হবে। নীচে একটি উদাহরণ দেখান হয়েছে যেখানে শুধু last/1 কে ইম্পোর্ট করা হয়েছে-

iex> import List, only: [last: 1]
iex> first([1, 2, 3])
** (CompileError) iex:13: undefined function first/1
iex> last([1, 2, 3])
3

আর যদি আমরা last/1 ছাড়া বাকি সব ইম্পোর্ট করতে চাই তাহলে-

iex> import List, except: [last: 1]
nil
iex> first([1, 2, 3])
1
iex> last([1, 2, 3])
** (CompileError) iex:3: undefined function last/1

নাম/অ্যারিটি যুগলকে :only:except কেই শুধু না, :functions আর :macros কে দিয়ে আমরা ফিল্টার করতে পারি যে শুধু ফাংশনকে নিব নাকি ম্যাক্রোকে-

import List, only: :functions
import List, only: :macros

require

কম ব্যবহৃত হলেও require/2 গুরুত্বপূর্ণ। require/2 এর মাধ্যমে আমরা নির্দেশ দেই যেন উল্লেখিত মডিউলটি অবশ্যই কম্পাইলড হয়। ম্যাক্রো আনয়নের সময়ে এটি ব্যবহার করা হয়-

defmodule Example do
  require SuperMacros

  SuperMacros.do_stuff()
end

লোড না করে ম্যাক্রো ব্যবহার করলে এরর পেতে হবে।

use

ইউজ ম্যাক্রো একটি বিশেষ ম্যাক্রো __using__/1 কে কল করে।

# lib/use_import_require/use_me.ex
defmodule UseImportRequire.UseMe do
  defmacro __using__(_) do
    quote do
      def use_test do
        IO.puts("use_test")
      end
    end
  end
end

এরপর আমরা এই লাইনকে UseImportRequire এ ব্যবহার করতে পারি।

use UseImportRequire.UseMe

UseImportRequire.UseMe একটি ফাংশন use_test/0 কে ডিফাইন করে __using__/1 ম্যাক্রোর মাধ্যমে।

ইউজ এই একটি কাজই করে। __using__ ম্যাক্রো প্রায়শ ব্যবহৃত হয় অ্যালিয়াস, রিকুয়ার, অথবা ইম্পোর্ট কল করতে। এটি দ্বারা মডিউল কিভাবে, কি কি ফাংশন কিভাবে ব্যবহার করবে তার পলিসি স্থাপন করা যায়। __using__/1 দিয়ে অন্যান্য মডিউল এমনকি সাব-মডিউলকেও রেফার করা যায়।

ফিনিক্স ফ্রেমওয়ার্ক __using__/1 এর ব্যবহার দিয়ে বারংবার অ্যালিয়াস ও ইম্পোর্ট কল করা থেকে প্রোগ্রামারকে বিরত রাখে।

Ecto.Migration মডিউলের একটি ছোট উদাহরণ নীচে দেয়া হয়েছে-

defmacro __using__(_) do
  quote location: :keep do
    import Ecto.Migration
    @disable_ddl_transaction false
    @before_compile Ecto.Migration
  end
end

Ecto.Migration.__using__/1 ম্যাক্রো একটি ইম্পোর্ট ব্যবহার করে যার ফলে যখন আমরা use Ecto.Migration লিখি তখন আমরাও import Ecto.Migration ব্যবহার করে ফেলি।

আবারো বলা হচ্ছে- ইউজ ম্যাক্রো শুধুমাত্র ওই মডিউলের __using__/1 কল করে। ভালভাবে বুঝতে হলে পড়ে নিন __using__/1 এর ডকুমেন্টেশান।

নোট: quote, alias, use, require হল ম্যাক্রো যা মেটাপ্রোগ্রামিংয়ের সময়ে ব্যবহৃত হয়।

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!