Dart는 완전한 객체 지향 언어이기 때문에, 함수조차도 객체이며 Function
이라는 자료형에 해당한다. 다시 말해 모든 함수는 변수에 할당될 수도 있고, 다른 함수에 매개 변수로서 전달도 가능하다. 또한 Dart 클래스도 함수로 호출이 가능한데, 이에 대해서는 나중에 알아보도록 하자.
다음은 가장 평범한 함수 선언의 예시다:
int isEvenNum(int num) {
return num % 2 == 0;
}
그리고 똑똑한 Dart는 (실제로 그럴 일은 없겠지만) 프로그래머가 실수로 출력 자료형을 생략해도 알아서 추측해준다. 그래서 이런 구현도 가능하다:
isEvenNum(int num) {
return num % 2 == 0;
}
여기에 만약 함수 코드를 한 줄로 줄일 수 있다면, {}(중괄호)와 return
를 생략하고 =>
이런 식으로 구현도 가능하다:
isEvenNum(int num) => num % 2 == 0;
다만 개인적인 의견으로는, 혼자 갖고 노는 코드면 상관이 없겠지만 다른 사람들과 협업을 해야 한다면 자료형은 명확히 써주는 게 좋다고 생각한다. 코드를 보는 타인이 굳이 return
문을 읽어가면서 출력 자료형을 추측해야 할 이유는 없으니까...
Dart의 매개 변수 형식은 크게 세 가지로 나뉜다:
여기서 positional이란 표현은 매개 변수의 순서에 영향을 받는다는 것을 나타내는 것 같다. (나타내는 것 같다고 하는 이유는 오피셜이 아니기 때문) 보통 함수가 여러 개의 매개 변수를 입력받게 되면, 순서로 그걸 구분하기 때문이다. 아래 Python 코드처럼 말이다:
def myFunc(a, b, c):
print([a, b, c].join(" / "))
myFunc(1, 2, 3)
위 코드의 4번째 줄을 보면, 1, 2, 3은 입력 순서에 따라 각각 a, b, c로 인식되고 그에 따라 함수가 실행된다. 이런 의미에서 positional이라는 표현을 붙인 것으로 추측이 된다.
어쨌든 사용자는 원하는 수만큼 필수 매개 변수를 사용할 수 있다. 아예 안 써도 되고, 여러 개 사용해도 되고. 그리고 필수 매개 변수와 함께 추가로, 선택적 매개 변수 또는 명명된 매개 변수 둘 중 하나를 같이 활용할 수 있다. 둘 중에 하나만. 그니까 만약 필수 매개 변수 + 선택적 매개 변수의 조합이라면, 명명된 매개 변수는 사용할 수 없다는 말이다.
또한 매개 변수의 순서는 무조건 필수 매개 변수가 앞에, 그리고 그 뒤에 선택적 혹은 명명된 매개 변수가 와야 한다.
필수 매개 변수는 그냥 여러분이 아는 그대로 작성해주면 된다. 그리고 위에서 언급한 것처럼 순서에 영향을 받는다:
void func(int a, int b) {
// TODO
}
선택적 매개 변수는 선언 시 [](대괄호)로 감싸야 한다. 아래 코드를 참고하자:
void func(int a, [int? b]) => print(b.runtimeType.toString());
이 코드에서 눈여겨볼 점은, b
에 해당하는 매개 변수를 넘겨주지 않을 경우 null
값으로 대체된다는 점이다. 사실 nullable한 변수를 초기화하지 않을 경우, 자동으로 null
이 할당된다는 건 전에도 적어둔 바 있지만 혹시나 해서 리마인드를 위해 다시 언급해 본다. 어쨌든 이걸 활용해서 null
과의 값을 비교하는 조건문 등도 사용해볼 수 있다.
그리고 당연히 필수 매개 변수처럼 순서에 영향을 받는다.
명명된 매개 변수는 선언 시 {}(중괄호)로 감싸야 한다. 그리고 param: value
와 같이 코드를 작성해서 매개 변수에 값을 할당하게 된다. 아래 코드를 참고하자:
void func(int a, {int? param1, int? param2, int? param3}) {
// TODO
}
func(3, param1: 10, param2: 30, param3: 124); // 원래 순서
func(3, param3: 124, param2: 30, param1: 10); // 바꾼 순서
func(3, param1: 10, param3: 124); // 하나 생략함
명명된 매개 변수는 순서와 상관 없이 매개 변수 이름으로 구분할 수 있기 때문에, 매개 변수 순서에 영향을 받지 않는다. 또한 위에서와 마찬가지로 nullable한 매개 변수를 초기화하지 않을 경우, null
로 대체된다.
void func(int a, {required int param1, int? param2, int? param3}) {
// TODO
}
만약 특정한 명명된 매개 변수가 함수에 반드시 필요한 값이라면, 위 코드와 같이 그 매개 변수의 자료형 앞에 required
키워드를 붙여 주면 된다. 이렇게 하면 굳이 nullable한 자료형 말고 non-nullable한 자료형을 사용할 수 있게 된다.
매개 변수의 기본값은 =
를 통해 정해줄 수 있다. 아래 코드를 참고하자:
void func(int a, [int b = 3]) => print(a + b);
이렇게 되면 매개 변수를 하나만 입력할 경우, b
는 자동으로 3으로 초기화되어 연산이 진행된다.
main()
함수모든 Dart 앱은 프로그램 진입점에 해당하는, 최상위 수준의 main()
함수를 갖고 있어야 한다. main()
함수는 void
를 반환하며, 매개 변수를 활용하기 위해 List<String>
형태의 arguments
리스트에 접근할 수 있다.
// 평범한 main 함수
void main() => print("Hello, world!");
// 매개 변수를 갖는 main 함수
void main(List<String> arguments) {
// TODO
}
위에서 언급한 것처럼, 함수를 객체로서 또 다른 함수에 넘겨줄 수 있다. 아래를 참고하자:
void printInteger(int num) => print(num);
var list = [1, 2, 3];
// 함수를 매개 변수에 직접 전달
list.forEach(printInteger);
또는 변수에 함수를 직접 할당할 수도 있다. 아래를 참고하자:
var func = (str) => str.toUpperCase();
print(func("hello")); // "HELLO" 출력됨
대부분의 함수는 이름을 갖고 있지만, 사용자에 필요에 따라 이름 없는 함수를 활용해야 할 수도 있다. Dart는 이를 지원하며, 선언 형식은 다음과 같다:
// 형식
(자료형 변수명1, 자료형 변수명2, ..., 자료형 변수명n) {
// TODO
}
// 작성 예시
(int val1, int val2) {
return val1 + val2;
}
감이 잘 안 온다면, 바로 위의 printInteger
함수를 조금 수정해서 보편적인 함수와 익명 함수의 형태를 비교해보자:
// 보편적인 함수
void printInteger(int num) => print(num);
var list = [1, 2, 3];
list.forEach(printInteger);
// 익명 함수
var list = [1, 2, 3];
list.forEach((num) {
print(num);
});
// 익명 함수 (한 줄로 줄였을 때)
var list = [1, 2, 3];
list.forEach((num) => print(num));
이런 익명 함수는 특히, 함수를 명명된 매개 변수로 요구하는 경우가 많은 Flutter에서 자주 활용하게 된다. 대표적으로 setState()
함수...
Dart의 모든 함수는 값을 반환한다. 만약 반환값이 정해지지 않았을 경우, return null;
이 함수 끝에 암시적으로 추가된다. 다만 사용자가 함수의 반환값 자료형을 non-nullable한 것으로 설정했다면, 그런 거 없고 무조건 반환 값을 돌려줘야 하겠지..?
noReturnFunc () {}
assert(noReturnFunc() == null);