নেক্সট স্টেপস ইন স্ক্যালা

Last updated 3 months ago

এই চ্যাপ্টার এ আমরা কিছু স্ক্যালা কন্ট্রোল-ফ্লো নিয়ে আলোচনা করবো -

খুব ইন্টারেস্টিং কিছু নাও মনে হতে, শুধু মাত্র কিছু ভ্যারিয়েবল ডিক্ল্যারেশান এবং এক্সপ্রেশান এর ডেমনস্ট্রেশান। তাহলে আমরা আমাদের ইন্টারপ্রেটার-এ ফিরে যাই-

শুরুতে আমরা একটি সিম্পল এক্সপ্রেশান লিখি - একটি সংখ্যা এবং আরেকটি সংখ্যা যোগ করি । ইতপূর্বে আমরা যদিও করেছি-

scala> 1 + 1
res0: Int = 2

একটু সফিস্তিকেটেড কোড লিখতে হলে আমাদের ভ্যারিয়েবল এর দরকার হয়।

scala> var x: Int = 1 + 1
x: Int = 2

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

এখানে var বলতে বুঝায় x একটি মিউটেবল ভ্যারিয়েবল অর্থাৎ এর মান আমরা পরিবর্তন করতে পারি।

scala> x = 5
x: Int = 5

এবার অন্য ভ্যারিয়েবল দিয়ে চেষ্টা করি-

scala> var s : String = "Hello "
s: String = "Hello "

আমরা স্ট্রিং Concatenate করতে পারি-

scala> s = s + "scala!"
s: String = Hello scala!

আমরা এবার অন্যান্য ল্যংগুয়েজর এর মতো রেগুলার কন্ট্রোল ফ্লো দেখি -

scala> if (true) x = x + 12 else x = x * 13

এক্ষেত্রে কনসোল কোন কিছু প্রিন্ট করে নি। এটি একটি রেগুলার কন্ট্রোল - ফ্লো আমরা যদি এখন x এর মান দেখতে চাই, তাহলে -

scala> x
res3: Int = 17

আমরা একটু ইন্টারেস্টিং জিনিস দেখি। স্ক্যালাতে if – else শুধুমাত্র একটা স্টেটমেন্ট হিসেবে ব্যবহার না করে একে এক্সপ্রেশান হিসেবে ব্যবহার করা যায়।

scala> x = if (true) x + 12 else x * 13
x: Int = 29

এর মানে, x হতে পারে x + 12 অথবা x * 13 এটি নির্ভর করে কন্ডিশন ভ্যালু এর উপর।

ইন্টারেস্টিং। আমরা চাইলে এখন এই এক্সপ্রেশান ব্যবহার করে আরও কমপ্লেক্স এবং সফিস্তিকেটেড এক্সপ্রেশান তৈরি করতে পারি।

scala> var x = 5
x: Int = 5
scala> var y = if (false) {
| x + 1
| } else {
| if (true) {
| x - 1
| } else {
| x -2
| }
| }

ইন্টারেষ্টিং লজিকাল কুনানড্রাম !

এখন y এর মান কত হতে পারে ?

যেহেতু , শুরুর if ব্লক-এ false তাই এটি else ব্লকে যাবে, এবং এখানের if ব্লক true তাই রেজাল্ট হবে x – 1

অর্থাৎ -

y: Int = 4

আমরা জানি আরও এক ভাবে ভ্যারিয়েবল ডিক্ল্যায়ার করা যায় – সেটি হলো val

scala> val z = 1 + 1
z: Int = 2

এখানে z হল- ইমিউটেবল ভ্যারিয়েবল ,অর্থাৎ আমরা চাইলে এতে ভ্যালু রি-এসাইন করতে পারবো । এবং আমরা যদি করতেও চাই, সাথে সাথে কম্পাইলার ইরর দেখাবে -

scala> z = z + 1
<console>:8: error: reassignment to val
z = z + 1
^

কিন্তু আমি এখানে একটা জিনিস মিস করে গেছি । একটু খেয়াল করলেই দেখা যাবে যে var y = … এই এক্সপ্রেশান এ কোন টাইপ ডেসক্রিপশান লিখিনি। কিন্তু তার পরেও এটি ঠিকঠাক মতো কাজ করে গেছে। আমরা ভাল করেই জানি যে স্ক্যালা স্ট্যাটিক ল্যাংগুয়েজ এবং কম্পাইল ল্যাংগুয়েজ। সুতরাং কম্পাইলার কম্পাইল টাইম এ সব স্ট্যাটিক্যালি টাইপ চেক করার কথা। কিন্তু এখানে আমরা কোন টাইপ দেইনি বলে ইরর দেখানোর কথা ছিল। কিন্তু স্ক্যালা কম্পাইলার তা করে নি। স্ক্যালা অন্যান্য স্ট্যাটিক ল্যাংগুয়েজ যেমন- জাভা, সি এর মতোই। কিন্তু স্ক্যালাতে একটি মজার জিনিস আছে যাকে আমরা বলি টাইপ ইনফারেন্স(Type Inference)। স্ক্যালা এক্সপ্রেশান এর টাইপ থেকে এখানে y ভ্যারিয়েবল এর টাইপ ইনফার করতে পারে।

নিচের উদাহরণটি দেখি -

scala> var x = 1

এখানে এক্সপ্রেশান হচ্ছে ভ্যালু 1 এবং এটি ইন্টিজার। সুতরাং এটি থেকেই বুঝা যাচ্ছে যে x টাইপ হবে Int.

এভাবে অন্যান্য টাইপ এর ক্ষত্রেও এটি টাইপ ইনফার করতে পারে।

এখানে আরো একটি ইন্টারেস্টিং জিনিস খেয়াল করি - আমাদের এক্সপ্রেশান যদি এমন হয়-

scala> :t if (false) "hello" else 1
Any

অথবা -

scala> :t if (true) "hello" else 1
Any

Note: এখানে :t এক স্পেশাল অপারেটর যা দিয়ে আমরা টাইপ ডেসক্রিপশান বের করতে পারি।

এখানে দেখা যাচ্ছে যে দুটি ক্ষেত্রেই টাইপ হচ্ছে Any

এর মানে কি? এর উত্তর দেখতে হলে আমাদের দেখতে হবে -scala type lattice

Scala Type Latics

এটি হলো স্ক্যালা এর টাইপ হায়ারার্কি। এই হায়ারার্কি এর একদম উপরে আছে Any। এটি দুই প্রকর হতে পারে। ভ্যালু টাইপ এবং রেফারেন্স টাইপ। AnyVal হচ্ছে সকল প্রিমিটিভ টাইপ এর সাব ক্লাস । অর্থাৎ এগুলো হচ্ছে বিল্ট-ইন টাইপ যেগুলো JVM এ থাকে - যেমন Floating point, integer ইত্যাদি। তবে এখানে ডায়াগ্রাম এ Byte আসলে Short কে extends করে না এভাবে Short আসলে Int কে extends করে না। এটি শুধুমাত্র ডায়াগ্রাম তৈরির সুবিধার্থে করা হয়েছে। আর ডান পাশের গুলো হচ্ছে রেফারেন্স টাইপ – ক্লাস , অ্যারে ইত্যাদি, যেমন String, List . এখানে একটা জিনিস বলে নেই, সব গুলো টাইপ এর একটি কমন সাব ক্লাস আছে, সেটি হলো - Null. অর্থাৎ যেকোন টাইপ আসলে Null হতে পারে।

নিচের একটি চার্ট দেওয়া হলো-

Data Type

Description

Byte

8 bit signed value. Range from -128 to 127

Short

16 bit signed value. Range -32768 to 32767

Int

32 bit signed value. Range -2147483648 to 2147483647

Long

64 bit signed value. -9223372036854775808 to 9223372036854775807

Float

32 bit IEEE 754 single-precision float

Double

64 bit IEEE 754 double-precision float

Char

16 bit unsigned Unicode character. Range from U+0000 to U+FFFF

String

A sequence of Chars

Boolean

Either the literal true or the literal false

Unit

Corresponds to no value

Null

null or empty reference

Nothing

The subtype of every other type; includes no values

Any

The supertype of any type; any object is of type Any

AnyRef

The supertype of any reference type

এবার আমরা আবার ইন্টারপ্রেটারে ফিরে যাই -

scala> :t if (false) "hello" else 1
Any

এবং

scala> :t if (true) "hello" else 1
Any

এর দুটির টাইপ এখানে Any। কেন এর উত্তর আমরা ল্যাটিস থেকেই দেখতে পারছি যে, এদের কমন ancestor হচ্ছে - Any

আমরা আরও কয়েকটি উদাহরণ দেখি -

scala> :t if(true) 1 else 1.0
Double

লক্ষ করি, এটির টাইপ হচ্ছে Double কারণ Int কে কনভার্ট করে Double বাননো যায়।

এবার আমরা অন্য একটি বিষয় লক্ষ্য করি -

scala> val s:String = if(true) "hello" else 1
<console>:7: error: type mismatch;
found : Int(1)
required: String
val s:String = if(true) "hello" else 1

আমরা দেখতে পাচ্ছি যে , এখানে s এর টাইপ হচ্ছে String কিন্তু এক্সপ্রেশান থেকে তা String অথবা Int যে কোনটি হতে পারে, এবং সিটি নির্ধিরণ হবে রানটাইম-এ কিন্তু যেহেতু স্ক্যালা একটি কম্পাইল্ড ল্যাংগুয়েজ, তাই এটি type mismatch ইরর দিচ্ছে।

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

আমরা যেহেতু ভ্যারিয়েবল ব্যবহার করতে পারি, সুতরাং ফাংশন নিয়ে কথা বলি।

নিচের ফাংশনটি দেখি -

scala> def add(a: Int, b: Int): Int = a + b
add: (a: Int, b: Int) Int
scala> add(3,4)
res3: Int = 7

দেখা যাচ্ছে যে এটি দুটি Int টাইপ ডাটা নিয়ে তা যোগ করে রিটার্ন করে।

কিন্তু এই ফাংশনটি যদি এভাবে লিখি -

scala> def add(a , b) = a + b
<console>:1: error: ':' expected but ',' found.
def add(a , b) = a + b
^

দেখা যাচ্ছে যে, এটি কাজ করছে না । যদিও আমরা a + b থেকে বুঝে নিতে পারি এটি দুটি Int হতে পারে। কিন্তু ব্যপারটি হচ্ছে a + b দুটি Double অথবা Float ও হতে পারে।

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

তবে আমরা চাইলে রিটার্ন টাইপ নাও দিতে পারি। এক্ষেত্রে স্ক্যালা এটিকে ইনফার করে নিতে পারে। উদাহরণ-

scala> def add(a: Int, b: Int) = a + b
add: (a: Int, b: Int)Int
scala> add( 3, 4)
res4: Int = 7

কারন এক্ষত্রে আমরা যেহেতু a এবং b এর টাইপ জানি, সুতরাং এটির এক্সপ্রেশান থেকে টাইপ ইনফার করা যাচ্ছে।

তবে কিছু সিচুয়েশান আছে যেখানে স্ক্যালা রিটার্ন টাইপ ইনফার করতে পারে না । বিশেষত রিকার্সিভ মেথড গুলোর ক্ষেত্রে।

উদাহরণসরূপ ফিবোনাচি নাম্বার এর জন্যে একটি ফাংশান লিখি -

scala> def fib(n: Int) = if (n==1 || n ==2) 1 else fib(n -1) + fib(n-2)
<console>:7: error: recursive method fib needs result type
def fib(n: Int) = if (n==1 || n ==2) 1 else fib(n -1) + fib(n-2)
^

সুতরাং দেখা যাচ্ছে এক্ষেত্রে এটি রিটার্ন টাইপ ইনফার করতে পারে নি। কিন্তু যদি আমরা রিটার্ন টাইপ দিয়ে দিই, তাহলে এটি খুব ভালভাবে কাজ করে –

scala> def fib(n: Int): Int = if (n==1 || n ==2) 1 else fib(n -1) + fib(n-2)
fib: (n: Int)Int
scala> fib(2)
res5: Int = 1
scala> fib(3)
res6: Int = 2
scala> fib(4)
res7: Int = 3
scala> fib(5)
res8: Int = 5
scala> fib(6)
res9: Int = 8

সুতরাং মনে রাখতে হবে যে, রিকার্সিভ ফাংশান এর ক্ষেত্রে অবশ্যই রিটার্ন টাইপ দিতে হবে।