# [Function](https://dart.dev/language/functions) * 函數也是物件, 它的類型是 [Function](https://api.dart.dev/stable/3.5.2/dart-core/Function-class.html) * Dart類的實例如果有實作[call()](https://hackmd.io/@asdf121472/HJ5BN2PV1g)方法, 則也可像函數一樣呼叫實例. * 函數只有一行主體時可以用 => 表示. ``` bool isBiggerThanZero(num n) => n>0; void sayHello(String name)=>print('Hello, $name'); Function f=(e)=>print(e); f('hi'); ``` ## 參數 位置參數沒有預設值, [選擇性位置參數] 可以有預設值 位置參數後只能2選一, {named parameters} 或 [optional positional parameters] ### { named parameters } * 命名參數不提供預設值則其預設值是null (必須宣告為可空類型) * 使用 `=` 設定預設值(預設值必須是編譯時常數), 呼叫時使用 `:` 指定新的參數值 ``` void printer(String name, {String? from}) { if (from != null) { print('$name say hello from $from'); } else { print('$name just say hello'); } } void main() { printer('Bob', from: 'Line'); printer('Bob'); } ``` * 要強制呼叫者為其賦值, 可以使用 `required` 來修飾, **此時不能有預設值**, required 命名參數可以是null, 但叫用時也要直接寫明 ``` void printer(String name, {required String? from}) { if (from != null) { print('$name say hello from $from'); } else { print('$name just say hello'); } } void main() { printer('Bob', from: 'Line'); // Bob say hello from Line printer('Bob', from: null); // Bob just say hello } ``` ### [ Optional positional parameters ] * 一般位置參數不可設定預設值, 可選位置參數可設定預設值 * 可選位置參數不提供預設值則其預設值是null (必須宣告為可空類型) ``` String say(String from, String msg, [String? device]) { var result = '$from says $msg'; if (device != null) { result = '$result by a $device'; } return result; } void main() { assert(say('Bob', 'Hello') == 'Bob says Hello'); assert(say('Bob', 'Hello', 'phone') == 'Bob says Hello by a phone'); } ``` * 使用 = 設定預設值(預設值必須是編譯時常數) ``` String say(String from, String msg, [String device = 'phone']) { var result = '$from says $msg with a $device'; return result; } ``` ## The main() function * main()函數作為應用程式的入口點, 可以帶有參數 ``` // args.dart // cmd 中執行程式: dart run args.dart 1 test void main(List<String> arguments) { print(arguments); // [1,test] assert(arguments.length == 2); assert(int.parse(arguments[0]) == 1); assert(arguments[1] == 'test'); } ``` ### 附錄 Dart的安裝 使用windows 套件管理程式 [Chocolatey](https://medium.com/@yk61035/language-tool-windows%E5%A5%97%E4%BB%B6%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7-chocolatey-6bd29220b6bc) ## 函數是一級成員 * 當作傳入參數 ``` void printElement(int element) { print(element);} var list = [1, 2, 3]; list.forEach(printElement); ``` * 指派給變數 ``` var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!'; assert(loudify('hello') == '!!! HELLO !!!'); ``` ## 匿名函數 (anonymous functions, lambdas, or closures) * 匿名函數可以無參數, 有參數時, 參數型別可有可無 `([[Type] param1[, ...]]) {codeBlock;}` ``` List<String> list = ['apple', 'orange', 'banana']; // 參數e, 無型別 var upperCaseList=list.map((e) => e.toUpperCase()).toList(); // 參數e, 有型別 upperCaseList.forEach((String e)=>print('$e : ${e.length}')); ``` ## 變數作用域 可以按照「大括號向外」來查看變數是否在範圍內 ``` 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); } nestedFunction(); } myFunction(); } ``` ## 閉包 (closures) 閉包是為了函式的再利用, 經由重新包裝舊函數, 以增加新功能 (python中的decorator語法糖) ``` // 原本寫好的函數 int aPlusb(int a, int b){ return a+b; } /* 希望把 aPlusb 的結果再乘以n倍 所以用 multiN 重新包裝 aPlusb */ Function multiN(int a, int b){ return (int n)=>aPlusb(a,b)*n; } void main() { Function multi135=multiN(1, 3); assert(multi135(5)==20); } ``` ## Tear-offs > 當您使用不帶括號的函式、方法或命名建構函式時,Dart 會建立一個Tear-offs。這是一個閉包,它採用與函數相同的參數,並在呼叫它時呼叫底層函數。如果您的程式碼需要一個閉包來呼叫具有與閉包接受的相同參數的命名函數,請不要將呼叫包裝在 lambda 中。使用Tear-offs。 ``` var charCodes = ['How ', 'are ', 'you ', 'today?']; var buffer = StringBuffer(); // Function tear-off charCodes.forEach(print); // Method tear-off charCodes.forEach(buffer.write); print(buffer); ``` ## 函數相等性 ``` void foo() {} // A top-level function class A { static void bar() {} // A static method void baz() {} // An instance method } void main() { Function x; // Comparing top-level functions. x = foo; assert(foo == x); // Comparing static methods. x = A.bar; assert(A.bar == x); // Comparing instance methods. var v = A(); // Instance #1 of A var w = A(); // Instance #2 of A var y = w; x = w.baz; // These closures refer to the same instance (#2), // so they're equal. assert(y.baz == x); // These closures refer to different instances, // so they're unequal. assert(v.baz != w.baz); } ``` ## return 函數都有回傳值, 沒有return的函數有隱式的 return null ``` foo(){} assert(foo()==null); ``` 多個回傳值用record回傳 ``` (String, int) foo() { return ('Bob', 42); } ``` ## 生成器 (Generators) 當你有一連串的數值, 且希望要用到時才產生項目值(以節省執行時間), 這時可用Generators, dart有兩種生成器函數: * Synchronous 同步: 回傳 [Iterable](https://api.dart.dev/stable/dart-core/Iterable-class.html) 物件 用 `sync*` 標記函數, 並以yield回傳函數值, 則此函數會回傳一個Generator **(Iterator)** ``` Iterable<String> toUpperCase(List<String> list) sync* { for (String i in list) { yield i.toUpperCase(); } } List<String> alpha = []; for (int i = 97; i <= 122; i++) { alpha.add(String.fromCharCode(i)); } Iterable<String> generator = toUpperCase(alpha); generator.forEach(print); // tear-offs ``` * Asynchronous 異步:傳回 [Stream](https://api.dart.dev/stable/dart-async/Stream-class.html) 物件 用 `async*` 標記函數, 並以yield回傳函數值, 則此函數會回傳一個Generator **(Stream)** ``` void main() async { Stream<int> numberStream = getNumbersStream(); await for (int number in numberStream) { print(number); // Output: 1, 2, 3, 4, 5 } print('Stream is done!'); } // A function that returns a stream of numbers Stream<int> getNumbersStream() async* { for (int i = 1; i <= 5; i++) { await Future.delayed(Duration(seconds: 1)); // Simulate a delay yield i; // Emit each number } } ```