티스토리 뷰
다트] 함수, 화살표 구문, 매개변수, 익명 함수, 렉시컬 스코프, 렉시컬 클로저, tear-off, 반환 값, 제너레이터, 외부 함수
철철박사 2024. 7. 20. 22:35함수
다트는 진정한 객체지향 언어이기 때문에 함수도 객체이고, 함수는 Function이라는 타입을 갖는다. 함수는 객체이므로 변수에 할당하거나 다른 함수에 인수로 전달할 수도 있다.
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
타입을 생략해도 타입 추론으로 함수는 여전히 작동하지만 추천하지 않는다.
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
화살표 구문
함수 본문에 표현식이 하나라면 화살표 구문(arrow syntax)를 사용할 수 있다.
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
매개변수
다트에서 지원하는 매개변수의 종류는 3가지가 있다.
- 위치 매개변수 (positional arguments)
- 명명된 매개변수 (named arguments)
- 선택적 위치 매개변수 (optional positional arguments)
매개변수는 다음과 같은 조합으로 사용할 수 있다.
- 위치 매개변수
- 위치 매개변수 + 명명된 매개변수
- 위치 매개변수 + 옵셔널 위치 매개변수
- 명명된 매개변수
- 옵셔널 위치 매개변수
명명된 매개변수
명명된 매개변수는 required로 표시하지 않으면 선택사항이 된다. 매개변수를 {}로 감싸면 명명된 매개변수가 된다. 기본값이 없으면 널-가능 타입이어야 한다.
/// bold과 hidden은 선택사항이 된다.
void enableFlags({bool? bold, bool? hidden}) {...}
함수를 호출할 때 "매개변수이름: 값"형식으로 명명된 매개변수를 지정할 수 있다.
enableFlags();
enableFlags(bold: true);
enableFlags(bold: true, hidden: false);
enableFlags(hidden: false, bold: true);
enableFlags(hidden: false);
=을 사용하여 기본값을 지정할 수 있다. 기본값은 컴파일-타임 상수여야 한다.
void enableFlags({bool bold = false, bool hidden = false}) {...}
// bold는 true가 될 것이고, hidden은 false가 될 것입니다.
enableFlags(bold: true);
명명된 매개변수를 필수로 사용하게 하려면 required를 사용한다.
const Scrollbar({required Widget child});
required를 사용해도 널-가능 타입을 사용해서 null을 입력받을 수 있다.
const Scrollbar({required Widget? child});
위치 매개변수와 명명된 매개변수가 같이 사용될 때, 명명된 매개변수를 먼저 인수로 전달할 수 있다.
void repeat(void Function() operation, {int? times}) { ... }
repeat(times: 2, () {
...
});
선택적 위치 매개변수
매개변수를 []로 감싸면 선택적 위치 매개변수가 된다. 기본값을 제공하지 않으면 널-가능 타입이어야 한다.
String say(String from, String msg, [String? device]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
return result;
}
선택적 위치 매개변수를 사용하지 않고 say 함수를 호출하는 예제이다.
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
다음은 위치 매개변수를 사용하여 호출한 예제이다.
assert(say('Bob', 'Howdy', 'smoke signal') ==
'Bob says Howdy with a smoke signal');
=를 사용하여 기본값을 지정할 수 있다. 기본값은 컴파일-타임 상수여야 한다.
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() 함수를 가져야 한다. 이 함수가 앱의 진입점(entry point) 역할을 한다. main() 함수는 void를 반환하며, 인수 전달이 필요하면 List<String> 매개변수를 사용한다.
void main() {
print('Hello, World!');
}
다음은 인수를 사용하는 main() 함수이다.
void main(List<String> arguments) {
print(arguments);
assert(arguments.length == 2);
assert(int.parse(arguments[0]) == 1);
assert(arguments[1] == 'test');
}
위 코드가 args.dart 파일일 경우, 커맨드 라인에서 다음과 같이 사용된다.
dart args.dart 1 test
함수를 일급 객체로 사용하기
함수는 일급 객체이므로 다른 함수의 매개변수로 전달할 수 있다.
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
list.forEach(printElement);
함수를 변수에 할당할 수도 있다.
var func = printElement;
Function으로 명시적으로 타입을 지정할 수도 있다.
void Function(int) func = printElement;
익명 함수
대부분의 함수는 main()처럼 이름이 지정된 함수이다. 익명 함수(또는 람다, 클로저)라고 불리는 이름이 없는 함수를 생성할 수도 있다.
익명 함수는 일반 함수와 유사한 구조를 갖는다.
list.forEach((int element) {
print(element);
});
다트는 타입 추론이 되므로 익명 함수의 매개변수 타입을 사용하지 않아도 된다.
list.forEach((element) {
print(element);
});
위의 코드는 화살표 표기법으로 더 간결하게 만들 수 있다.
list.forEach((element) => print(element));
렉시컬 스코프 (Lexical scope)
다트는 렉시컬 스코프 언어로, 변수의 범위가 코드의 레이아웃을 기반으로 정해진다. 중괄호를 따라 바깥으로 나가면서 변수가 범위 내에 있는지 확인할 수 있다.
다음 코드에서 nestedFunction()이 모든 레벨에서 상위 레벨까지의 변수를 사용할 수 있다는 것에 주목하자.
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);
}
}
}
렉시컬 클로저 (Lexical closures)
클로저는 원래 스코프를 벗어나 사용되더라도 랙시컬 스코프에서 변수에 접근할 수 있는 함수 객체이다. 함수는 주변 스코프에서 정의된 변수를 클로저로 닫을 수 있다.
다음 예에서 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(add4(3) == 7);
}
함수 동등성 테스트
다음은 최상위 함수, 정적 메서드, 인스턴스 메서드의 동등성을 확인하는 예제이다.
void topLevelFunc() {} // 최상위 함수
class MyClass {
static void staticFunc() {} // 정적 메서드
void instanceFunc() {} // 인스턴스 메서드
}
void main() {
// 최상위 함수를 비교한다.
var _topLevelFunc = topLevelFunc;
assert(_topLevelFunc == topLevelFunc);
// 정적 메서드를 비교한다.
var _myClassStaticFunc = MyClass.staticFunc;
assert(_myClassStaticFunc == MyClass.staticFunc);
var myClass1 = MyClass();
var myClass2 = MyClass();
var myClass1InstanceFunc = myClass1.instanceFunc;
var myClass2InstanceFunc = myClass2.instanceFunc;
// 같은 인스턴스의 메서드를 비교하므로 동등하다.
assert(myClass1.instanceFunc == myClass1InstanceFunc);
// 서로 다른 인스턴스의 메서드를 비교하므로 동등하지 않다.
assert(myClass1InstanceFunc != myClass2InstanceFunc);
}
Tear-off
괄호 없이 함수, 메서드, 또는 명명된 생성자를 참조하면 Dart는 tear-off를 생성한다. tear-off는 동일한 매개변수를 취하고 호출하는 클로저이다.
클로저가 동일한 매개변수를 받아 명명된 함수를 호출해야 하는 경우, 익명 함수를 만들지 말고 tear-off를 사용하자.
var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
좋음 : tear-offs를 사용하는 경우
// Function tear-off
charCodes.forEach(print);
// Method tear-off
charCodes.forEach(buffer.write);
나쁨 : tear-offs를 사용하지 않고, 익명 함수로 사용하는 경우
// Function lambda
charCodes.forEach((code) {
print(code);
});
// Method lambda
charCodes.forEach((code) {
buffer.write(code);
});
반환 값
모든 함수는 값을 반환한다. 반환 값이 지정되지 않는 경우, return null;이 암시적으로 함수 본문 끝에 추가된다.
foo() {}
assert(foo() == null);
함수에서 여러 값을 반환하려면 레코드를 사용하면 된다.
(String, int) foo() {
return ('something', 42);
}
제너레이터
값의 연속을 지연하여 생성해야 할 때, 제너레이터를 사용하는 것이 좋다.
다트는 두 가지 종류의 제너레이터 함수를 내장하고 있다.
- 동기 제너레이터 : Iterable 객체를 반환한다.
- 비동기 제너레이터 : Stream 객체를 반환한다.
동기 제너레이터 함수를 구현하려면 함수 본문을 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);
}
}
외부 함수
외부 함수는 선언과 별도로 구현되는 함수이다.
함수 선언 앞에 external 키워드를 포함하여 다음과 같이 선언한다.
external void someFunc(int i);
외부 함수의 구현은 다른 다트 라이브러리에서 오거나, 더 일반적으로 다른 언어에서 올 수 있다.
외부 함수는 최상위 함수, 인스턴스 메서드, getter 또는 setter, 또는 비-리디렉팅 생성자가 될 수 있다.
인스턴스 변수도 외부일 수 있는데, 이는 외부 getter와 변수가 final이 아닌 외부 setter와 동일하다.
'다트 공식 문서 번역' 카테고리의 다른 글
다트] 컬렉션 - 리스트(list), 셋(set), 맵(map) (0) | 2024.07.22 |
---|---|
다트] 레코드 - 구문, 필드, 타입, 동등성, 집합 반환 (0) | 2024.07.22 |
다트] 내장 타입 - 숫자, 문자열, 불리언, 룬과 그래프 클러스터, 심볼 (0) | 2024.07.20 |
다트] 키워드 (0) | 2024.07.20 |
다트] 라이브러리 사용법 (0) | 2024.07.20 |