Saturday, April 15, 2017

ল্যামডা এক্সপ্রেশনঃ পর্ব এক (Lambda Expression: Part 1)

অনেকগুলো জনপ্রিয় প্রোগ্রামিং ল্যাংগুয়েজেই ল্যামডা এক্সপ্রেশন রয়েছে। যেমন- জাভাস্ক্রিপ্ট, পাইথন, গ্রুভি, স্ক্যালা, সি শার্প, সি++ ইত্যাদি। জাভাতে এই বিশেষ ফিচারটি অনেকদিন অনুপস্থিত থাকায় জাভা প্রোগ্রামারদের মধ্যে মনোকষ্টের কমতি ছিল না। ২০০৬ সালে জাভা পাঁচ রিলিজ হওয়ার পর এই ফিচারটি নিয়ে আলোচনা শুরু হলেও এটি খুব একটা আলোর মুখ দেখে নি। অনেকেই বলতে শুরু করে "Java is the new COBOL”। তবে জাভাকে ওরাকল কিনে নেওয়ার পর এই অবস্থার পরিবর্তন হতে শুরু করে এবং অবশেষে প্রোগ্রামারদের প্রবল মনোবেদনা ঘুচিয়ে ২০১৪ সালে রিলিজ হয় জাভা আট যাতে এই বিশেষ এবং খুবই গুরুত্বপূর্ণ ফিচারটি সংযুক্ত করা হয়।

চলুন তাহলে এই বিশেষ প্রোগ্রামিং ফিচার, ল্যামডা এক্সপ্রেশন নিয়ে আলোচনা করা যাক।


ল্যামডা এক্সপ্রেশনঃ

ল্যামডা টার্মটি এসেছে ল্যম্বডা ক্যালকুলাস (Lambda Calculus) থেকে। এটি গণিতিক যুক্তি ও কম্পিউটার সাইন্সে ব্যবহৃত এক ধরণের ফর্মাল সিস্টেম যাতে বিভিন্ন ধরণের কম্পিউটিশন বিভিন্ন ভ্যারিয়েবলের মাধ্যমে প্রকাশ করা হয়। তবে এই আমাদের এই আলোচনায় তাত্বিক দিকটি পরিহার করে বরং সাধারণভাবে এর আচরণ ও ব্যবহার নিয়ে কথা বলা যাক। 

সহজকরে বলা যেতে পারে যে, ল্যমডা এক্সপ্রেশন হলো একটি ছোট কোড যা কোনো কাজ করে থাকে। উদহারণ-

(int x) → {return x +1;}

উপরের কোডটি একটি ল্যামডা এক্সপ্রেশনের উদাহরণ। এটি দেখে মনে হচ্ছে একটি মেথড কিন্তু এর কোনো নাম নেই। এতে একটি মেথডে যা কিছু থাকে তার পায় সবই রয়েছে, যেমন – প্যারামিটার, মেথড বডি। তবে এতে রিটার্ন টাইপ দেখা যাচ্ছে না। রিটার্ন টাইপ এক্ষেত্রে কম্পাইলার বুঝে নিতে পারে, কারণ এর রিটার্ন স্ট্যাটমেন্টে দেখা যাচ্ছে যে এটি একটি ইন্টিজার রিটার্ন করছে। তাহলে একে বলতে পারি নাম ছাড়া মেথড বা অ্যানোনিমাস মেথড।


অ্যানোনিমাস ইনার ক্লাস ও ল্যাম্ডা এক্সপ্রেশন

জাভা অ্যানোনিমাস ইনার ক্লাস তৈরি করতে দেয় এবং এর বিভিন্ন রকম ব্যবহার রয়েছে। এটিও একধরণের ক্লাস তবে এর কোনো নাম নেই। এটি সাধারণত অন্য একটি ক্লাসের মধ্যে তৈরি করা হয় বলে একে অ্যানোনিমাস ইনার ক্লাস বলা হয়। অ্যানোনিমাস ক্লাস দুই ভাবে তৈরি করা যায়- ইন্টারফেস থেকে এবং একটি ক্লাস থেকে।


1
2
3
public interface Filterable {
     boolean apply(Object obj);
}

উপরের ইন্টারফেইসটি থেকে একটি অ্যানোনিমাস ইনার ক্লাস তৈরি করা যেতে পারে।


1
2
3
4
5
6
7
new Filterable() {
    @Override
    public boolean apply(Object object) {
 // other logic 
        return false;
    }
};

এখানে ইন্টারফেস থেকে একটি ইনার ক্লাস তৈরি করা হয়েছে। এখানে লক্ষ করুন, ইন্টারফেসের সঙ্গে একটি নিউ অপারেটর ব্যবহার করা হয়েছে এবং কার্লি ব্রেসের মধ্যে ইন্টারফেইসটির মেথডটি ইম্প্লিেমেন্ট করা হয়েছে। অর্থাৎ অ্যানোনিমাস ইনার ক্লাস তৈরি করতে হলে নিউ অপারেটর ব্যবহার করতে হয়।

একইভাবে ক্লাস থেকেও অ্যানোনিমাস ইনার ক্লাস তৈরি করা যায়। যে ক্লাস থেকে অ্যানোনিমাস ক্লাস তৈরি করা হবে সেই ক্লাসটিকে অ্যানোনিমাস ইনার ক্লাস ইনহেরিট করে। উদাহরণ-


1
2
3
4
5
new Person() {
    public void doSomething() {
        System.out.println("doing something");
    }
};

এখানে Person ক্লাস ব্যবহার করে একটি অ্যানোনিমাস ক্লাস তৈরি করা হয়েছে। তবে এই পদ্ধতি নিয়ে এই অনুচ্ছেদে এর বেশি আলোচনা হবে না।

এবার চলুন এই অ্যানোনিমাস ইনার ক্লাসের সুবিধা নিয়ে আলোচনা করা যাক -

মনে করুন, আপনার কাছে একটি কন্টাক্ট লিস্ট আছে। আপনি এই কন্টাক্ট লিস্ট থেকে নানাভাবে কন্টাক্টগুলোকে ফিল্টার করতে চান।

আপনার কন্টাক্ট ক্লাসটি দেখতে এরকম-


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Contact {
    enum Sex {
        MALE, FEMALE
    }

    private String name;
    private String emailAddress;
    private String phoneNumber;
    private int age;
    private Sex sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }
}

এখন আপনি একটি মেথড লিখতে পারেন, যা আপনার লিস্ট থেকে যাদের বয়স ১৮ থেকে ২৫ এর মধ্যে তাদেরকে আলাদা করবে। মেথডটি এভাবে লেখা যেতে পারে -



1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import java.util.ArrayList;
import java.util.List;

public class ContactService {
    public List<Contact> findContactAgedBetween18And25(List<Contact> contactList) {
        List<Contact> contacts = new ArrayList<>();
        
        for (Contact contact : contactList) {
            if (contact.getAge() >= 18 && contact.getAge() <= 25) {
                contacts.add(contact);
            }
        }
        return contacts;
    }
}

এক্ষেত্রে এই মেথড প্রথমে কন্টাক্ট লিস্ট থেকে একটি ফর লুপ এবং একটি ইফ স্ট্যাটমেন্ট ব্যবহার করে প্রত্যেকটি কন্টাক্ট পরীক্ষা করে দেখছে যে এদের বয়স ১৮ থেকে ২৫ এর মধ্যে কিনা। তারপর মেথডের মধ্যে একটি লোকাল ভ্যারিয়েলে সেগুলো রাখছে এবং লুপ শেষ হয়ে গেলে রিটার্ন করছে।

কিছুক্ষণ পর আপনার মনে হলো, আপনি এই বয়সের মধ্য শুধুমাত্র পুরুষগুলোকে আলাদা করতে চান। সেক্ষেত্রে আপনি একটি নতুন মেথড লিখতে পারেন।


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public List<Contact> findContactMaleAgedBetween18And25(List<Contact> contactList) {
    List<Contact> contacts = new ArrayList<>();

    for (Contact contact : contactList) {
        if (contact.getAge() >= 18
                && contact.getAge() <= 25
                && contact.getSex() == Contact.Sex.MALE) {
            contacts.add(contact);
        }
    }

    return contacts;
}

এভাবে যদি মহিলাদের আলাদা করতে চান তাহলে -


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public List<Contact> findContactFemaleAgedBetween18And25(List<Contact> contactList) {
    List<Contact> contacts = new ArrayList<>();

    for (Contact contact : contactList) {
        if (contact.getAge() >= 18
                && contact.getAge() <= 25
                && contact.getSex() == Contact.Sex.FEMALE) {
            contacts.add(contact);
        }
    }

    return contacts;
}

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

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


1
2
3
interface FilterCriteria {
    boolean match(Contact contact);
}

এখন আমরা একটি মেথড লিখবো যেখানে এই ইন্টারফেইসটি প্যারামিটার হিসেবে থাকবে।


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public List<Contact> findContacts(List<Contact> contactList, FilterCriteria criteria) {
    List<Contact> contacts = new ArrayList<>();

    for (Contact contact : contactList) {
        if (criteria.match(contact)) {
            contacts.add(contact);
        }
    }

    return contacts;
}

এই লক্ষ্য করুন , আমরা উপরের মেথডের ইফ স্ট্যাটমেন্টটি সামন্য পরিবর্তন করেছি। এটিতে কোনো নির্দিষ্ট লজিক নেই, বরং এটি আমাদের ইন্টারফেসটির একটি মেথডকে কল করছে যা কিনা বুলিয়ান রিটার্ন করে। এর অর্থ হলো- এই match() মেথডটি যেসব Contact এর ক্ষেত্রে true রিটার্ন করবে শুধুমাত্র সেগুলোকে ভেতরের লিস্টে যুক্ত করবে এবং রিটার্ন করবে। এখন এই মেথডটি ব্যবহার করে দেখা যাক-


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import java.util.ArrayList;
import java.util.List;

public class ContactApp {
    public static void main(String[] args) {
        List<Contact> contactList = new ArrayList<>(); // lets assume we have that list
        ContactService contactService = new ContactService();
        List<Contact> contactAged18To25 = contactService.findContacts(contactList, new FilterCriteria() {
            @Override
            public boolean match(Contact contact) {
                return contact.getAge() >= 18 && contact.getAge() <= 25;
            }
        });
    }
}

উপরের মেথডটিতে আর্গুমেন্ট হিসেবে একটি অ্যানোনিমাস ইনার ক্লাস দেওয়া হয়েছে যাতে আমরা match() মেথডের মূল লজিকটুকু লিখেছি। এভাবে আমাদের যদি এখন এই ১৮ থেকে ২৫ বছর বয়সের কন্টাক্টগুলো থেকে শুধুমাত্র মহিলাগুলোকে আলাদা করতে চাই তাহলে কন্টাক্ট সার্ভিসে(ContactService) নতুন কোনো মেথড যুক্ত করতে হবে না। শুধুমাত্র কল করার সময় আমাদের কোডের বিহেবিয়ারটুকু আর্গুমেন্ট হিসেবে পাস করলেই হয়ে যাচ্ছে।


1
2
3
4
5
6
7
8
List<Contact> contactAged18To25Female = contactService.findContacts(contactList, new FilterCriteria() {
    @Override
    public boolean match(Contact contact) {
        return contact.getAge() >= 18
                && contact.getAge() <= 25
                && contact.getSex() == Contact.Sex.FEMALE;
    }
});

এভাবে আমরা অ্যানোনিমাস ইনার ক্লাস ব্যবহার করে বিহেবিয়ার প্যারামিটারাইজড করতে পারি। এতে করে অনে কম কোড লিখে অনেক বেশি ফিচার তৈরি করা যাচ্ছে।

তবুও উপরের কোডটিতে খেয়াল করলে দেখা যাবে যে বেশ কিছু কোড বারবার লিখতে হচ্ছে।

চিত্র: প্রয়োজনীয় কোড লজিক

এই কোডটিতে শুধুমাত্র লাল রঙ্গের অংশটুকু আমাদের প্রয়োজনীয় কোড, বাকিটুকু অর্থাৎ নিউ অপারেটর থেকে শুরু করে, ওভারাইড অ্যানোটেশন, পাবলিক কিওয়ার্ড, রিটার্ন টাইপ, মেথডের নাম ইত্যাদি একই রয়ে গেছে পত্যেকবার কল করার সময়। এই কোডগুলোকে boilerplate কোড বলা হয়।

তবে জাভা ৮ ল্যাম্বডা এক্সপ্রশন আসার সুবাধে এই boilerplate কোডটুকুকেও বাদ দেওয়া যায়। উদাহরণ -


1
2
3
4
5
6
List<Contact> contactAged18To25Female = contactService.findContacts(contactList,
        (Contact contact) -> {
            return contact.getAge() >= 18
                    && contact.getAge() <= 25
                    && contact.getSex() == Contact.Sex.FEMALE;
        });

উপরের কোডটুকু লক্ষ্য করুন, এতে শুধুমাত্র প্রয়োজনীয় কোডটুকু রয়েছে। তাহলে আমাদের প্রথম ল্যাম্বডা এক্সপ্রেশন লেখা হয়ে গেলো।

এই প্যারামিটাইরাইজড কোডটুকু আলাদা করে লিখলে -


1
2
3
4
5
6
(Contact contact) -> {
    
    return contact.getAge() >= 18
            && contact.getAge() <= 25
            && contact.getSex() == Contact.Sex.FEMALE;
};

যা শুরুতে ল্যাম্বডা নিয়ে কথা বলার সময় যে উদাহরণ দেওয়া হয়েছিল তার সঙ্গে মিলে যায়।

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

আরও একটি ব্যবাহার দেখা যাক-


1
2
3
4
5
6
7
File root = new File("./");
File[] files = root.listFiles(new FileFilter() {
    @Override
    public boolean accept(File pathname) {
        return pathname.isFile();
    }
});

অনেক সময় আমাদের ফাইল লিস্টিং করতে হয়, অর্থাৎ একটি ফোল্ডারে যে ফাইলগুলো  রয়েছে তা বিভিন্নভাবে ফিল্টার করে তার লিস্ট করা যায়। এক্ষেত্রে উপরের কোডটি রুট ফোল্ডারে যে ফাইলগুলো ডিরেক্টরী নয় শুধুমাত্র ফাইল(জাভাতে ইউনিক্স সিস্টেম অনুসরণ করে ফাইল এবং ডিরেক্টরী দুটুই একটি ক্লাস java.io.File দিয়ে অ্যাবস্ট্র্যাক্ট করা হয়েছে), সেগুলোকে লিস্ট করবে।

তবে উপরের কোডটুকু সংক্ষিপ্ত করে ল্যামডা এক্সপ্রেশন ব্যবহাv করে এভাবে লেখা যায় -


1
File[] files = file.listFiles(pathname -&gt; pathname.isFile());

কিংবা আরও সংক্ষেপে -


1
File[] files = file.listFiles(File::isFile); //method reference

এখানে মেথড রেফারেন্স ব্যবহার করা হয়েছে।

এই আর্টিক্যাল এ শুধুমাত্র ল্যামডা এক্সপ্রেশনের সাধারণ ব্যবহার দেখানো হলো। পরিবর্তীতে পর্বে এ নিয়ে আরও বিস্তারিত থাকবে।