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

ফাংশন

এলিক্সির এবং অন্যান্য ফাংশনাল ল্যাঙ্গুয়েজে ফাংশন হল প্রথম শ্রেণীর নাগরিক। এই অধ্যায়ে আমরা এলিক্সিরের বিভিন্ন ধরণের ফাংশন নিয়ে কথা বলব এবং আলোচনা করব এদের পার্থক্য ও ব্যবহার নিয়ে।

নামহীন ফাংশন (Anonymous Function)

নাম থেকেই বুঝা যাচ্ছে যে নামহীন ফাংশনের কোন নাম নেই। যেমনটি আমরা দেখেছিলাম Enum অধ্যায়ে, এরা প্রায়েই ব্যবহৃত হয় অন্যান্য ফাংশনে। এলিক্সিরে এই ধরনের নামহীন ফাংশন বানাতে হলে আমাদের দুইটি কী-ওয়ার্ড লাগবে- fnend। এদের মাধ্যমে আমরা যে কোন প্যারামিটার লিস্ট ও ফাংশন বডিকে ডিফাইন করতে পারি তাদের -> দিয়ে আলাদা করে।

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

iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5

& শর্ট-হ্যান্ড

নামহীন ফাংশন এতটাই ব্যবহৃত হয় যে এলিক্সির একটি শর্টকাট দিয়ে থাকে তাদের তৈরি করার জন্য-

iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5

দেখেই বুঝা যাচ্ছে যে প্যারামিটার হিসেবে ব্যবহৃত হয় &1, &2, &3 ইত্যাদি।

প্যাটার্ন ম্যাচিং

শুধুমাত্র বেরিয়েবলের ক্ষেত্রেই নয়, ফাংশন বানানোর সময়েও প্যাটার্ন ম্যাচিং কাজে আসে।

এলিক্সির প্যাটার্ন ম্যাচিংয়ের মাধ্যমে প্রথম সারির প্যারামিটার মিলিয়ে থাকে সংশ্লিষ্ট বডির সাথে-

iex> handle_result = fn
...>   {:ok, result} -> IO.puts "Handling result..."
...>   {:error} -> IO.puts "An error has occurred!"
...> end

iex> some_result = 1
1
iex> handle_result.({:ok, some_result})
Handling result...
:ok
iex> handle_result.({:error})
An error has occurred!

নেইমড ফাংশন

নামহীন ফাংশন তো দেখলাম, এবার নামওয়ালা ফাংশন তথা নেইমড ফাংশন দেখা যাক। এলিক্সিরে নেইমড ফাংশন ব্যবহৃত হয় মডিউলের ভেতর, def কী-ওয়ার্ডের মধ্য দিয়ে। আগামী অধ্যায়ে আমরা মডিউল নিয়ে কথা বলব, তাই আপাতত শুধু ফাংশনের দিকে আলোকপাত করা যাক।

এক মডিউলের ভেতর সৃষ্ট ফাংশন অন্যান্য মডিউলে ব্যবহারযোগ্য। এটি এলিক্সিরের একটি বেশ প্রয়োজনীয় বিল্ডিং ব্লক।

defmodule Greeter do
  def hello(name) do
    "Hello, " <> name
  end
end

iex> Greeter.hello("Sean")
"Hello, Sean"

যদি আমাদের ফাংশন শুধু এক লাইনের হয়ে থাকে, তবে আমরা একে সংক্ষিপ্ত করতে পারি do: এর মাধ্যমে।

defmodule Greeter do
  def hello(name), do: "Hello, " <> name
end

প্যাটার্ন ম্যাচিংয়ের জ্ঞান দিয়ে চলুন রিকারসনের একটি উদাহরণ দেখি আমরা, নেইমড ফাংশনের মাধ্যমে-

defmodule Length do
  def of([]), do: 0
  def of([_ | tail]), do: 1 + of(tail)
end

iex> Length.of []
0
iex> Length.of [1, 2, 3]
3

ফাংশন নামকরণ ও অ্যারিটি

আমরা আগেই বলেছি যে এলিক্সিরে ফাংশনের পরিচয় হল তার নাম ও অ্যারিটির জুটি। এর মানে আপনি নিম্নবর্ণিত কাজ করতে পারেন-

defmodule Greeter2 do
  def hello(), do: "Hello, anonymous person!"   # hello/0
  def hello(name), do: "Hello, " <> name        # hello/1
  def hello(name1, name2), do: "Hello, #{name1} and #{name2}"
                                                # hello/2
end

iex> Greeter2.hello()
"Hello, anonymous person!"
iex> Greeter2.hello("Fred")
"Hello, Fred"
iex> Greeter2.hello("Fred", "Jane")
"Hello, Fred and Jane"

ফাংশনের নামগুলো আমরা কমেন্টে দিয়েছি। ফাংশনের প্রথম রূপ কোন প্যারামিটার নেয় না, তাই এটি পরিচিত হবে hello/0 হিসেবে; দ্বিতীয়টি একটি আর্গুমেন্ট নেয় বিধায় একে আমরা ডাকব hello/1। অন্যান্য ল্যাঙ্গুয়েজের ফাংশন ওভারলোডিংয়ের মত নয় এরা, এরা আসলে ভিন্ন ভিন্ন ফাংশন।

প্যাটার্ন ম্যাচিং শুধুমাত্র তখনি ব্যবহৃত হয় যখন একাধিক প্যাটার্ন সম-আর্গুমেন্ট বিশিষ্ট ফাংশনের উপর ব্যবহৃত হয়।

প্রাইভেট ফাংশন

যদি আমরা কোন ফাংশনকে শুধুমাত্র তার নিজস্ব মডিউলে ব্যবহার করতে চাই (অন্য মডিউল থেকে নয়) তখন আমরা তাদের প্রাইভেট হিসেবে ঘোষণা করব। এলিক্সিরে আমরা defp দিয়ে প্রাইভেট ফাংশন তৈরি করি।

defmodule Greeter do
  def hello(name), do: phrase <> name
  defp phrase, do: "Hello, "
end

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.phrase
** (UndefinedFunctionError) function Greeter.phrase/0 is undefined or private
    Greeter.phrase()

গার্ড

কন্ট্রোল স্ট্রাকচার অধ্যায়ে আমরা গার্ড নিয়ে কিছু কথা বলেছিলাম। এখন আমরা দেখব কি করে এদেরকে নেইমড ফাংশনে ব্যবহার করা হয়।

এলিক্সির যখনি কোন ফাংশন ম্যাচ করে, এরপর সংশ্লিষ্ট গার্ড চেক করা হবে।

নীচের ফাংশন দুইটি একই প্যাটার্ন ফলো করে, কিন্তু এদের মধ্যে পার্থক্য করা হয় গার্ডের ভিন্নতা (যেমন আর্গুমেন্ট টাইপ) দিয়ে।

defmodule Greeter do
  def hello(names) when is_list(names) do
    names = Enum.join(names, ", ")
    
    hello(names)
  end

  def hello(name) when is_binary(name) do
    phrase() <> name
  end

  defp phrase, do: "Hello, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

ডিফল্ট আর্গুমেন্ট

আমরা ডিফল্ট আর্গুমেন্ট প্রদান করি argument \\ value সিনট্যাক্স দিয়ে:

defmodule Greeter do
  def hello(name, language_code \\ "en") do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello("Sean", "en")
"Hello, Sean"

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.hello("Sean", "es")
"Hola, Sean"

যখন আমরা গার্ড ও ডিফল্ট আর্গুমেন্টের সমন্বয় সাধন করি তখন একটি সমস্যার সৃষ্টি হয়। সেটা কী তা দেখা যাক-

defmodule Greeter do
  def hello(names, language_code \\ "en") when is_list(names) do
    names = Enum.join(names, ", ")
    
    hello(names, language_code)
  end

  def hello(name, language_code \\ "en") when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

** (CompileError) iex:31: definitions with multiple clauses and default values require a header. Instead of:

    def foo(:first_clause, b \\ :default) do ... end
    def foo(:second_clause, b) do ... end

one should write:

    def foo(a, b \\ :default)
    def foo(:first_clause, b) do ... end
    def foo(:second_clause, b) do ... end

def hello/2 has multiple clauses and defines defaults in one or more clauses
    iex:31: (module)

এলিক্সির ডিফল্ট আর্গুমেন্ট ও একাধিক ম্যাচিং ফাংশনকে একত্রে ব্যবহার করতে দেয় না, কারণ তা কিছু কনফিউসনের সৃষ্টি করে। এ ধরনের সমাধানের জন্য আমরা একটি ফাংশন হেড অ্যাড করব আমাদের ডিফল্ট আর্গুমেন্টের সাথে-

defmodule Greeter do
  def hello(names, language_code \\ "en")

  def hello(names, language_code) when is_list(names) do
    names = Enum.join(names, ", ")
    
    hello(names, language_code)
  end

  def hello(name, language_code) when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

iex> Greeter.hello ["Sean", "Steve"], "es"
"Hola, Sean, Steve"
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!