Day 3: Functions, Records & Patterns

Master the art of writing reusable functions with named parameters and arrow syntax, then level up with Records and Patterns to return multiple values without creating dummy classes!

မင်္ဂလာပါ.. 👋

100 days of Flutter ရဲ့ Day 3 က ကြိုဆိုပါတယ်။ ဒီနေ့မှာတော့ dart langauge နဲ့ ပတ်သက်တာတွေကို ဆက်လက်ပြီး လေ့လာသွားပါမယ်။ ဒီနေ့ဆွေးနွေးသွားမယ့် ခေါင်းစဥ်တွေကတော့

  • What are Functions?
  • What are Records?
  • What are Patterns?

တွေပဲ ဖြစ်ပါတယ်။


What are Functions?

ဒီနေ့မှာတော့ ကျွန်တော်တို့တွေ program တွေရေးရာမှာ မပါမဖြစ် Function တွေအကြောင်းနဲ့ result တွေကို ရှင်းရှင်းလင်းလင်းနဲ့ ဘယ်လို တနေရာကနေ တနေရာကို ပို့သွားမလဲဆိုတာ ဆက်လက်ပြီး လေ့လာကြရအောင်ပါ။

ကျွန်တော်တို့ ရေးမယ့် program က ဟင်းချက်တဲ့ program ဆိုပါစို့။ သူ့မှာ step တွေအများကြီးပါမယ်။ လိုအပ်တဲ့ပစ္စည်းတွေ ဝယ်တာက အစ နောက်ဆုံး ဟင်းတခွက် ဖြစ်လာတဲ့အထိ အဆင့်ဆင့်ကို ဖြတ်လာရမှာပဲ ဖြစ်ပါတယ်။ ဒီလိုအဆင့်တဆင့်ချင်းကို Function အနေနဲ့ မြင်ကြည့်လို့ရပါတယ်။

ဒီမှာ ကြည့်မယ့်ဆို အဆင့်တိုင်းမှာ result တွေ ထွက်လာတာကို မြင်ရမှာပဲ ဖြစ်ပါတယ်။ Shopping အဆင့်မှာကို result အနေနဲ့ groceries ရလာမှာဖြစ်ပြီး ကျန်တဲ့အဆင့်တွေမှာလဲ ဆိုင်ရာဆိုင်ရာ result တွေကို ရလာမှာ ဖြစ်ပါတယ်။​ ဒီလို အဆင့်ဆင့် ကျော်ဖြတ်ပြီးရင်တော့ အရသာရှိတဲ့ ဟင်းတခွက် ထွက်လာမှာပဲ ဖြစ်ပါတယ်။

မေးစရာရှိတာက အကုန်လုံးကို step တခုထဲအနေနဲ့မလုပ်ဘဲ ဘာလို့ အဆင့်တွေ ခွဲနေတုန်းပေါ့။ အဆင့်တဆင့်ကို လူတစ်ယောက်က တာဝန်ယူပြီး လုပ်တယ်ဆိုပါစို့။ ဒီလိုမျိုး အဆင့်ခွဲလိုက်ချင်းအားဖြင့် ကျွန်တော်တို့ လိုအပ်တဲ့အချိန် အဲ့အဆင့်ကို တာဝန်ယူထားတဲ့သူကို ခေါ်လို့ရသွားပါတယ်။ ဥပမာ ကျွန်တော်တို့ အဆင်သင့် လှီးဖြတ်ပြီးသား အသားနဲ့ အသီးအရွက်တွေ ဝယ်လာတဲ့နေ့ဆို washing, preparing တို့ကို တာဝန်ယူထားတဲ့သူတွေကို ဖြတ်ဖို့မလိုဘဲ cooking step ကို တာဝန်ယူထားတဲ့သူကိုပဲ တန်းခေါ်လို့ ရသွားမှာပဲ ဖြစ်ပါတယ်။ ဒီလို တာဝန်တွေ ခွဲခြားပေးတာကို program မှာ function တွေအနေနဲ့ သတ်မှတ်ပါတယ်။

Basic Function

ဒါဆို Dart မှာ function တွေဘယ်လို ရေးလဲဆိုတော လေ့လာကြရအောင်ပါ။

void main() {
  greet();
}

void greet() {
  print("Hello!");
}

အပေါ်က code ကို run လိုက်မယ်ဆို အောက်မှာ ပြသလိုမျိုး မြင်ရမှာပဲ ဖြစ်ပါတယ်။

$ fvm dart run bin/learn_dart.dart
Hello!

ဒါကတော့ အရိုးအရှင်းဆုံး funtion ပဲ ဖြစ်ပါတယ်။ void ဆိုတာကတော့ ဘာ result မှ function ကနေပြန်လည်ရယူလို့ မလိုတဲ့အခါ သုံးပါတယ်။

Function that takes and gives

တကယ်လို့ result တခုခု ပြန်ရဖို့လိုတယ်ဆိုရင်တော့ void အစား တခြား type ကို ရေးပါတယ်။

void main() {
  print("Result: ${add(1, 3)}");
}

int add(int num1, int num2) {
  return num1 + num2;
}
$ fvm dart run bin/learn_dart.dart
Result: 4

အပေါ်မှာပြထားတဲ့ ဥပမာမှာ int type ကို return ပြန်ထားတာ ဖြစ်ပါတယ်။ ဒီနေရာမှာ အခြားသော String, double, bool အစရှိသဖြင့် တခြားသော type တွေ ဖြစ်နိုင်ပါတယ်။ နောက်ပိုင်း Object Oriented Programming ရောက်တဲ့အခါ custom type တွေကိုပါ return ပြန်ပေးလို့ရတယ်ဆိုတာ တွေ့ရမှာ ဖြစ်ပါတယ်။

ကျွန်တာ်တို့ add ဆိုတဲ့ function ကိုခေါ်လိုက်တဲ့အချိန်မှာ value ၂ခုကို ထည့်ပေးလိုက်ပါတယ်။ ဒါတွေကိုတော့ function parameter တွေလို့ခေါ်ပါတယ်။ add function definition လုပ်တဲ့အခါမှာလဲ num1 နဲ့ num2 ဆိုပြီး parameter တွေအတွက် ကြိုပြီး သတ်မှတ်ပေးထားတာကို တွေ့ရမှာပဲ ဖြစ်ပါတယ်။ ဒီတော့ add function ကို ခေါ်ချင်တိုင်း parameter ၂ခု ထည့်ပေးရမှာပဲ ဖြစ်ပါတယ်။ parameter တွေကလဲ သတ်မှတ်ထားတဲ့ type ဖြစ်ဖို့လိုပါတယ်။ type လွဲမှားနေရင်တော့ အခုလိုမျိုး error ကို မြင်နေရမှာပဲ ဖြစ်ပါတယ်။

Arrow Function

တကယ်လို့ function လုပ်တဲ့အလုပ်က အရမ်း simple ဖြစ်လွန်းတယ်ဆို အတိုချုံ့ပြီး Arrorw function အနေနဲ့လဲ ရေးလို့ရပါတယ်။

void main() {
  print("Result: ${add(1, 3)}");
}

int add(int num1, int num2) => num1 + num2;

All about Parameters

Optional Parameters

တကယ်လို့ ကျွန်တော်တို့ parameter တွေ ထည့်ပေးတဲ့အခါ အမြဲမလိုတဲ့ အခါမျိုး ရှိတယ်ဆိုပါစို့။ ဥပမာ First Name နဲ့ Last Name လိုမျိုးပေါ့။ အဲ့ဒါဆို ဘယ်လို လုပ်မလဲ ဆက်ကြည့်ရအောင်ပါ။ optional parameter အနေနဲ့ သတ်မှတ်ချင်တယ်ဆို type နဲ့တဆက်ထဲမှာ ? ထည့်ပေးရမှာပဲ ဖြစ်ပါတယ်။

void main() {
  greet("Aung Aung");
  greet("Kyaw Kyaw", null, false);
  greet("Lewis", "Hamilton", true);
}

void greet(String firstName, [String? lastName, bool single = true]) {
  print(
    "${lastName != null ? "$firstName $lastName" : firstName} is ${single ? 'single' : 'married'}",
  );
}
$ fvm dart run bin/learn_dart.dart
Aung Aung is single
Kyaw Kyaw is married
Lewis Hamilton is single

Named Parameters

တကယ်လို့ parameter value တွေကများလို့ နေရာ မှားပြီး pass လုပ်မိမှာ စိုးတဲ့အခါ named parameter ကို အသုံးပြုလို့ရပါတယ်။ ဒီမှာလဲ optional parameter သတ်မှတ်လို့ရပါတယ်။

void main() {
  profile(firstName: "Aung Aung", age: 20, height: 170);
}

void profile({
  required String firstName,
  String? lastName,
  required int age,
  required int height,
}) {
  print('Name: $firstName ${lastName ?? ''}');
  print("Age: $age years old");
  print("Height: $height cm");
}
$ fvm dart run bin/learn_dart.dart
Name: Aung Aung 
Age: 20 years old
Height: 170 cm

What are Records?

Record ဆိုတာကတော့ သက်ဆိုင်ရာ value တွေကို စုထားနိုင်အောင် လုပ်ပေးထားတဲ့ type တမျိုးပဲ ဖြစ်ပါတယ်။ ဥပမာလေးနဲ့ဆို ပိုပြီး ထင်သာမြင်သာ ရှိသွားပါလိမ့်မယ်။

Record as Variable Type

ကျွန်တော်တို့ မြို့တွေကို တည်နေရာ သတ်မှတ်ကြတဲ့အခါ latitude, longitude ဆိုပြီး အတူသတ်မှတ်ကြတာ ရှိပါတယ်။ ဒီတော့ ဒီ၂ခုက မြို့နဲ့ တွဲဖက်ပြီး ရှိနေရမယ့် value ပဲ ဖြစ်ပါတယ်။

void main() {
  final location = ('Yangon', 16.8, 96.0);
  print("City Location:");
  print("Name: ${location.$1}");
  print("Latitude: ${location.$2}");
  print("Longitude: ${location.$3}");
}
$ fvm dart run bin/learn_dart.dart
City Location:
Name: Yangon
Latitude: 16.8
Longitude: 96.0

( နဲ့ ) နဲ့ type သတ်မှတ်ထားတာကို record လို့ခေါ်ပါတယ်။ value တခုထက်ပိုပြီးတော့ return ပြန်ပေးနိုင်ပါတယ်။ သူ့ထဲက value တွေကို ခွဲပြီး ရယူချင်တဲ့အခါ xxx.$1, xxx.$2 စသဖြင့် အသုံးပြုပါတယ်။ $1 တို့ $2 တို့ဆိုတာ position တွေကို ပြောတာပဲ ဖြစ်ပါတယ်။

Record as Function Return Type

အပေါ်မှာ ပြထားခဲ့တဲ့ function တွေမှာ return type ကို void မဟုတ်ဘဲ တခြား type တခုခု သတ်မှတ်တဲ့အခါ function က အဲ့ဒီ type ကို return ပြန်ပေးတာကို တွေ့ရမှာ ဖြစ်ပါတယ်။

int add(int num1, int num2) {
  return num1 + num2;
}

တကယ်လို့ ကိုယ် return ပြန်ချင်တာက value တခုထက်ပိုတဲ့အခါ Record type ကို အသုံးပြုလို့ရပါတယ်။ ဥပမာ အနေနဲ့ဆို မြို့တွေရဲ့latitude နဲ့ longitude ကို တပြိုင်ထဲ function ကနေ ပြန်လိုချင်တယ်ဆိုပါစို့။ ဒါဆိုရင်တော့ အခုလိုမျိုး သတ်မှတ်ပေးလို့ရပါတယ်။

void main() {
  final cityName = 'Yangon';
  var result = findPosition(cityName);
  print("City: $cityName, Lat: ${result.$1} Long: ${result.$2}");
}

(double lat, double long) findPosition(String cityName) {
  if (cityName == 'Yangon') {
    return (16.8, 96.0);
  } else if (cityName == 'Mandalay') {
    return (21.9, 96.0);
  } else {
    return (0.0, 0.0);
  }
}
$ fvm dart run bin/learn_dart.dart
City: Yangon, Lat: 16.8 Long: 96.0

တကယ်လို့ ကိုယ်က named values တွေ အနေနဲ့ return ပြန်ချင်ရင်တော့ အခုလိုမျိုး ပြင်ရေးလိုက်လို့ ရပါတယ်။

void main() {
  final cityName = 'Yangon';
  var result = findPosition(cityName);
  print("City: $cityName, Lat: ${result.lat} Long: ${result.long}");
}

({double lat, double long}) findPosition(String cityName) {
  if (cityName == 'Yangon') {
    return (lat: 16.8, long: 96.0);
  } else if (cityName == 'Mandalay') {
    return (lat: 21.9, long: 96.0);
  } else {
    return (lat: 0.0, long: 0.0);
  }
}
$ fvm dart run bin/learn_dart.dart
City: Yangon, Lat: 16.8 Long: 96.0

result.lat, result.long ဆိုပြီး အသုံးပြုလို့ ရသွားမှာ ဖြစ်ပါတယ်။ ပြပေးတဲ့ result ကတော့ အတူတူပဲ ဖြစ်ပါလိမ့်မယ်။


What are Patterns?

အပေါ်မှာ ပြထားတဲ့တဲ့ record ထဲက value တွေကို access လုပ်တာတွေကို တချက်ပြန်ကြည့်ကြရအောင်ပါ။

final location = ('Yangon', 16.8, 96.0);
// Access -> .$1, .$2, .$3


(double lat, double long) findPosition(String cityName) {
  ...
}
// Access -> .$1, .$2


({double lat, double long}) findPosition(String cityName) {
  ...
}
// Access -> .lat, .long

ဒါတွေကို ပိုမိုပြီး အဆင်မြင့်တဲ့ pattern အနေနဲ့ ပြင်ဆင်ပြီး ရေးသွားလို့ရပါတယ်။ pattern တွေကို value တွေ destructuring လုပ်တဲ့အခါ သုံးပါတယ်။ ကိုယ်က ဘယ်လို ပုံစံတွေနဲ့ ပြန်လာမလဲ သိတဲ့ အခါ value တွေကို တခါထဲ ခွဲထုတ်ပြီး နာမည်တွေနဲ့ တခါထဲ ဆွဲထုတ် ပေးသွားတာမျိုးပဲ ဖြစ်ပါတယ်။

void main() {
  final (name, lat, long) = ('Yangon', 16.8, 96.0);
  print("City Location:");
  print("Name: $name");
  print("Latitude: $lat");
  print("Longitude: $long");
}
$ fvm dart run bin/learn_dart.dart
City Location:
Name: Yangon
Latitude: 16.8
Longitude: 96.0

ဒါပေမယ့် return ပြန်ထဲက named values တွေအနေနဲ့ return ပြန်ထားတယ်ဆိုရင်တော့ : ကို destructure လုပ်ထားတဲ့ name တွေရှေ့မှာ ထည့်ပေးဖို့ လိုပါတယ်။

void main() {
  final (name, :lat, :long) = ('Yangon', lat: 16.8, long: 96.0);
  print("City Location:");
  print("Name: $name");
  print("Latitude: $lat");
  print("Longitude: $long");
}
$ fvm dart run bin/learn_dart.dart
City Location:
Name: Yangon
Latitude: 16.8
Longitude: 96.0

Pattern match လုပ်တာက Record type တခုထဲကို support လုပ်တာ မဟုတ်ပါဘူး။ JavaScript အသုံးပြုဖူးတဲ့သူတွေကို list တွေကို ... နဲ့ ဖြန့်ချတာကို သိကြမယ်ထင်ပါတယ်။ Dart မှာလဲ အဲ့လိုမျိုး လုပ်လို့ရပါတယ်။

void main() {
  var [first, second, ...rest, last] = [1, 2, 3, 4, 5];
  print('First: $first, Second: $second, Rest: $rest, Last: $last');
}
$ fvm dart run bin/learn_dart.dart
First: 1, Second: 2, Rest: [3, 4], Last: 5

Record/Pattern Matching System

void main() {
  processOrder(item: Item.mohinga, quantity: 2, distance: 3.5);
  
  print('\n---\n');
  
  processOrder(item: Item.shanNoodle, quantity: 1, distance: 12);
  
  print('\n---\n');
  
  processOrder(item: Item.teaLeafSalad, quantity: 5, distance: 4);
}

enum Item {
  mohinga('Mohinga', 3000),
  shanNoodle('Shan Noodle', 2000),
  teaLeafSalad('Tea Leaf Salad', 1500);

  final String label;
  final int price;

  const Item(this.label, this.price);
}

enum OrderStatus { accepted, rejected }

void processOrder({
  required Item item,
  required int quantity,
  required double distance,
}) {
  print("Processing order for $quantity x ${item.label}");

  var (status, :total, :deliveryFee, :message) = calculateOrder(
    price: item.price,
    quantity: quantity,
    distance: distance,
  );

  print("Status: ${status.name}");
  print("Message: $message");

  if (status == OrderStatus.accepted) {
    print("Subtotal: ${total - deliveryFee} Kyats");
    print("Delivery Fee: $deliveryFee Kyats");
    print("Total: $total Kyats");
  }
}

(OrderStatus status, {int total, int deliveryFee, String message})
calculateOrder({
  required int price,
  required int quantity,
  required double distance,
}) {
  if (distance > 10) {
    return (
      OrderStatus.rejected,
      total: 0,
      deliveryFee: 0,
      message: "Too Far Away!",
    );
  }

  int deliveryFee = (distance * 500).round();
  int subTotal = price * quantity;

  return (
    OrderStatus.accepted,
    total: subTotal + deliveryFee,
    deliveryFee: deliveryFee,
    message: 'Order confirmed!',
  );
}
$ fvm dart run bin/learn_dart.dart
Processing order for 2 x Mohinga
Status: accepted
Message: Order confirmed!
Subtotal: 6000 Kyats
Delivery Fee: 1750 Kyats
Total: 7750 Kyats

---

Processing order for 1 x Shan Noodle
Status: rejected
Message: Too Far Away!

---

Processing order for 5 x Tea Leaf Salad
Status: accepted
Message: Order confirmed!
Subtotal: 7500 Kyats
Delivery Fee: 2000 Kyats
Total: 9500 Kyats

ဒီနေ့ Day 3 အတွက်ကတော့ ဒီလောက်ပဲ ဖြစ်ပါတယ်။ အဆုံးထိ ဖတ်ပေးတဲ့အတွက် အများကြီး ကျေးဇူးတင်ပါတယ်။ နားမလည်တာတွေ အဆင်မပြေတာတွေ ရှိခဲ့ရင်လဲ အောက်မှာပေးထားတဲ့ discord server ထဲမှာ လာရောက်ဆွေးနွေးနိုင်ပါတယ်။ နောက်နေ့တွေမှာလဲ ဆက်လက်ပြီး sharing လုပ်သွားပေးသွားမှာ ဖြစ်တဲ့အတွက် subscribe လုပ်ထားဖို့ ဖိတ်ခေါ်ပါတယ်။