함수는 코드에서 여러 번 재사용할 수 있는 코드 블록으로, 특정 작업을 수행하기 위해 정의된다. Dart에서 함수는 기본적으로 void 타입으로 정의되거나 값을 반환할 수 있는 타입으로 정의될 수 있다. 함수의 호출은 해당 함수가 수행되어야 할 시점에서 이루어진다.

함수의 기본 구조

Dart에서 함수는 반환형, 함수명, 그리고 매개변수로 구성된다. 가장 기본적인 함수 구조는 다음과 같다.

반환형 함수명(매개변수) {
  // 함수 본문
}

반환형

반환형은 함수가 실행된 후 반환할 데이터의 타입을 의미한다. 예를 들어, 함수가 정수를 반환하는 경우 반환형은 int이다. 만약 함수가 값을 반환하지 않으면 반환형은 void로 설정된다.

함수명

함수명은 함수를 호출할 때 사용하는 이름이다. Dart에서는 함수명에 알파벳, 숫자, 밑줄(_)을 사용할 수 있으며, 숫자로 시작할 수 없다.

매개변수

매개변수는 함수에 전달될 값들이며, 함수 실행에 필요한 데이터를 전달하는 역할을 한다. 매개변수는 여러 개 정의할 수 있고, 각 매개변수는 특정한 데이터 타입을 가질 수 있다. 예를 들어, 두 정수를 더하는 함수를 정의하려면 다음과 같이 할 수 있다.

int add(int a, int b) {
  return a + b;
}

이 함수는 ab라는 두 정수 매개변수를 받아 그들의 합을 반환한다.

함수 호출

정의된 함수를 호출하려면 함수명과 함께 필요한 매개변수를 전달해야 한다. 위에서 정의한 add 함수를 호출하는 예시는 다음과 같다.

int result = add(3, 4); // result는 7이 된다.

함수를 호출하면 함수는 매개변수로 받은 값을 사용하여 정의된 작업을 수행하고, 그 결과를 반환한다.

수학적 표현

함수 호출은 수학적으로 표현할 수 있다. 함수 f(x)가 주어졌을 때, 함수의 호출은 다음과 같다.

y = f(x)

예를 들어, 두 숫자를 더하는 함수 f(a, b)는 다음과 같이 정의할 수 있다.

f(a, b) = a + b

그리고 함수가 호출될 때, 매개변수 ab에 값이 전달되면 그 합이 반환된다. 위 코드의 예시는 다음과 같은 수학적 표현에 해당한다.

\mathbf{result} = f(3, 4) = 7

이와 같은 수식적 표현은 함수의 동작을 이해하는 데 매우 유용하다.

매개변수의 기본 값과 선택적 매개변수

Dart에서는 함수 매개변수에 기본 값을 제공할 수 있으며, 선택적으로 전달할 매개변수를 지정할 수 있다. 이를 통해 함수의 유연성을 높일 수 있다.

기본 값

함수를 정의할 때, 매개변수에 기본 값을 설정할 수 있다. 기본 값을 가지는 매개변수는 함수 호출 시 전달되지 않으면 해당 값으로 초기화된다.

void printMessage(String message, [String sender = "Anonymous"]) {
  print('$message from$sender');
}

위 함수는 message 매개변수는 필수로 받고, sender 매개변수는 선택적으로 받으며, 전달되지 않으면 "Anonymous"로 기본 값이 설정된다. 함수 호출 예시는 다음과 같다.

printMessage('Hello');  // Hello from Anonymous
printMessage('Hello', 'Dart');  // Hello from Dart

선택적 매개변수

매개변수를 대괄호 []로 감싸면 해당 매개변수가 선택적으로 처리된다. 선택적 매개변수는 함수 호출 시 반드시 제공되지 않아도 된다. Dart는 두 가지 형태의 선택적 매개변수를 지원한다: 위치 기반 선택적 매개변수와 이름 기반 선택적 매개변수이다.

위치 기반 선택적 매개변수

매개변수에 순서를 기반으로 선택적으로 값을 전달할 수 있다. 위 예제에서 sender는 위치 기반 선택적 매개변수이다. 함수 호출 시 첫 번째 매개변수는 필수로 제공되어야 하고, 두 번째 매개변수는 선택 사항이다.

이름 기반 선택적 매개변수

이름 기반 선택적 매개변수는 매개변수의 이름을 지정하여 값을 전달할 수 있다. 이를 통해 매개변수 순서를 엄격하게 따르지 않고 원하는 매개변수에만 값을 전달할 수 있다.

void displayUserInfo({String name = "Unknown", int age = 0}) {
  print('Name: $name, Age:$age');
}

이 함수는 nameage라는 이름 기반 선택적 매개변수를 가지며, 둘 다 기본 값을 갖는다. 함수 호출 예시는 다음과 같다.

displayUserInfo();  // Name: Unknown, Age: 0
displayUserInfo(name: 'Alice');  // Name: Alice, Age: 0
displayUserInfo(age: 25);  // Name: Unknown, Age: 25

수학적 표현

선택적 매개변수를 가진 함수는 수학적으로도 유용하게 표현할 수 있다. 예를 들어, f(a, b) 함수가 있을 때, b는 선택적으로 제공되는 매개변수라면, 이를 다음과 같이 표현할 수 있다.

f(a, b = b_{\text{default}}) = a + b

이 수식에서 b가 제공되지 않으면 기본 값인 b_{\text{default}}가 사용된다. 따라서 함수 호출에서 매개변수가 선택적으로 전달될 수 있는 상황을 설명할 수 있다.

익명 함수

익명 함수(Anonymous Function)는 이름이 없는 함수로, 함수가 간단하거나 일시적으로 필요한 경우 사용된다. Dart에서 익명 함수는 다음과 같이 정의된다.

var multiply = (int a, int b) {
  return a * b;
};

익명 함수는 보통 함수가 정의된 후 바로 사용되기 때문에 이름을 붙이지 않고 변수에 할당하여 사용하거나 함수의 인수로 전달할 수 있다.

void main() {
  print(multiply(3, 4));  // 12
}

람다 표현식

람다 표현식(Lambda Expression)은 익명 함수의 간결한 형태로, Dart에서 매우 자주 사용된다. Dart에서는 람다 표현식을 => 연산자를 이용하여 정의할 수 있다. 일반 함수와 달리 람다 표현식은 단일 명령문만 포함할 수 있다. 따라서 함수 본문이 단 한 줄로 이루어진 경우에 유용하게 사용할 수 있다.

람다 표현식의 기본 구조

람다 표현식은 => 연산자를 사용하여 정의된다. 이때, 함수 본문은 중괄호 {} 대신 => 뒤에 오는 한 줄의 표현식으로 대체된다. 반환 값 역시 자동으로 반환되므로 return 키워드를 명시할 필요가 없다.

int add(int a, int b) => a + b;

위 함수는 ab를 더한 값을 반환하는 람다 표현식이다. 함수 호출은 일반 함수와 동일한다.

void main() {
  print(add(2, 3));  // 5
}

여러 예시

람다 표현식은 다양한 형태로 활용될 수 있다. 예를 들어, 리스트(List)의 각 요소에 특정 연산을 적용하려면 다음과 같이 사용할 수 있다.

var numbers = [1, 2, 3, 4, 5];
var squared = numbers.map((n) => n * n).toList();
print(squared);  // [1, 4, 9, 16, 25]

위 코드에서 map 함수에 전달된 익명 함수는 람다 표현식으로 작성되어, 리스트의 각 요소를 제곱하는 역할을 한다.

람다 표현식의 또 다른 예시는 리스트 필터링이다.

var evenNumbers = numbers.where((n) => n % 2 == 0).toList();
print(evenNumbers);  // [2, 4]

이 함수는 리스트의 짝수만을 필터링하여 새로운 리스트를 반환한다.

수학적 표현

람다 표현식은 수학적으로 함수 f를 정의할 때와 유사하게 표현할 수 있다. 예를 들어, 두 매개변수 a, b를 더하는 함수는 다음과 같이 간단히 표현할 수 있다.

f(a, b) = a + b

또한, 리스트에서 짝수만 필터링하는 작업을 수학적으로 표현하면 다음과 같다.

\mathbf{evenNumbers} = \{ x \in \mathbf{numbers} \mid x \% 2 = 0 \}

이 표현은 \mathbf{numbers} 리스트에서 x \% 2 = 0인 요소들만 선택하는 것을 나타낸다.

람다 표현식은 Dart에서 복잡한 연산을 간결하게 표현할 수 있도록 도와주는 강력한 도구이다.

함수의 매개변수 전달 방식

Dart에서 함수의 매개변수는 값에 의한 호출(call by value) 방식을 사용하여 전달된다. 이는 함수에 전달된 매개변수가 함수 내부에서 수정되더라도 원본 값은 변경되지 않음을 의미한다. Dart에서는 모든 데이터 타입이 객체이기 때문에, 함수에 전달되는 값이 원시 데이터 타입(예: int, double, bool)일 경우, 해당 값의 복사본이 함수로 전달된다.

값에 의한 호출

Dart에서 기본적으로 사용하는 매개변수 전달 방식은 값에 의한 호출이다. 이를 통해 함수에 값을 전달할 때, 해당 값의 복사본이 함수 내부에서 사용된다. 이는 함수가 원본 값을 수정하지 못하도록 하는 방식이다.

예를 들어, 다음 코드를 살펴봅시다.

void increment(int number) {
  number++;
}

void main() {
  int a = 5;
  increment(a);
  print(a);  // a는 여전히 5이다.
}

위 코드에서 increment 함수는 매개변수 number를 1 증가시키지만, 원본 변수 a는 그대로 5로 남습니다. 이는 값에 의한 호출 방식 때문에 발생하는 현상이다.

참조에 의한 호출

Dart에서는 모든 객체가 참조로 전달되기 때문에, 객체 자체는 참조에 의한 호출(call by reference) 방식으로 처리된다. 이는 함수 내부에서 객체의 속성을 변경하면 원본 객체의 속성도 변경된다는 의미이다. 객체를 함수로 전달할 때, 객체의 메모리 참조를 함수가 사용하게 된다.

예를 들어, 리스트와 같은 객체를 함수로 전달하면 다음과 같은 결과를 얻을 수 있다.

void modifyList(List<int> numbers) {
  numbers[0] = 100;
}

void main() {
  var list = [1, 2, 3];
  modifyList(list);
  print(list);  // [100, 2, 3]
}

위 코드에서 modifyList 함수는 리스트의 첫 번째 요소를 100으로 변경하며, 이는 함수 밖의 원본 리스트에도 영향을 미친다. 객체는 참조에 의해 전달되므로 함수 내부에서 객체의 속성을 수정할 수 있다.

수학적 표현

값에 의한 호출은 수학적으로 다음과 같이 표현할 수 있다. 예를 들어, 함수 f(x)가 주어졌을 때, 매개변수 x의 복사본이 함수 내부에서 사용된다.

f(x_{\text{copy}}) = x_{\text{copy}} + 1

이때, 원본 x 값은 변경되지 않는다. 참조에 의한 호출의 경우는 다음과 같다. 함수 g(\mathbf{x})는 리스트 \mathbf{x}의 참조를 사용하여 그 값을 변경할 수 있다.

g(\mathbf{x}) = \mathbf{x}[0] = 100

이 함수는 리스트 \mathbf{x}의 첫 번째 요소를 100으로 변경하며, 이는 함수 밖에서도 그대로 유지된다.

재귀 함수

재귀 함수(Recursive Function)는 자기 자신을 호출하는 함수이다. 재귀 함수는 복잡한 문제를 단순화하여 반복적으로 해결할 수 있도록 해준다. 그러나 무한히 재귀 호출이 발생하지 않도록 탈출 조건을 정의해야 한다.

기본 구조

재귀 함수는 다음과 같은 구조를 갖는다.

반환형 함수명(매개변수) {
  if (탈출 조건) {
    return 결과;
  }
  return 함수명(재귀 호출);
}

탈출 조건이 만족될 때까지 함수는 계속해서 자신을 호출하게 된다.

예시: 팩토리얼 계산

팩토리얼 계산은 재귀 함수의 대표적인 예이다. 팩토리얼 n!은 1부터 n까지의 정수를 곱한 값으로 정의되며, 다음과 같은 수식으로 표현할 수 있다.

n! = n \times (n - 1)!

이 수식을 재귀 함수로 구현하면 다음과 같다.

int factorial(int n) {
  if (n == 0) {
    return 1;
  }
  return n * factorial(n - 1);
}

이 함수는 n = 0일 때 1을 반환하고, 그렇지 않으면 자기 자신을 다시 호출하여 값을 계산한다.

수학적 표현

재귀 함수는 수학적으로 귀납적 정의와 유사한다. 팩토리얼 계산을 수학적으로 표현하면 다음과 같다.

f(n) = \begin{cases} 1 & \text{if } n = 0 \\ n \times f(n-1) & \text{if } n > 0 \end{cases}

이와 같은 재귀적 정의는 함수의 동작을 수학적으로 이해하는 데 도움을 준다.