# ปูพื้น Dart ให้พอเขียน Flutter ตอนที่ 2

#Dev#Dart#Flutter

บทความตอนนี้จะกล่าวถึงฟังก์ชันและ Operator ในภาษา Dart ซึ่งผมจะเอาข้อมูลโดยส่วนใหญ่มาจาก Dart Tour (opens new window) แล้วตัดส่วนเฉพาะเท่าที่จำเป็นมาครับ
คลิกที่นี่สำหรับ ตอนที่ 1

Tips

สามารถใช้ Dart Pad (opens new window) ในการทดลองเขียนได้ เพื่อเพิ่มความเข้าใจ

Header

# Functions

Dart เป็นภาษาเป็นเป็น OOP (Object-Oriented Programming) อย่างแท้จริง เพราะงั้นฟังก์ชันเองก็เป็น Object อันหนึ่ง และยังมีชนิดเป็นฟังก์ชัน
นั่นหมายความว่าเราสามารถกำหนดค่าฟังก์ชันใส่ตัวแปร หรือส่งผ่านเข้าไปในฟังก์ชันอื่นได้
โดยมีบาง Instance (เป็นสิ่งที่สร้างมาจาก Class) ที่สามารถเรียกเหมือนฟังก์ชันได้อีกด้วย

ตัวอย่างการประกาศฟังก์ชัน

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

bool เป็นส่วนที่บอกว่าฟังก์ชันนี้คืนค่าออกมาเป็นชนิดอะไร ซึ่งในที่นี้เป็นค่า Boolean
isNoble คือ ชื่อของฟังก์ชัน
int atomicNumber คือ Parameter ที่ฟังก์ชันนี้รับ
return เป็นส่วนที่บอกว่าฟังก์ชันจะคืนค่าอะไรเมื่อเรียกเสร็จ

Tips

เราสามารถไม่ใส่ bool หรือชนิดของค่าที่คืนได้
แต่การใส่ไว้ทำให้เราดูแล้วรู้ได้ทันทีว่าฟังก์ชันนี้จะคืนค่าอะไร

และ ถ้าภายในฟังก์ชันของเรามีการกระทำเพียงคำสั่งเดียว เราสามารถใช้รูปแบบที่สั้นกว่าได้ เช่น

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

โดย => คำสั่ง นั้นเป็นรูปแบบย่อของ { return คำสั่ง; }
ในบางครั้งเราจำเรียกรูปแบบ => ว่า Arrow Syntax

ฟังก์ชันจะรับ Parameter ได้ 2 แบบ คือ required (ต้องมี) หรือ optional (มีหรือไม่มีก็ได้) ซึ่ง required parameter นั้นจะขึ้นก่อนแล้วจึงตามด้วย optional parameter

# Optional Parameters

มีสองแบบให้เลือก named (กำหนดผ่านชื่อ) หรือ position (กำหนดผ่านตำแหน่ง)

# Named parameters

เมื่อเราเรียกใช้ฟังก์ชันเราสามารถส่ง Parameter ผ่านชื่อได้โดย ชื่อของParameter: ค่าที่จะส่ง เช่น

enableFlags(bold: true, hidden: false);

ซึ่งตอนที่เราประกาศฟังก์ชันนั้นเราจะต้องใส่ {bold, hidden, …} เพื่อกำหนดชื่อของ Parameter ที่เราจะรับ

void enableFlags({bool bold, bool hidden}) {...}

ถึงแม้ว่า Named Parameters จะเป็นแบบไม่บังคับก็ตามแต่ว่าเราสามารถใส่ @required เพื่อบังคับให้ใส่ได้

import 'package:meta/meta.dart'; // ถ้าจะใช้ @required ต้อง import ตัวนี้ด้วย
void enableFlags({bool bold,  bool hidden}) {...}

# Positional parameters

ถ้าต้องการกำหนด Positional parameters เราจะกำหนดโดยการใส่ Parameter ด้านใน [] เช่น

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

ตัวอย่างการเรียกฟังก์ชัน

say('Bob', 'Howdy') // เมื่อไม่กำหนด Parameter ตัวที่ 3 ค่าของ device จะเป็น null
// หรือ
say('Bob', 'Howdy', 'smoke signal')

# Default parameter values

เราสามารถใช้ = ในการกำหนดค่าเริ่มต้นได้หากผู้เรียกใช้ฟังก์ชันไม่ได้กำหนด Parameter มา

void enableFlags({bool bold = false, bool hidden = false}) {...}

enableFlags(bold: true);  // bold จะเก็บค่า true ส่วน hidden จะเก็บค่า false

จากตัวอย่างข้างบนใช้ Default parameter กับแบบที่เป็น Named ซึ่งเราสามารถใช้กับ Positional ได้เหมือนกัน

String say(String from, String msg, [String device = 'carrier pigeon', String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

say('Bob', 'Howdy') // device ถ้าไม่กำหนดค่าให้จะเป็น 'carrier pigeon' และ mood จะเป็น null

และถ้าอยากใส่ Default parameter List หรือ Map ก็ทำได้ แต่จะต้องใส่ const นำหน้า เช่น

void doStuff(
  {List<int> list = const [1, 2, 3],
  Map<String, String> gifts = const {
      'first': 'paper',
      'second': 'cotton',
      'third': 'leather'
}}) {
  print('list:  $list');
  print('gifts: $gifts');
}

# ฟังก์ชัน main()

ทุกๆแอปหรือโปรแกรมจะต้องมีฟังก์ชัน main() ซึ่งเป็นจุดเริ่มต้นของทุกๆอย่าง โดยฟังก์ชัน main() นั้นจะไม่คืนค่าอะไร และสามารถมี Optional Parameters ที่เป็น List<String> ได้หนึ่งตัว

void main() {
  print('Hello, World!');
}
// เรียกผ่าน Command line เช่น dart args.dart 1 test
void main(List<String> arguments) {
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');
}

# การส่งผ่านฟังก์ชัน

เพราะว่าฟังก์ชันก็เป็น Object อันหนึ่งเราจึงสามารถส่งผ่านอีกฟังก์ชันได้

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// ส่งฟังก์ชัน printElement ไปยังอีกฟังก์ชันหนึ่ง
list.forEach(printElement);

หรือจะกำหนดใส่ตัวแปรก็ยังได้

var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');

# Anonymous functions หรือฟังก์ชันนิรนาม

ฟังก์ชันโดยส่วนใหญ่จะมีชื่อทั้งนั้น เช่น main(), printElement() ถึงอย่างนั้นคุณก็สามารถสร้างฟังก์ชันที่ไม่มีชื่อได้เหมือนกัน
ซึ่งหน้าตาของฟังก์ชันนิรนามก็เหมือนกันฟังก์ชันที่มีชื่อ ต่างกันแค่ไม่มีชื่อให้เรียก โดยปกติแล้วเราจะกำหนดมันใส่ตัวแปร หรือส่งไปเป็น Parameter ของฟังก์ชันอื่น

([[Type] param1[, …]]) {
codeBlock;
};

ตัวอย่างด้านล่างจะเป็นการใช้ฟังก์ชันนิรนามส่งเข้าไปเป็น Parameter ของ .forEach โดยฟังก์ชันนี้รับ Parameter 1 ตัวชื่อ item
ฟังก์ชันนิรนามนี้จะถูกเรียกโดยส่งค่าสมาชิกที่อยู่ใน List ไปเป็นตัวแปรชื่อ item

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

ถ้าฟังก์ชันมีแค่การกระทำเดียวเหมือนด้านบนนี้ สามารถใช้ => ให้เขียนสั้นลงได้อีก

list.forEach((item) => print('${list.indexOf(item)}: $item'));

# Lexical scope หรือขอบเขตการทำงาน

ฟังก์ชันด้านในสามารถเข้าถึงตัวแปรที่อยู่ด้านนอกได้

bool topLevel = true;

void main() {
  var insideMain = true;

  void myFunction() {
    var insideFunction = true;

    void nestedFunction() {
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    }
  }
}

# Return values

ฟังก์ชันทุกฟังก์ชันจะมีการคืนค่าทั้งหมด ถ้าไม่มีการกำหนด return ในฟังก์ชัน ฟังก์ชันจะคืนค่า null ออกมา

# Operators

มีมากมายหลายหลากโดยลำดับการทำงานจะเริ่มจากแถวที่อยู่สูงกว่า ถ้าลำดับเท่ากันจะเริ่มจากซ้ายไปขวา

Description Operator
unary postfix expr++ expr-- () [] . ?.
unary prefix -expr !expr ~expr ++expr --expr
multiplicative * / % ~/
additive + -
shift << >> >>>
bitwise AND &
bitwise XOR ^
bitwise OR |
relational and type test >= > <= < as is is!
equality == !=
logical AND &&
logical OR ||
if null ??
conditional expr1 ? expr2 : expr3
cascade ..
assignment = *= /= += -= &= ^= etc.
// ใส่วงเล็บเพื่อให้อ่านง่ายขึ้น
if ((n % i == 0) && (d % i == 0)) ...

// อ่านยากแต่ให้ผลที่เหมือนกัน
if (n % i == 0 && d % i == 0) ...

# Arithmetic operators

เป็น Operator ที่ใช้ในการคำนวณ

Operator ความหมาย
+ บวก
- ลบ
-expr เปลี่ยนค่าเป็นจำนวนลบ
* คูณ
/ หาร
~/ หาร (แต่ผลเป็น int เสมอ)
% หารเอาเศษ

ตัวอย่าง เช่น

assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // ผลลัพธ์เป็นชนิด double
assert(5 ~/ 2 == 2); // ผลลัพธ์เป็นชนิด int
assert(5 % 2 == 1); // ได้เศษจากการหาร

และ Dart ก็รองรับการใช้งาน ++ และ -- ด้วย ยกตัวอย่างเช่น

var a, b;

a = 0;
b = ++a; // บวกค่า a เพิ่มอีก 1 ก่อนที่จะกำหนดค่าให้กับ b
assert(a == b); // 1 == 1

a = 0;
b = a++; // กำหนดค่าของ a ให้กับ b ก่อนที่จะบวกค่าของ a เพิ่มอีก 1
assert(a != b); // 1 != 0

a = 0;
b = --a; // ลบค่า a ลงอีก 1 ก่อนที่จะกำหนดค่าให้กับ b
assert(a == b); // -1 == -1

a = 0;
b = a--; // กำหนดค่าของ a ให้กับ b ก่อนที่จะลบค่าของ a ลงอีก 1
assert(a != b); // -1 != 0

# Equality and relational operators

เป็น Operator ที่ใช้ในการเปรียบค่าระหว่างสองค่า โดยผลลัพธ์จะเป็น true หรือ false

Operator ความหมาย
== เท่ากัน
!= ไม่เท่ากัน
> มากกว่า
< น้อยกว่า
>= มากกว่าหรือเท่ากับ
<= น้อยกว่าหรือเท่ากับ

ถ้าหากเราต้องการตรวจสอบว่า object หรือตัวแปรมีค่าเดียวกันหรือไม่ เราจะใช้ == ในการตรวจสอบ (ในกรณีที่เราอยากรู้ว่า object เป็นตัวเดียวกันแบบเป้ะๆเลยหรือไม่จะใช้ฟังก์ชัน identical() แทน)

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(2 <= 3);

# Type test operators

เป็น Operator ที่ใช้ในการตรวจสอบชนิดของตัวแปรหรือข้อมูล

Operator ความหมาย
as แปลงชนิดของข้อมูล
is เช็คว่าเป็นชนิดข้อมูลนั้นหรือไม่
is! เช็คว่าไม่ใช่ชนิดข้อมูลนั้น
if (emp is Person) {
  // Type check
  emp.firstName = 'Bob';
}

(emp as Person).firstName = 'Bob';

จากตัวอย่างด้านบน อาจจะคล้ายๆกันแต่ไม่เหมือนกัน เราใช้ is ในการทดสอบก่อนแล้วจึงใช้งานตัวแปร และใช้ as ในการแปลงค่า emp ให้เป็นชนิด Person ก่อนแล้วจึงกำหนดค่า

คำเตือน

การใช้ as ตามด้านบนอาจจะทำให้เกิดปัญหาได้ถ้า emp ไม่สามารถแปลงเป็น Person ได้

# Assignment operators

เป็น Operator ที่ใช้ในการกำหนดค่าให้กับตัวแปร

a = 5;
b ??= 10; // ถ้า b เป็น null จะกำหนดค่า 10 ให้กับ b

เราสามารถเขียน Operator อื่นๆผสมกับ = ได้ เช่น

= -= /= %= >>= ^=
+= *= ~/= <<= &= |=

หลักการก็คือ
a op= b เหมือนกันกับ a = a op b เช่น
a += b เหมือนกันกับ a = a + b

var a = 2; // กำหนดค่าโดยใช้ =
a *= 3; // คูณและกำหนดค่าเหมือนกับเขียน a = a * 3
assert(a == 6);

# Logical operators

เป็น Operator ที่ใช้ในการกลับค่า หรือผสมค่า Boolean

Operator ความหมาย
!expr กลับค่าระหว่าง true หรือ false
|| เหมือน OR ในตรรกศาสตร์
&& เหมือน AND ในตรรกศาสตร์
if (!done && (col == 0 || col == 3)) {
  // ทำบางสิ่งบางอย่าง
}

# Conditional expressions

เป็น Operator ที่สามารถใช้แทน if-else ได้ในบางกรณี

เงื่อนไข ? ประโยค1 : ประโยค2 ถ้าเงื่อนไขเป็นจริงจะทำ ประโยค1 ไม่เช่นนั้นจะทำ ประโยค2 พร้อมทั้งคืนค่าของประโยคนั้นๆด้วย

ตัวแปร1 ?? ตัวแปร2 ถ้า ตัวแปร1 ไม่ใช่ null จะคืนค่าของ ตัวแปร1 ไม่เช่นนั้นจะคืนค่าของ ตัวแปร2

ถ้าต้องการกำหนดค่า โดยขึ้นอยู่กับเงื่อนไขบางอย่างเราจะใช้ ?:

var visibility = isPublic ? 'public' : 'private';

ถ้าต้องการตรวจสอบว่าตัวแปรเป็น null แล้วไปใช้อีกค่าแทน

String playerName(String name) => name ?? 'Guest';

โดยตัวอย่างด้านบนสามารถเขียนได้อีกแบบ

// เขียนแบบยาวกว่าโดยใช้ ?: operator
String playerName(String name) => name != null ? name : 'Guest';

// เขียนแบบยาวกว่ามากโดยใช้ if-else
String playerName(String name) {
  if (name != null) {
    return name;
  } else {
    return 'Guest';
  }
}

# Cascade notation (..)

อันนี้ไม่ใช่ Operator แต่เป็นส่วนหนึ่งของ Syntax ในภาษา Dart ใช้ในการเข้าถึง object เดิมโดยไม่ต้องเขียน . ซ้ำๆ

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

ให้ผลเหมือนกับการเขียน

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

# อื่นๆ

Operator ความหมาย
() แสดงถึงการเรียกใช้ฟังก์ชัน
[] ใช้ในการเข้าถึง List ในตำแหน่งที่กำหนด
. ใช้ในการเข้าถึง object เช่น foo.bar (แปลว่าเรียกหา bar ที่อยู่ใน foo)
?. เหมือนกับ . แต่มีการตรวจสอบว่า object เป็น null ก่อนเรียกใช้หรือไม่

Part หน้าเราจะมาเริ่มกันในเรื่องการเขียน if-else, loop, หรือ switch-case และการรับมือกับ error ทีเกิดขึ้นในภาษา Dart กันครับ 😁

# แบบฝึกหัด


ข้อที่ 1 เราสามารถประกาศฟังก์ชันโดยรับตัวแปรแบบ Named และ Positional พร้อมๆกันได้หรือไม่?

ข้อที่ 2 ฟังก์ชันที่ไม่มีการใส่ return จะคืนค่าออกมาเป็นอะไร?

ข้อที่ 3 จากโปรแกรมด้านล่าง ค่าของ a และค่าของ b สุดท้ายแล้วจะเป็นเท่าไร?
var a = 0, b = 0;
void someFunc(newValue1, [newValue2 = 1]) {
  var b;
  a = newValue1;
  b = newValue2;
}
someFunc(10);
print('a = $a, b = $b');

ข้อที่ 4 จากโปรแกรมด้านล่าง answer จะเป็นเท่าไร?
var answer = (3 + 10 * 3) / 11;
print(answer);

ข้อที่ 5 จากโปรแกรมด้านล่าง answer จะเป็นอะไร?
var a = 1;
var answer = a >= 1 && a < 10
print(answer);

ข้อที่ 6 จากโปรแกรมด้านล่าง answer จะเป็นอะไร?
var answer = true || false && false;
print(answer);

ข้อที่ 7 จากโปรแกรมด้านล่าง a และ b จะเป็นอะไร?
var a = false;
a = a ?? true;
var b = a ? 1: 2;
print('a = $a, b = $b');
อัปเดตเมื่อ: 1 ปีที่แล้ว