[২.০] ফাংশন এবং প্যাকেজ ( Functions and Packages )
এতক্ষণে আমরা Go এর মৌলিক সিনট্যাক্সগুলো, ডাটা টাইপ, কন্ডিশন এবং লুপ শিখে ফেলেছি। এখন আমরা এগুলো বাস্তবে প্রয়োগ করব । ধরি আমাদের একটি দোকান আছে যেখানে কাস্টমার প্রতিটা প্রোডাক্টে কিছু ছাড় পাবে, যদি সে ঐ পণ্যে বেশি পরিমান অর্থ ব্যয় করে । ধরি কেউ কোন একটা প্রোডাক্টে ১০০০ এর বেশি ব্যয় করেছে তাহলে সে ওই প্রোডাক্টে ৫% ডিসকাউন্ট পাবে । তাহলে প্রথমে ওই প্রোডাক্টের মূল্য এবং কাস্টমার কয়টি প্রোডাক্ট কিনেছে সেটা থেকে প্রোডাক্টের মোট মূল্য বের করতে হবে এবং যদি তা এক হাজারের বেশি হয় তাহলে আমরা তাদের ওই প্রোডাক্টে ৫% ডিসকাউন্ট দিব। তাহলে কোডটা দেখে নেই –
package main
import "fmt"
func main() {
var discount float64 = 0.95
var price, total float64
var count int
price = 152.50
count = 3
total = price * float64(count)
if total > 1000.0 {
total *= discount
}
fmt.Println("Total payable: ", total)
price = 1500.00
count = 7
total = price * float64(count)
if total > 1000.0 {
total *= discount
}
fmt.Println("Total payable: ", total)
}
আউটপুট –
Total payable: 457.5
Total payable: 9975
এখানে একজন ১৫২.৫০ টাকার তিনটি প্রোডাক্ট কিনেছে। আমরা মোট মূল্য বের করব। যেহেতু এখানে count হল ইন্টিজার এবং price এর ডাটা টাইপ float64 তাই গুন করার জন্য আমাদেরকে count কে float64 এ পরিবর্তন করে নিতে হবে । এর পরে আমরা চেক করব যদি মোট মূল্য ১০০০ বেশি হয় তাহলে তার ওপর আমরা 5% ডিসকাউন্ট দিব অর্থাৎ 0.95 দিয়ে গুন দেবো । এরপর সেটা আমরা প্রিন্ট করব ।
ধরি সে আরেকটি প্রোডাক্ট কিনলো ১৫০০ টাকার এবং count হল 7 তাহলে একই ভাবে আমরা total payable বের করবো ।
আমাদের কোড সুন্দরভাবে কাজ করে কিন্তু এখানে একটা সমস্যা আছে । সেটা হলো এখানে অনেকখানি কোড ডুপ্লিকেট হয়েছে এবং এটার অবস্থা আরো জটিল হবে যখন কেউ আরো বেশি প্রোডাক্ট কিনে।
এই সমস্যাটা আমরা সমাধান করতে পারি ফাংশন দিয়ে । তাহলে দেখে আসি এই ফাংশন জিনিসটা কি …
[২.১] ফাংশন ( Functions )
সহজ ভাষায় ফাংশন হল কিছু কোড বা অপারেশনের সমষ্টি, যা কোন নির্দিষ্ট কাজ সম্পন্ন করে চাহিদামতো রেজাল্ট রিটার্ন করে এবং এই কোড ব্লকটি প্রোগ্রামের এর বিভিন্ন জায়গা থেকে কল করা যায়। প্রায় সব প্রোগ্রামিং ল্যাঙ্গুয়েজ এর মতই, Go তেও ফাংশন আছে এবং আমরা এতক্ষন না জেনেই অনেক ফাংশন ব্যবহার করে আসছি যেমন Println, যা fmt প্যাকেজের একটি ফাংশন ।
এখন আমরা কিভাবে ফাংশন লিখতে হয় সেটা শিখব । একটা সিম্পল ফাংশন দেখতে অনেকটা এরকম হয় –
- একটি ফাংশনের শুরুতে func কিওয়ার্ড থাকবে ।
- এরপরে ফাংশন এর নাম ।
- তারপর ফাস্ট ব্র্যাকেট থাকে যেখানে আর্গুমেন্ট থাকতে পারে যা আমরা একটু পরে শিখব।
- এর পরে ফাংশন এর ব্লক থাকে যেখানে আমরা কোড করতে পারি । একটি ফাংশনের ব্লক শুরু এবং শেষ হয় সেকেন্ড ব্র্যাকেট দিয়ে ।
ফাংশন ডিক্লেয়ার করার পরে আমরা প্যাকেজের অন্য যেকোনো জায়গা থেকে ফাংশন এর নাম এবং ফাস্ট ব্র্যাকেট দিয়ে ফাংশনটা কল করতে পারি বা এর ভেতরের কোড রান করতে পারি –
package main
import "fmt"
func Hello() {
fmt.Println("Hola!")
}
func main() {
Hello() //Hello function called and prints “Hola!” in console
}
এখানে Hello ফাংশনকে কল করা হয়েছে তাই “Hola” প্রিন্ট হবে ।
- Go তে অ্যাক্সেস মডিফায়ারঃ অন্যান্য অনেক ল্যাঙ্গুয়েজে অ্যাক্সেস মোডিফায়ার থাকে যেটা কোন ফাংশন প্রাইভেট নাকি পাবলিক এটা বোঝানোর জন্য ব্যবহৃত হয়। Go তে অ্যাক্সেস মডিফায়ার এর কাজটা হয় ফাংশনের প্রথম অক্ষরের উপর ভিত্তি করে । কোন ফাংশনের প্রথম অক্ষর যদি ক্যাপিটাল বা বড় হাতের অক্ষর হয় তাহলে সেটা পাবলিক বা exported এবং ছোট হাতের অক্ষর হলে সেটা প্রাইভেট । প্রাইভেট ফাংশন ওই প্যাকেজ বা ফোল্ডারের বাইরে অ্যাক্সেস করা যায় না তাই যদি আমরা কোন ফাংশন কে অন্য প্যাকেজে ব্যাবহার করতে চাই তাহলে তার প্রথম অক্ষর অবশ্যই বড় হাতের অক্ষর হতে হবে । যেমন Println ফাংশনের প্রথম অক্ষর ক্যাপিটাল হওয়ার কারনেই আমরা যে কোন জায়গা থেকে Println কল করতে পারি । প্যাকেজ নিয়ে পরে আমরা আরো বিস্তারিত ভাবে জানব ।
- ফাংশনে প্যারামিটার পাস করাঃ আমরা চাইলে ফাংশনে এক বা একাধিক প্যারামিটার পাস করতে পারি । প্যারামিটারগুলো ওই ফাংশনের লোকাল ভ্যারিয়েবলের মত কাজ করে এবং তাদের ভ্যালু সেট হয় যখন আমরা সেই ফাংশন কল করি । ফাংশন ডিক্লেয়ার করার সময় ফাংশনের প্রথম ব্র্যাকেটের মধ্যে আমরা কমা দিয়ে প্যারামিটার গুলো ডিক্লেয়ার করতে পারি এবং অবশ্যই প্রত্যেকটা প্যারামিটারের নামের পরে তার ডাটা টাইপ (int, bool etc) উল্লেখ করতে হবে । ফাংশনটি কল করার সময় এই প্যারামিটারগুলো একই ক্রমে সাজিয়ে পাস করতে হবে –
package main
import "fmt"
func Repeat(text string, times int) {
for i := 0; i < times; i++ {
fmt.Println(text)
}
}
func main() {
Repeat("Hola", 5) //prints Hola 5 times
}
এই ফাংশনে আমাদের দুইটি প্যারামিটার আছে, একটি string টাইপের এবং একটি ইন্টিজার টাইপের । তাই main ফাংশন থেকে এই Repeat ফাংশন কল করার সময় আমাদের ২টি প্যারামিটার দিতে হবে,যদি একটি string এবং অন্যটি ইন্টিজার তাহলে ফাংশনের ভেতরের কোড এক্সিকিউট হবে । এক্ষেত্রে Hola পাঁচবার প্রিন্ট হবে ।
চলুন এখন তাহলে আমরা আমাদের আগের প্রোগ্রামটায় ফাংশন অ্যাড করি –
package main
import "fmt"
func main() {
CalculatePayable(152.50, 3) //called function
CalculatePayable(1500, 7)
}
// Function Name start with uppercase means public function
// CalculatePayable calculate the total payable bill
func CalculatePayable(price float64, count int) {
var discount float64 = 0.95
total := price * float64(count)
if total > 1000.0 {
total *= discount
}
fmt.Println("Total payable: ", total)
}
আউটপুট –
Total payable: 457.5
Total payable: 9975
এখন আমাদের প্রোগ্রামটা আগের থেকে গোছানো ও সিম্পল হয়েছে এবং এখানে কোন ডুপ্লিকেট কোডও নাই । এখন সেই কাস্টমার যদি আরো প্রোডাক্ট ক্রয় করে তাহলে আমরা এই ফাংশন ব্যবহার করে সহজেই তার total payable বের করতে পারবো ।
[২.১.১] ফাংশন থেকে এক বা একাধিক ভ্যালু রিটার্ন
এখানে আমাদের এই ফাংশনটা total ভ্যালু print করে দেয় কিন্তু এই total ভ্যারিয়েবল টা ফাংশনের ব্লকে থাকার কারনে ফাংশনের বাইরে এই ভ্যারিয়েবলের ভ্যালু আমরা পাই না । তাহলে কিভাবে আমরা সেই ভ্যালু পেতে পারি ?
একটি ফাংশন যে কোন টাইপের ভ্যালু রিটার্ন করতে পারে। সেক্ষেত্রে কোন টাইপের ভ্যালু রিটার্ন করবে সেটা ফাংশন ডিক্লেয়ার করার সময় প্যারামিটার ডিক্লেয়ার করার পরে উল্লেখ করে দিতে হবে , তারপর ফাংশন ব্লকের মধ্যে return কিওয়ার্ডের পরে যে ভ্যালু আমরা রিটার্ন করতে চাই তা দিতে হবে এবং আমাদের অবশ্যই মনে রাখতে হবে যে তার ডাটা টাইপ এবং রিটার্ন টাইপ একই হতে হবে –
//square returns square value of a number
func square(num float64) float64 {
return math.Pow(num, 2)
}
এখানে square ফাংশনটি একটি float64 টাইপ এর ভ্যালু রিটার্ন করবে । math.Pow(x, y) ফাংশনটি x ^ y হিসাব করে একটি float64 রিটার্ন করে ।
এভাবে আমরা কোন ফাংশন থেকে একটি ভ্যালু রিটার্ন পেতে পারি এবং সেই ভ্যালু আমরা চাইলে কোন ভ্যারিয়েবল এর মধ্যে রাখতে পারি অথবা তা আরেকটি ফাংশনের প্যারামিটার হিসেবেও ব্যবহার করতে পারি –
package main
import (
"fmt"
)
func main() {
ans := square(3)
fmt.Println(ans)
fmt.Println(square(5)) // called square function and print value
}
চলুন তাহলে আমরা আমাদের আগের প্রোগ্রামটায় এই বিষয়টি ইমপ্লিমেন্ট করি এবং বের করি একজন কাস্টমারকে মোট কত টাকা দিতে হবে । এক্ষেত্রে আমরা ফাংশন থেকে প্রত্যেকটা প্রোডাক্টে কত টাকা দিতে হবে সেটা বের করব। এরপর সব যোগ করে মোট কত টাকা দিতে হবে এটা বের করব –
package main
import "fmt"
func main() {
var amount, total float64
amount = CalculatePayable(152.50, 3)
fmt.Println("Product one: ", amount)
total += amount
amount = CalculatePayable(1500, 7)
fmt.Println("Product two: ", amount)
total += amount
fmt.Println("Total: ", total)
}
// CalculatePayable returns the calculated payable bill
func CalculatePayable(price float64, count int) float64 {
var discount float64 = 0.95
total := price * float64(count)
// check total amount is greater than 1000 or not
if total > 1000.0 {
total *= discount
}
return total
}
আউটপুট –
Product one: 457.5
Product two: 9975
Total: 10432.5
Go তে কোন একটি ফাংশন একাধিক টাইপের ভ্যালু রিটার্ন করতে পারে। এক্ষেত্রে প্যারামিটার এর পরে আরেকটি ব্র্যাকেট দিয়ে সেখানে রিটার্ন টাইপগুলো কমা দিয়ে দিয়ে লিখতে হবে –
package main
import "fmt"
func returnMany() (int, string, bool) {
return 5, "Hello", true
}
func main() {
myInt, myString, myBool := returnMany()
fmt.Println(myInt, myString, myBool)
}
এখন কেউ যদি ভুল করে প্রোডাক্টের পরিমাণ(count) অথবা প্রোডাক্টের দামে(price) এ নেগেটিভ ভ্যালু দিয়ে দেয় তাহলে কি করা যেতে পারে?
এই সমস্যার সমাধান হিসেবে আমাদের একটা উপায় খুঁজে বের করতে হবে যেখানে ফাংশনটা বলে দিতে পারবে যে price বা count এর ভ্যালু নেগেটিভ হয়েছে ।
একটি সহজ উপায় হচ্ছে, যদি নেগেটিভ ভ্যালু পাস করা হয় সেক্ষেত্রে আমরা সাথে একটি string ভ্যালু রিটার্ন করব, যেখানে বিষয়টা উল্লেখ থাকবে এবং সবকিছু ঠিকঠাক থাকলে তা ফাকা থাকবে। এরপরে ফাংশন কল করার সময় আমরা ওই string চেক করব এবং যদি তা ফাকা না হয় তাহলে আমরা বুঝবো কোন ঝামেলা হয়েছে –
package main
import "fmt"
// CalculatePayable returns the calculated payable bill
func CalculatePayable(price float64, count int) (float64, string) {
var discount float64 = 0.95
// check the given price and count is negative or not
if price < 0 || count < 0 {
return 0, "price and count can not be negative"
}
total := price * float64(count)
if total > 1000.0 {
total *= discount
}
return total, ""
}
func main() {
total, message := CalculatePayable(-152.50, 3)
if message != "" {
fmt.Println(message)
} else {
fmt.Println("Total payable: ", total)
}
}
আউটপুট –
price and count can not be negative
এর মধ্য দিয়ে আমরা কোন ফাংশন থেকে একাধিক ভ্যালু রিটার্ন করার প্রয়োগ ও দেখে ফেললাম।
বিঃদ্রঃ Go তে একটি error টাইপ আছে যেটা দিয়ে কোডের সমস্যার কথা জানান দেয়া হয়। error সম্পর্কে আমরা পরে বিস্তারিত জানতে পারব তাই আপাতত আমরা string দিয়ে কাজ চালাই। error শেখার পরে আবার এখানে এসে string এর বদলে error দিয়ে ফাংশনটা লিখব।
এখানে জেনে রাখা ভালো ফাংশনের যে প্যারামিটার যায় সেটা পাস বাই ভ্যালু হিসেবে যায় অর্থাৎ ফাংশন এর মধ্যে সেই প্যারামিটারের ভ্যালু চেঞ্জ করা হলেও ফাংশনের বাইরে সেই চেঞ্জ টা পাওয়া যায় না । আমরা যদি চাই ফাংশনের বাইরেও সেই ভ্যালুটা চেঞ্জ হোক তাহলে পয়েন্টার ব্যবহার করতে হবে । পয়েন্টারের ব্যবহার আমরা পরে বিস্তারিত জানতে পারবো।
[২.১.২] ভ্যারিয়েডিক ফাংশন ( Variadic Function )
আমরা হয়তো লক্ষ্য করে থাকবো Println ফাংশনটায় কমা দিয়ে দিয়ে অনেকগুলো প্যারামিটার পাস করা যায় । এখানে প্যারামিটারের সংখ্যা নির্দিষ্ট নয়, প্যারামিটার থাকতেও পারে, নাও থাকতে পারে আবার এক বা একাধিকও থাকতে পারে । চাইলে আমরাও এমন ফাংশন তৈরি করতে পারি, আর এটাকেই বলা হয় ভ্যারিয়েডিক ফাংশন –
package main
import "fmt"
func main() {
myFunc()
myFunc(1)
myFunc(1, 2)
myFunc(1, 2, 3, 4, 5)
}
func myFunc(numbers ...int) {
fmt.Println(numbers)
}
আউটপুট –
[]
[1]
[1 2]
[1 2 3 4 5]
এক্ষেত্রে যে ভ্যারিয়েবলটির সংখ্যা নির্দিষ্ট নয় সেটির ডাটা টাইপের আগে “…” নোটেশন দিতে হবে, যা সকল পাস করা ভ্যালুকে একত্রে একটি স্লাইস হিসেবে রিসিভ করবে। এখন স্লাইস এর সব অপারেশন আমরা এর উপর করতে পারি যেমন এর উপর লুপ চালিয়ে আমরা এর ভ্যালু গুলো অ্যাক্সেস করতে পারি বা প্রিন্ট করতে পারি –
package main
import "fmt"
func main() {
myFunc("Hello World")
myFunc("Hello World", 1, 2)
myFunc("Hello World", 1, 2, 3, 4, 5)
}
func myFunc(mystring string, numbers ...int) {
fmt.Println(mystring)
for number := range numbers {
fmt.Println(number)
}
}
আউটপুট –
Hello World
Hello World
0
1
Hello World
0
1
2
3
4
এখন আমাদের কাছে যদি আগে থেকে একটি স্লাইস বা অ্যারে থাকে এবং সেটাকে আমরা এই ফাংশনে পাস করাতে চাই তাহলে সেটার নামের পরে “…” নোটেশন দিয়ে তা আমরা পাস করাতে পারি –
package main
import "fmt"
func main() {
mySlice := []int{1, 2, 3, 4, 5} // initialize slice with elements
myFunc("Hello World", mySlice...)
}
func myFunc(mystring string, numbers ...int) {
fmt.Println(mystring)
//print all slice element with range for loop
for number := range numbers {
fmt.Println(number)
}
}
আউটপুট –
Hello World
0
1
2
3
4
[২.১.৩] ডেফার ( Defer )
“defer” হলো এমন একটি কিওয়ার্ড যা কোনো ফাংশন বা মেথড কল করার আগে ব্যবহার করা হলে সেই ফাংশন বা মেথড টি ততক্ষণ পর্যন্ত এক্সিকিউট হবে না যতক্ষণ পর্যন্ত ডেফারযুক্ত লাইনটি যেই ফাংশনে আছে সেই ফাংশনের সব কাজ শেষ না হয় ।
অর্থাৎ যদি একটি ফাংশন এর মধ্যে defer দিয়ে আরেকটি ফাংশন কল করা হয় তাহলে প্রথম ফাংশনটি এক্সিকিউট হয়ে রিটার্ন হওয়ার আগে ডেফার যুক্ত ফাংশনটি এক্সিকিউট হবে। এক্ষেত্রে হতে পারে প্রথম ফাংশনটি স্বাভাবিকভাবে return করেছে অথবা কোন এরর এর কারনে মাঝপথে return করেছে বা কোন কারণে অস্বাভাবিকভাবে প্রোগ্রাম বন্ধ হয়ে গেছে । যাই হোক না কেন ডেফার এর স্টেটমেন্ট টুকু এক্সিকিউট হবে । এটা অনেকটা “finally” ব্লকের মত কাজ করে –
package main
import "fmt"
func main() {
defer fmt.Println("Good bye!")
fmt.Println("Hello world")
}
এখানে আউটপুট হবে –
Hello world
Good bye!
আমরা শুধুমাত্র কোন ফাংশন বা মেথড কল এর আগেই defer ব্যবহার করতে পারি, অন্য কোনো স্টেটমেন্ট এর আগে এটি ব্যবহার করা যাবে না আর যদি কোন ফাংশনে যদি আমরা একাধিক defer স্টেটমেন্ট ব্যবহার করি তাহলে এটা উল্টা দিক থেকে এক্সিকিউট হবে মানে শেষের স্টেটমেন্টটা আগে এক্সিকিউট হবে –
package main
import "fmt"
func main() {
defer fmt.Println("Good bye!")
defer fmt.Println("হ্যালো")
defer fmt.Println("Hola!")
fmt.Println("Hello")
}
আউটপুট –
Hello
Hola!
হ্যালো
Good bye!
defer সাধারনত ব্যবহার করা হয় ফাইনাল কোনো টাস্ক অথবা ক্লিনআপ অপারেশনের জন্য, যেমন প্রোগ্রাম থেকে বের হওয়ার আগে প্রয়োজনীয় রিসোর্স পরিষ্কার করতে বা কোন ফাইল ওপেন থাকলে তা ক্লোজ করার জন্য।
[২.১.৪] নেমড রেজাল্ট প্যারামিটার ( Named Result Parameter )
আমরা চাইলে ফাংশনের রিটার্ন প্যারামিটার গুলোর নামও দিতে পারি। যদি রিটার্ন প্যারামিটারগুলোর নাম থাকে তাহলে রিটার্ন করার সময় শুধু return লিখলেই হবে । এগুলো ফাংশনের শুরুতে ডিফাইন করা ভ্যারিয়েবল এর মত আচরণ করে অর্থাৎ এই ভ্যারিয়েবল গুলো ফাংশন এর মধ্যে ব্যবহার করা যাবে এবং ভ্যালু অ্যাসাইন করা যাবে । এক্ষেত্রে রিটার্ন করার সময় এদের মধ্যে ওই সময়ে অ্যাসাইন থাকা ভ্যালুটাই রিটার্ন হবে ।
আমরা যদি আমাদের আগের CalculatePayable ফাংশনটিতে নেমড রিটার্ন প্যারামিটার ব্যবহার করি তাহলে ফাংশনটি এমন হবে –
package main
import "fmt"
// CalculatePayable returns the calculated payable bill
func CalculatePayable(price float64, count int) (total float64, msg string) {
var discount float64 = 0.95
if price < 0 || count < 0 {
msg = "price and count can not be negative"
return
}
total = price * float64(count)
if total > 1000.0 {
total *= discount
}
return
}
func main() {
total, message := CalculatePayable(152.50, 3)
if message != "" {
fmt.Println(message)
} else {
fmt.Println("Total payable: ", total)
}
}
এখানে ফাংশনের শুরুতেই total এবং msg এ ডিফল্ট ভ্যালু float64 এর জন্য 0 এবং string এর জন্য “” সেভ হয়ে থাকে । এর পরে ফাংশন এর মধ্যে যদি কোন ভুল হয় তাহলে আমরা msg এ আমাদের message string টা রাখব আর সব কিছু ঠিক থাকলে total এ টোটাল ভ্যালুটা রাখব । এরপরে শুধু return কিওয়ার্ড ব্যবহার করে আমরা ফাংশনের যে কোন জায়গা থেকে রিটার্ন করতে পারি ।