Functions
Dart는 진짜 객체지향 언어라서 함수도 객체야. 즉, 함수도 Function 타입을 가지고 있어서 변수에 담을 수 있고, 다른 함수의 인자로 넘길 수도 있어. 심지어 클래스의 인스턴스를 함수처럼 호출할 수도 있지.
함수 정의하기
보통 함수는 이렇게 정의해:
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
타입을 생략해도 되긴 하지만, 공식 스타일 가이드(Effective Dart)는 공개 API에는 타입 명시를 권장해.
짧은 함수라면 화살표 문법(=>)을 쓰면 간단해:
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
여기서 => expr는 사실 { return expr; }의 축약형이야.
매개변수
함수 매개변수는 크게 세 가지:
- 필수 위치 매개변수
- 이름 있는 매개변수 (
{}로 감쌈) - 선택적 위치 매개변수 (
[]로 감쌈)
Named parameters (이름 있는 매개변수)
이름 매개변수는 기본적으로 선택적이야.
정의할 땐 {param1, param2} 이렇게 쓰고, 호출할 땐 paramName: value 형태로 써.
void enableFlags({bool bold = false, bool hidden = false}) {
...
}
enableFlags(bold: true);
required키워드를 붙이면 필수로 만들어야 함.required라도null을 허용할 수 있음 (required Widget? child).
Optional positional parameters (선택적 위치 매개변수)
[] 안에 넣으면 선택적 위치 매개변수야. 기본값을 지정하지 않으면 자동으로 null.
String say(String from, String msg, [String device = 'carrier pigeon']) {
return '$from says $msg with a $device';
}
say('Bob', 'Howdy'); // 기본값 사용
say('Bob', 'Howdy', 'smoke signal');
main() 함수
모든 Dart 앱은 반드시 main() 함수가 있어야 해. 프로그램 시작점이야.
인자로 List<String>을 받을 수 있어.
void main() {
print('Hello, World!');
}
void main(List<String> args) {
print(args);
}
함수도 1급 객체
Dart에선 함수도 변수에 담을 수 있어.
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
list.forEach(printElement); // 함수 전달
또는 아예 변수에 함수 자체를 담을 수도 있어:
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
print(loudify('hello'));
함수 타입(Function type)
함수 자체도 타입을 가짐.
함수의 선언부에서 이름을 빼고 Function으로 바꾸면 돼.
void greet(String name, {String greeting = 'Hello'}) =>
print('$greeting $name!');
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');
익명 함수 (Anonymous functions)
이름 없는 함수도 만들 수 있어. 흔히 람다 또는 클로저라고도 부름.
const list = ['apples', 'bananas', 'oranges'];
var uppercaseList = list.map((item) {
return item.toUpperCase();
}).toList();
uppercaseList.forEach((item) {
print('$item: ${item.length}');
});
짧게 쓰고 싶으면 화살표 문법을 쓰면 돼:
var uppercaseList = list.map((item) => item.toUpperCase()).toList();
스코프와 클로저
Dart는 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);
}
}
}
이처럼 중첩 함수는 바깥 변수들을 자유롭게 쓸 수 있어.
클로저
클로저는 함수가 자신이 정의된 스코프의 변수를 캡처해서 나중에도 쓸 수 있는 기능이야.
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
var add2 = makeAdder(2);
print(add2(3)); // 5
Tear-offs
괜히 람다로 감싸지 말고, 함수 이름을 그냥 넘기면 돼.
var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
charCodes.forEach(print); // 함수 tear-off
charCodes.forEach(buffer.write); // 메서드 tear-off
함수 동등성 비교
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();
var w = A();
var y = w;
x = w.baz;
assert(y.baz == x); // 같은 인스턴스면 동일
assert(v.baz != w.baz); // 다른 인스턴스면 다름
}
반환값
모든 함수는 값을 반환해. 아무것도 안 쓰면 자동으로 return null;.
foo() {}
assert(foo() == null);
여러 값을 반환하고 싶으면 Record를 쓰면 돼:
(String, int) foo() {
return ('something', 42);
}
제너레이터 (Generators)
값을 하나씩 게으르게 만들어내고 싶을 때 sync*와 async*를 쓴다.
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}
yield*는 다른 제너레이터를 위임해서 실행할 때 씀.
외부 함수 (External functions)
external 키워드를 쓰면, 함수 몸체 없이 시그니처만 정의할 수 있어. 실제 구현은 다른 Dart 라이브러리나 C/JS 같은 외부 언어에서 가져오는 경우가 많아.
external void someFunc(int i);
이건 FFI(외부 함수 인터페이스) 같은 상황에서 주로 쓰임.
👉 정리하면, Dart에서 함수는 객체라서 변수에 담을 수도 있고, 다른 함수에 넘길 수도 있고, 심지어 tear-off나 클로저로 더 유연하게 쓸 수도 있어.
'다트🎯' 카테고리의 다른 글
| 라이브러리 가져오기 (0) | 2025.08.31 |
|---|---|
| 메타데이터 (0) | 2025.08.31 |
| 관리 흐름 - 에러 핸들링 (0) | 2025.08.31 |
| 관리 흐름 - 분기 (0) | 2025.08.31 |
| 관리 흐름 - 반복 (0) | 2025.08.31 |