Day 10: Functional Dart (Map/Fold/Where/Any/Every)
Transform raw API data into clean, readable pipelines by ditching for-loops and mastering functional programming with map, fold, where, any, and every operations.
မင်္ဂလာပါ.. 👋
100 days of Flutter ရဲ့ Day 10 က ကြိုဆိုပါတယ်။ ဒီနေ့မှာတော့ dart langauge နဲ့ ပတ်သက်တာတွေကို ဆက်လက်ပြီး လေ့လာသွားပါမယ်။ ဒီနေ့ဆွေးနွေးသွားမယ့် ခေါင်းစဥ်တွေကတော့ Functional Dart ပဲ ဖြစ်ပါတယ်။
ဒီနေ့မှာတော့ ရှေ့ရက်တွေမှာ ဆွေးနွေးခဲ့တဲ့ Object Oriented Programming (OOP) ကနေ နည်းနည်းကွဲထွက်တဲ့ Functional Programming အကြောင်းဆွေးနွေးသွားပါမယ်။
အရင်ဆုံး အခုပေးထားတဲ့ code မှာ ဘာတွေ လွဲမှားနေတယ်လို့ ထင်ပါသလဲ?
List<Order> processOrders(List<Order> orders) {
List<Order> validOrders = [];
for (int i = 0; i < orders.length; i++) {
if (orders[i].amount > 0) {
orders[i].status = 'processed';
validOrders.add(orders[i]);
}
}
return validOrders;
}ဘာမှ မလွဲဘူးလို့ ထင်ကောင်း ထင်ပါလိမ့်မယ်။ code က valid ဖြစ်ပြီး အလုပ်တာလဲ မှန်သယောင်ရှိလို့ပါ။ ဒါပေမယ့် သူ့မှာပြဿနာဖြစ်စေနိုင်တဲ့ အချက်တချို့ ရှိနေပါတယ်။
ပထမဆုံး တခုကတော့ mutation side effect ပါ။ orders[i].status = 'processed'; ဆိုတဲ့အသုံးက လက်ရှိ ရှိနေတဲ့ orders list ထဲက i index မှာရှိတဲ့ Order ကို သွားပြီးတော့ ပြင်နေတာ ဖြစ်ပါတယ်။ ဆိုလိုတာက processOrder ကို ခေါ်တိုင်းမှာ သူ့ထဲမှာမဟုတ်တဲ့ အပြင်က value တွေကို သွားပြီးတော့ ပြင်ဆင်နေတာတွေ ဖြစ်နေတဲ့ သဘော ဖြစ်ပါတယ်။
class Order {
final String id;
String status; // Mutable!
final double amount;
Order(this.id, this.status, this.amount);
@override
String toString() {
return "ORDER: $id, $status, $amount";
}
}
List<Order> processOrders(List<Order> orders) {
List<Order> validOrders = [];
for (int i = 0; i < orders.length; i++) {
if (orders[i].amount > 0) {
orders[i].status = 'processed';
validOrders.add(orders[i]);
}
}
return validOrders;
}
void main() {
final orders = [
Order('001', 'pending', 100.0),
Order('002', 'pending', 200.0),
];
print(">>>> Before");
print(orders);
final validOrders = processOrders(orders);
print(">>>> After");
print("ORIGINAL ORDERS:");
print(orders);
print("UPDATED ORDERS:");
print(validOrders);
}
// >>>> Before
// [ORDER: 001, pending, 100.0, ORDER: 002, pending, 200.0]
// >>>> After
// ORIGINAL ORDERS:
// [ORDER: 001, processed, 100.0, ORDER: 002, processed, 200.0]
// UPDATED ORDERS:
// [ORDER: 001, processed, 100.0, ORDER: 002, processed, 200.0]ဒီမှာကြည့်မယ်ဆို processOrders function ကိုခေါ်ပြီးသွားတဲ့အချိန်မှာ နဂိုရှိပြီးသား orders ထဲက status တွေအကုန် processed ဖြစ်သွားတာကို တွေ့ရမှာပါ။
int calculateDiscount(Order order) {
order.status = 'discounted';
return order.amount * 0.1;
}ဒီလိုမျိုး ထပ်ရှိခဲ့ရင်လဲ calculateDiscount လို့ခေါ်တိုင်း order ရဲ့ status ကို သွားပြောင်းနေမှာပါ။ ဒါကလဲ mutation side effect ပဲ ဖြစ်ပါတယ်။ ရှိပြီးသား နဂို value ကို သွားပြီးတော့ ပြောင်းတာတွေ နောက်ကွယ်မှာ လုပ်နေခြင်းက error တွေကို ဖိတ်ခေါ်နေသလိုပါပဲ။
နောက်ထပ်တခုကတော့ processOrders လိုမျိုးထဲမှာ index ကို အဓိက အသုံးပြုပြီး loop တွေ ရေးတာဖြစ်တဲ့အတွက် index error လဲဖြစ်နိုင်ပါတယ်။ < သုံးရမယ့်နေရာမှာ မတော်တဆ <= ဆိုပြီး သုံးမိရင် error တက်သွားပါလိမ့်မယ်။
final items = ['A', 'B', 'C']; // Length: 3, Indexes: 0, 1, 2
// ❌ WRONG: i <= items.length
for (int i = 0; i <= items.length; i++) {
print(items[i]);
// i=0: 'A' ✓
// i=1: 'B' ✓
// i=2: 'C' ✓
// i=3: CRASH! RangeError: Index out of range: 3
}
// ✅ CORRECT: i < items.length
for (int i = 0; i < items.length; i++) {
print(items[i]); // Works perfectly
}တကယ်လို့ list ထဲမှာရှိတဲ့ value တွေကို mutate လုပ်ရုံတင်မဟုတ်ဘဲ အသွင်းအထုတ်လိုမျိုးတွေ လုပ်တဲ့အခါမှာတော့ loop တခါပတ်တိုင်း length အပြောင်းအလဲတွေဖြစ်နေပြီး error တွေ တက်နိုင်ပါသေးတယ်။ list အပြောင်းအလဲဖြစ်သွားပေမယ့် index က တခုပြီးတခု တိုးသွား/လျော့သွားမှာ ဖြစ်တဲ့အတွက် value တွေ နေရာပြောင်းသွားတဲ့အခါ process လုပ်ဖို့ ကျော်သွားတာ ဖြစ်နိုင်ပါတယ်။ အောက်မှာ ပေးထားတဲ့ code မှာဆိုရင် even number တွေကို ဖယ်ထုတ်ပေးရမှာ ဖြစ်ပေမယ့် နောက်ဆုံး result ထွက်လာတဲ့အခါမှာတော့ even number ကျန်နေတာကို တွေ့ရမှာပါ။
final numbers = [1, 2, 6, 4, 5];
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 == 0) {
numbers.removeAt(i);
}
}
print(numbers);
// Output:
// [1, 6, 5]ဒါက for loop လိုမျိုးတွေ မသုံးရဘူးလို့ ဆိုလိုတာမဟုတ်ပါဘူး။ loop ထဲမှာ ဘာလုပ်လဲဆိုတာက အရေးကြီးတာပါ။ ဒီလိုမျိုး ပြဿနာတွေ အဖြစ်နည်းသွားအောင် ရေးလို့ရတဲ့ နည်းတွေကို အောက်မှာ ဆက်ပြောပြသွားမှာပါ။
Imperative Programming
ဒါကတော့ အပေါ်မှာပြခဲ့တဲ့ အတိုင်း ရေးတာကို ခေါ်တဲ့ အခေါ်အဝေါ်ပဲ ဖြစ်ပါတယ်။ Step by step instruction တွေနဲ့ ဘယ်အချိန်မှာ ဘာပြောင်း၊ ဘယ်လိုပြောင်းဆိုပြီး ရေးတာမျိုးကို ခေါ်တာပါ။
final numbers = [1, 2, 3, 4, 5];
int sum = 0;
for (int i = 0; i < numbers.length; i++) {
sum = sum + numbers[i];
}
print(sum);ဒီမှာဆို sum ကြေငြာတာတွေ, loop ပတ်တာတွေ, loop ထဲမှာ sum ကို numbers[i] နဲ့ ပေါင်းတာတွေကို အဆင့်ဆင့် ရေးပေးထားပါတယ်။
Declarative Programming
ဒါကတော့ လိုချင်တဲ့ ကိုယ်ဘာလိုချင်လဲဆိုတာကိုပဲ ပြောလိုက်တာမျိုး ဖြစ်ပါတယ်။ ဘယ်လို အဆင့်အဆင့် လုပ်သွားဆိုပြီးတော့ မပါဝင်ပါဘူး။
final numbers = [1, 2, 3, 4, 5];
final sum = numbers.reduce((a, b) => a + b);
print(sum);ဒီမှာတော့ အပေါ်ကလိုမျိုး အဆင့်ဆင့်မဟုတ်တော့ဘဲ .reduce(...) ဆိုတဲ့ function ကိုရေးပြီး a နဲ့ b ကို ပေါင်းပေးလို့ပဲ ရေးပေးလိုက်ပါတယ်။ loop ပတ်တာတွေ variable assign လုပ်တာတွေ, update လုပ်တာတွေ မပါနေတော့ပါဘူး။ ဒါကတော့ declarative programming ထဲမှာပါဝင်တဲ့ functional programming ပဲ ဖြစ်ပါတယ်။ Functional programming ဖြစ်ဖို့ဆို တခြား side effect တွေလဲ ပါဝင်နေလို့ မရပါဘူး။ pure function ဖြစ်နေဖို့ လိုအပ်ပါတယ်။
Imperative vs Declarative Example
int sum = 0;
for (int i = 0; i < numbers.length; i++) {
sum = sum + numbers[i];
}
int product = 1;
for (int i = 0; i < numbers.length; i++) {
product = product * numbers[i];
}final sum = numbers.reduce((a, b) => a + b);
final product = numbers.reduce((a, b) => a * b);
Higher Order Function
တကယ်တော့ .reduce လိုမျိိုး အသုံးအနှုန်းကို higher order function လို့ခေါ်ရင် ပိုပြီးတော့ မှန်ပါတယ်။ ဘာကြောင့်လဲဆိုတော့ သူ့ထဲကို ထည့်ပေးလိုက်တဲ့ parameter ဖြစ်တဲ့ (a, b) => a + b ကလဲ function တခု ဖြစ်နေလို့ပါ။ ဒါ့အပြင် value တခုအနေနဲ့မဟုတ်ဘဲ function ကို return ပြန်တာမျိုးဆိုလဲ higher order function လို့ ခေါ်ပါသေးတယ်။
final add = (int a, int b) => a + b;
final sum = numbers.reduce(add); Core Methods of Functional Programming
ဆက်ပြီးတော့ dart ရဲ့ functional programming ထဲမှာ အသုံးအများဆုံး methods တွေကို ဆက်ကြည့်ပါမယ်။
Filter (.where)
ဒါကိုတော့ list ထဲမှာ ပါတဲ့ value တွေကို စစ်ထုတ်တာမျိုး လုပ်ချင်တဲ့အခါ အသုံးပြုပါတယ်။
final prices = [100.0, 550.0, 200.0, 45.0, 700.0];
// Keep only affordable items (≤ $500)
final affordable = prices.where((price) => price <= 500);
print(affordable); // [100.0, 200.0, 45.0]App တွေ ရေးတဲ့အခါ search result တွေ, product filter လုပ်တာတွေ စတာတွေမှာ အသုံးပြုလို့ရပါတယ်။
Transformer (.map)
ဒါကိုတော့ list ထဲမှာ ပါတဲ့ value တိုင်းကို ပြောင်းလဲတာ လုပ်ချင်တဲ့အခါ အသုံးပြုပါတယ်။
final prices = [100.0, 200.0];
final labels = prices.map((p) => 'Price: \$$p');
// Result: ['Price: $100.0', 'Price: $200.0']Aggregator (.fold)
ဒါကိုတော့ list ထဲက value တွေကို တခုထဲအဖြစ် လိုချင်တဲ့အခါ အသုံးပြုပါတယ်။
final prices = [100.0, 200.0, 300.0];
// Sum everything starting from 0.0
final total = prices.fold(0.0, (previous, current) => previous + current);
print(total); // 600.0.fold ကိုတော့ .reduce နဲ့ ညီအကိုလို့ ပြောလို့ရပါတယ်။ အလုပ် လုပ်ပုံလုပ်နည်းကတော့ ဆင်တူပါတယ်။ .reduce မှာတော့ list က empty ဖြစ်နေတာတို့၊ type မတူတာတို့မှာ သုံးလို့မရပါဘူး။ .fold ကိုတော့ နေရာတိုင်းမှာ သုံးလို့ရပြီး initial value သတ်မှတ်လို့ရတာ ထပ်ဆောင်း ပါဝင်ပါတယ်။ ဒီတော့ type တူပြီး empty ဖြစ်မနေဘူးဆို .reduce ကို သုံးပြီး တခြား အချိန်တွေမှာတော့ .fold ကို သုံးလို့ရပါတယ်။
Existence Checker (.any)
ဒါကိုတော့ list က value တခုခုက ပေးထားတဲ့ function ထဲမှာ boolean check လုပ်တာ pass ဖြစ်တာ ပါလား၊ မပါလား စစ်တာမျိုးဖြစ်ပါတယ်။
final numbers = [1, 3, 5, 7, 8, 9];
// Does ANY number match "is even"?
final hasEven = numbers.any((n) => n % 2 == 0);
print(hasEven); // true (because 8 is even)
// Does ANY number match "is greater than 10"?
final hasLarge = numbers.any((n) => n > 10);
print(hasLarge); // false (no number > 10)Universal Checker (.every)
ဒါကိုတော့ list ထဲက value တိုင်း ပေးထားတဲ့ function ထဲမှာ boolean check လုပ်တာ pass ဖြစ် မဖြစ် စစ်တာမျိုးမှာ သုံးပါတယ်။
final numbers = [2, 4, 6, 8];
// Are ALL numbers even?
final allEven = numbers.every((n) => n % 2 == 0);
print(allEven); // true (all are even)
// Are ALL numbers greater than 5?
final allLarge = numbers.every((n) => n > 5);
print(allLarge); // false (2 and 4 are not > 5)Example
void main() {
final rawPrices = [100.0, 550.0, 200.0, 45.0, 700.0, 300.0];
// ✓ EXISTENCE CHECKER: Is there ANY affordable item?
final hasAffordable = rawPrices.any((price) => price <= 500);
print('Has affordable items? $hasAffordable'); // true
// ✓ UNIVERSAL CHECKER: Are ALL items expensive?
final allExpensive = rawPrices.every((price) => price > 100);
print('All items expensive? $allExpensive'); // false (45.0 is not)
// Only proceed if we have affordable items
if (!hasAffordable) {
print('Sorry, no affordable items available!');
return;
}
// Step 1: FILTER - Keep only affordable items
final affordableItems = rawPrices.where((price) => price <= 500);
print('After filtering: $affordableItems');
// Result: [100.0, 200.0, 45.0, 300.0]
// Step 2: TRANSFORMER - Apply 10% discount
final discountedPrices = affordableItems.map((price) => price * 0.9);
print('After discount: $discountedPrices');
// Result: [90.0, 180.0, 40.5, 270.0]
// Step 3: AGGREGATOR - Calculate total
final total = discountedPrices.fold(0.0, (prev, curr) => prev + curr);
print('Final Total: \$${total.toStringAsFixed(2)}');
// Result: $580.50
}အပေါ်မှာပေးထားတဲ့ code မှာဆိုရင် တဆင့်ခြင်းဆီ for loop လိုမျိုးတွေ မသုံးဘဲနဲ့ list တွေကို loop လုပ်သွားတာ တွေ့ရမှာပါ။ list ကို loop လုပ်ရုံတင်မကဘဲ list ထဲမှာပါတဲ့ value တွေပေါ် အခြေခံပြီးတော့ နောက်ထပ် list အသစ်တွေကိုပါ လုပ်သွားတာကို တွေ့ရမှာပဲ ဖြစ်ပါတယ်။
Method Chaining
အပေါ်မှာ ပြခဲ့တဲ့ code ကို အခုထက် တိုအောင် ဆက်ပြီး ရေးလို့ရပါသေးတယ်။ နောက်ဆုံး လိုချင်တဲ့ result က total price ဆိုပါစို့။ အဲ့ဒါဆို အခုလိုမျိုး ရေးလိုက်ခြင်းအားဖြင့် functional programming ကို အသုံးပြုပြီးတော့ တိုတိုနဲ့ တိကျပြီး နားလည်လွယ်တဲ့ code ကို ရရှိမှာပဲ ဖြစ်ပါတယ်။
void main() {
final rawPrices = [100.0, 550.0, 200.0, 45.0, 700.0, 300.0];
// ✓ EXISTENCE CHECK before processing
if (!rawPrices.any((price) => price <= 500)) {
print('Sorry, no affordable items available!');
return;
}
// ✓ Now do the full chain
final total = rawPrices
.where((price) => price <= 500) // FILTER
.map((price) => price * 0.9) // TRANSFORMER
.fold(0.0, (sum, p) => sum + p); // AGGREGATOR
print('Total: \$${total.toStringAsFixed(2)}');
}| Feature | For Loop (Imperative) | Functional (Declarative) |
|---|---|---|
| Readability | High boilerplate | Clean & descriptive |
| Error Risk | High (index bugs) | Almost zero |
| Modification | Hard to change | Easy to add steps |
| Testing | Must test indices | Test pure functions |
| Modern | 2020 vibes | 2025+ standard |
ဒါဆိုရင်တော့ imperative နည်းနဲ့ for loop တွေ သုံရမယ့်နေရာတချို့မှာ functional နဲ့ သုံးခြင်းအားဖြင့် ဘယ်လိုအကျိုးတွေ ရှိသွားမလဲဆို နားလည်လောက်ပြီ ထင်ပါတယ်။ အခြားသော functional methods တွေကိုလဲ လေ့လာပြီး စမ်းကြည့်ဖို့ အကြံပြုပါတယ်။
ဒီနေ့ Day 10 အတွက်ကတော့ ဒီလောက်ပဲ ဖြစ်ပါတယ်။ အဆုံးထိ ဖတ်ပေးတဲ့အတွက် အများကြီး ကျေးဇူးတင်ပါတယ်။ နားမလည်တာတွေ အဆင်မပြေတာတွေ ရှိခဲ့ရင်လဲ အောက်မှာပေးထားတဲ့ discord server ထဲမှာ လာရောက်ဆွေးနွေးနိုင်ပါတယ်။ နောက်နေ့တွေမှာလဲ ဆက်လက်ပြီး sharing လုပ်သွားပေးသွားမှာ ဖြစ်တဲ့အတွက် subscribe လုပ်ထားဖို့ ဖိတ်ခေါ်ပါတယ်။
- Youtube: https://www.youtube.com/@arkarmintun
- Newsletter: https://arkar.dev/
- Discord: https://discord.gg/3xUJ6k6dkH