Dart의 함수
원문 : https://dart.dev/language/functions
Dart는 진정한 객체지향 언어이기에 함수 또한 객체이며 Funtion
이라는 타입을 가집니다.
이 의미는 함수가 변수에 할당이 되거나 다른 함수에 인자로 전달될 수 있습니다.
또한 Dart 클래스의 인스턴스를 함수처럼 호출할 수 있습니다.
자세한 정보는 호출가능한 클래스를 참고하세요.
함수 구현체에 대한 예제입니다.
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
효율적인 Dart를 위해 공개 API의 타입지정을 추천하지만, 함수에서 타입을 생략해도 동작합니다.
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
만약 함수가 한가지 표현식만 있다면 간소화된 문법을 사용할 수 있습니다.
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
=> expr
문법은 { return expr; }
의 축약형입니다.=>
표기법은 화살표 문법이라고도 합니다.
매개변수
함수는 몇개던 필수적 위치의 매개변수를 가질 수 있습니다.
그 뒤로 명명된 매개변수나 선택적 매개변수를 가질 수 있습니다. (동시는 안됨)
Note
몇몇 API - 특히 Flutter의 위젯 생성자 - 는 필수 매개변수임에도 명명된 매개변수만 사용합니다.
다음 절에서 자세하게 알아보겠습니다.
함수에 인자를 전달하거나 함수의 매개변수를 정의할 때 마지막 콤마를 사용할 수 있습니다.
명명된 매개변수
명명된 매개변수는 명시적으로 required
가 마킹되기전까지는 선택적입니다.
함수를 정의할 때 명명된 매개변수를 지정하려면 {param1, param2, ...}
를 사용하면 됩니다.
// [bold]와 [hidden] 플래그를 설정
void enableFlags({bool? bold, bool? hidden}) {...}
함수를 호출할 때 명명된 인자를 paramName: value
를 사용해서 지정할 수 있습니다.
예로 들면 다음과 같습니다.
enableFlags(bold: ture, hidden: false);
null
외에 명명된 매개변수의 기본값을 정의하려면 =
를 사용해 기본값을 지정할 수 있습니다.
지정된 값은 컴파일타임 상수여야 합니다.
예로 들면 다음과 같습니다.
/// [bold]와 [hidden] 플래그 설정하기
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold는 true, hidden은 false
enableFlags(bold: true);
명명된 매개변수가 선택적 매개변수 중 하나이지만, required
를 어노테이션으로 추가해서 매개변수가 필수값으로 만들 수 있으며 사용자는 매개변수의 값을 제공해야 합니다.
예제는 다음과 같습니다.
const Scrollbar({super.key, required Widget child});
누군가 child
매개변수 없이 Scrollbar
를 만들려고 시도한다면 분석기는 이슈를 보고할 것 입니다.
Note
required
가 마킹된 매개변수는 null이 가능합니다.
const Scrollbar({super.key, required Widget? child});
위치적 인자를 먼저 배치하고 싶을 수 있지만 Dart는 요구하지는 않습니다.
Dart는 API에 적합하다면 명명된 인자를 인자 목록의 아무 곳에나 배치할 수 있도록 허용합니다.
repeat(times: 2, () {
...
});
선택적 위치 매개변수
함수의 매개변수 집합을 []
로 감싸면 선택적 위치 매개변수로 지정됩니다.
기본값을 제공하지 않으면 해당 타입의 기본값이 null
이 될 수 있도록 null 가능이어야 합니다.
String say(String from, String msg, [String? device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
선택적 매개변수없이 함수를 호출하는 예제입니다.
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
3번째 매개변수를 추가하여 함수를 호출하는 예제입니다.
assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');
null
외에 선택적 위치 매개변수의 기본값을 정의하려면 =
를 사용하여 기본값을 지정합니다.
지정된 기본값은 컴파일타임 상수여야 합니다.
예로 들면 다음과 같습니다.
String say(String from, String msg, [String device = 'carrier pigeon']) {
var result = '$from says $msg with a $device';
return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
main() 함수
모든 앱은 앱의 진입점을 제공하는 최상위 main()
함수를 가지고 있어야 합니다.main()
함수는 void
를 반환하고 선택적으로 List<String>
매개변수를 인자로 받습니다.
간단한 main()
함수 예제입니다.
void main() {
print('Hello, World!');
}
명령줄에서 인자를 받는 앱의 main()
함수 예제입니다.
// 앱을 다음과 같이 실행하면 됩니다 : 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');
}
args 라이브러리를 사용해서 명령줄 인자를 정의/파싱할 수 있습니다.
1급 클래스객체인 함수
함수를 다른 함수의 인자로 전달할 수 있습니다.
예로 들면 다음과 같습니다.
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// printElement를 인자로 전달하기
list.forEach(printElement)
또한 함수를 변수에 할당할 수 있습니다.
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
이 예제는 익명 함수를 사용했습니다.
더 자세한 내용은 다음 절을 참고하세요.
익명 함수
대부분의 함수는 main()
이나 printElement()
와 같이 이름을 가집니다.
익명 함수(람다 또는 클로저)라고 불리는 이름이 없는 함수를 만들 수도 있습니다.
익명 함수를 변수에 할당할수 있어서 콜렉션에 추가하거나 삭제할수 도 있습니다.
익명 함수는 일반적인 함수와 비슷하게 콤마로 구분하고 선택적 타입 어노테이션이 있는 0개 이상의 인자가 괄호 사이에 있습니다.
아래의 코드 블럭은 함수의 본문을 담고 있습니다.
([[Type] param1[, ...]]) {
codeBlock;
};
아래 예제는 타입이 없는 item
이라는 인자를 가진 익명 함수를 정의합니다.
목록의 각 아이템에 실행되는 함수는 문자열을 대문자로 변경합니다.
다음으로 forEach
에 전달된 익명함수에서 각 변경된 문자열을 길이와 함께 출력합니다.
const list = ['apples', 'bananas', 'oranges'];
list.map((item) {
return item.toUpperCase();
}).forEach((item) {
print('$item: ${item.length}');
});
Run 을 클릭하여 코드를 실행합니다.
void main() {
const list = ['apples', 'bananas', 'oranges'];
list.map((item) {
return item.toUpperCase();
}).forEach((item) {
print('$item: ${item.length}');
});
}
함수가 하나의 표현식 또는 반환 명령문을 가지고 있으면 화살표 표기법을 사용하여 간결하게 할 수 있습니다.
아래의 코드를 복사하여 DartPad에 붙여넣고 함수가 동일한지 Run을 클릭하여 실행해보세요.
list
.map((item) => item.toUpperCase())
.forEach((item) => print('$item: ${item.length}'));
어휘적 범위
Dart는 어휘적 범위를 가지는 언어이며, 변수의 범위를 정적으로 결정하여 코드의 레이아웃을 간결하게 합니다.
변수가 범위안에 있는지 보고싶으면 "중괄호 밖으로 따라가기"를 하면 됩니다.
각 범위 수준을 가지는 변수와 중첩 함수에 대한 예제입니다.
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()
에서 최상위 수준까지 모든 수준의 변수를 어떻게 사용할 수 있는지 확인해보세요.
어휘적 클로저
클로저는 함수가 본래의 범위 밖에서 사용되더라도 어휘적 범위에서 변수에 접근이 가능한 함수 객체입니다.
함수는 주변 범위에 정의된 변수를 포함시킬 수 있습니다.
아래 예제에서 makeAdder()
는 변수 addBy
를 캡쳐합니다.
반환된 함수가 어디로 가든, addBy
를 기억합니다.
/// 함수가 [addBy]에 함수 인자를 더하여 반환합니다.
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
// 2를 더하도록 함수를 만듦
var add2 = makeAdder(2);
// 4를 더하도록 함수를 만듦
var add4 = makeAdder(4);
assert(add2(3) == 5);
assert(add2(3) == 7);
}
함수 동일여부 테스트
최상위 함수, 정적 함수, 인스턴스 함수에 대한 동일여부를 테스트하는 예제입니다.
void foo() {} // 최상위 함수
class A {
static void bar() {} // 정적 함수
void baz() {} // 인스턴스 함수
}
void main() {
Function x;
// 최상위 함수 비교
x = foo;
assert(foo == x);
// 정적 함수 비교
x = A.bar;
assert(A.bar == x);
// 인스턴스 함수 비교
var v = A(); // A의 #1 인스턴스
var w = A(); // A의 #2 인스턴스
var y = w;
x = w.baz;
// 이 클로저는 동일한 인스턴스 (#2)를 참조합니다.
// 따라서 동일합니다.
assert(y.baz == x);
// 이 클로저는 다른 인스턴스를 참조합니다.
// 따라서 다릅니다.
assert(v.baz != w.baz);
}
반환 값
모든 함수는 값을 반환합니다.
반환되는 값이 명시되지 않으면, 암무적으로 함수 본문에 return null;
을 추가합니다.
foo() {}
assert(foo() == null);
생성기
값의 목록을 지연되게 생성이 필요한 경우 생성기 함수 사용을 고려해보세요.
Dart는 내장된 두가지 종류의 생성기 함수를 제공합니다.
동기식 생성기 함수를 구현하려면 함수본문에 sync*
를 마킹하고 값을 전달하기 위해 yield
표현식을 사용합니다.
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
비동기 생성기 함수를 구현하려면 함수본문에 async*
를 마킹하고 값을 전달하기 위해 yield
표현식을 사용합니다.
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
생성기가 재귀되면 성능을 향상시키기 위해 yield*
를 사용합니다.
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}