# 익명 함수와 람다 표현식

#### 익명 함수란?

익명 함수는 이름이 없는 함수를 말한다. 일반적으로 함수는 정의될 때 이름을 가지지만, 때로는 이름이 필요 없거나 임시로 사용하는 경우가 있다. 이러한 함수는 주로 일회성으로 사용되며, 함수의 동작이 단순하거나 코드의 가독성을 높이기 위한 목적으로 활용된다. Dart에서는 익명 함수를 간단하게 작성할 수 있으며, 이러한 익명 함수는 콜백 함수나 다른 함수의 인자로 사용되기도 한다.

익명 함수는 Dart에서 다음과 같이 정의할 수 있다:

```dart
var multiplyByTwo = (int x) {
  return x * 2;
};
```

위 예시에서 `multiplyByTwo`는 익명 함수의 참조를 저장하고 있다. `multiplyByTwo`는 함수 이름이 아니라 변수명이며, 실제 함수는 `(int x)`부터 시작하는 부분이다.

익명 함수의 특징:

* 함수 이름이 없다.
* 콜백 함수로 자주 사용된다.
* 함수를 인자로 넘길 때 유용하다.
* 지역 범위(scope)에서만 사용할 수 있다.

#### 람다 표현식

람다 표현식은 익명 함수의 특별한 형태로, 간결하게 함수를 정의하는 방법이다. Dart에서는 람다 표현식을 화살표(`=>`)를 사용하여 표현할 수 있으며, 이는 보통 간단한 연산이나 단일 명령어로 이루어진 함수를 정의할 때 사용된다.

예를 들어, 위의 익명 함수를 람다 표현식으로 변환하면 다음과 같다:

```dart
var multiplyByTwo = (int x) => x * 2;
```

람다 표현식은 일반적인 함수 정의와는 다르게 `return` 키워드를 생략하며, 화살표(`=>`) 뒤의 표현식이 함수의 반환값이 된다.

**문법 구조**

람다 표현식의 일반적인 구조는 다음과 같다:

```dart
(parameters) => expression;
```

여기서 `parameters`는 함수의 매개변수이고, `expression`은 반환될 표현식이다.

#### 익명 함수와 람다 표현식의 차이

익명 함수와 람다 표현식의 차이는 주로 문법적인 간결성에 있다. 익명 함수는 `return` 문을 포함하는 여러 줄로 구성될 수 있지만, 람다 표현식은 단일 표현식만 가질 수 있다.

익명 함수 예시:

```dart
var addNumbers = (int a, int b) {
  return a + b;
};
```

람다 표현식 예시:

```dart
var addNumbers = (int a, int b) => a + b;
```

람다 표현식은 본질적으로 간단한 계산이나 단일 작업을 수행할 때 사용하기 적합한다. 반면, 익명 함수는 보다 복잡한 논리나 여러 줄로 구성된 작업을 처리할 때 주로 사용된다.

#### 익명 함수와 람다 표현식의 활용 예시

익명 함수와 람다 표현식은 주로 콜백 함수나 간단한 처리를 해야 할 때 많이 사용된다. 이 두 가지는 Dart에서 고차 함수(higher-order function)를 사용할 때 매우 유용하다. 고차 함수는 다른 함수를 인자로 받거나, 함수를 반환하는 함수이다.

**콜백 함수로 사용하기**

콜백 함수는 주로 리스트를 순회할 때 또는 비동기 작업에서 자주 사용된다. Dart에서는 `List` 클래스의 `forEach` 메소드에 익명 함수나 람다 표현식을 콜백으로 전달할 수 있다.

예를 들어, 다음 코드는 리스트의 모든 요소를 출력하는 익명 함수의 사용 예시이다:

```dart
var numbers = [1, 2, 3, 4];
numbers.forEach((number) {
  print('Number: $number');
});
```

위 코드는 익명 함수를 사용하여 리스트의 각 요소를 `forEach` 메소드로 순회하며 출력하는 역할을 한다.

람다 표현식을 사용하면 이를 더 간결하게 표현할 수 있다:

```dart
numbers.forEach((number) => print('Number: $number'));
```

람다 표현식은 표현식이 간단할 때 유용하며, 코드의 가독성을 높이는 데 도움이 된다.

**고차 함수에서 사용하기**

고차 함수는 함수형 프로그래밍의 중요한 개념 중 하나로, Dart에서 매우 강력하게 활용될 수 있다. Dart의 `List` 클래스에는 함수형 프로그래밍의 개념을 도입한 다양한 메소드가 있다. 예를 들어 `map`, `where`, `reduce` 등이 고차 함수의 대표적인 예시이다.

다음은 리스트의 모든 요소를 제곱하는 고차 함수 `map`을 사용하는 예시이다:

```dart
var numbers = [1, 2, 3, 4];
var squaredNumbers = numbers.map((number) {
  return number * number;
});
print(squaredNumbers.toList());
```

람다 표현식으로 간결하게 변환하면 다음과 같이 작성할 수 있다:

```dart
var squaredNumbers = numbers.map((number) => number * number);
```

람다 표현식을 사용하면 코드를 더욱 간결하게 표현할 수 있다.

#### 익명 함수와 람다 표현식의 성능

익명 함수와 람다 표현식은 Dart에서 유연하고 간결한 코드 작성을 가능하게 하지만, 성능 측면에서 고려해야 할 점이 있다. Dart에서 익명 함수와 람다 표현식은 일반 함수와 동일하게 동작하지만, 이들이 남발될 경우 성능에 약간의 영향을 미칠 수 있다. 특히 많은 고차 함수가 중첩된 경우에는 성능 저하가 발생할 수 있다.

#### 클로저(Closure)와 익명 함수

익명 함수와 람다 표현식은 Dart에서 클로저(closure)로 동작할 수 있다. 클로저란 함수가 선언된 스코프(scope) 외부의 변수들을 참조할 수 있는 함수를 의미한다. Dart에서 함수는 자신이 선언된 스코프 외부의 변수를 "포착"하고, 그 변수를 계속해서 사용할 수 있다. 이는 함수형 프로그래밍에서 강력한 기능 중 하나이다.

**클로저 예시**

다음 예시는 클로저의 작동 방식을 보여준다. 익명 함수는 자신이 선언된 환경의 변수를 계속해서 참조할 수 있다:

```dart
Function makeAdder(int addBy) {
  return (int i) => i + addBy;
}

void main() {
  var add2 = makeAdder(2);
  var add5 = makeAdder(5);

  print(add2(3)); // 5
  print(add5(3)); // 8
}
```

위의 예시에서 `makeAdder` 함수는 `addBy`라는 변수를 포착(closing)하고, 나중에 반환된 함수가 이를 참조한다. 클로저는 이처럼 함수가 선언된 환경에서 변수를 "기억"하고, 해당 변수를 나중에 사용할 수 있게 만든다.

#### 클로저와 상태 유지

클로저는 변수의 상태를 유지하는 데도 유용하게 쓰일 수 있다. Dart에서 상태를 유지하기 위해 종종 클로저를 사용하여 내부 상태를 관리한다. 예를 들어, 다음 코드에서는 익명 함수를 이용해 상태를 유지하는 예시이다:

```dart
Function counter() {
  int count = 0;
  
  return () {
    count += 1;
    return count;
  };
}

void main() {
  var myCounter = counter();
  
  print(myCounter()); // 1
  print(myCounter()); // 2
  print(myCounter()); // 3
}
```

이 예시에서 `counter` 함수는 `count`라는 내부 상태를 유지하는 클로저를 반환한다. 반환된 함수는 호출될 때마다 `count`의 값을 증가시키며, 그 값을 반환한다. 클로저를 통해 함수는 상태를 유지할 수 있으며, 이를 활용하여 다양한 프로그래밍 기법을 적용할 수 있다.

#### 함수형 프로그래밍에서의 응용

람다 표현식과 익명 함수는 함수형 프로그래밍에서 매우 자주 사용된다. Dart는 함수형 프로그래밍의 요소를 지원하며, 이러한 함수들을 활용하여 더 간결하고 선언적인 코드를 작성할 수 있다.

예를 들어, Dart에서 함수형 스타일로 리스트를 변환하거나 필터링할 때 람다 표현식을 사용할 수 있다:

```dart
var numbers = [1, 2, 3, 4, 5];

// 짝수만 필터링
var evenNumbers = numbers.where((n) => n % 2 == 0).toList();
print(evenNumbers); // [2, 4]

// 모든 숫자를 제곱
var squaredNumbers = numbers.map((n) => n * n).toList();
print(squaredNumbers); // [1, 4, 9, 16, 25]
```

위의 코드에서 `where` 메소드는 리스트에서 조건을 만족하는 요소만 반환하며, `map` 메소드는 리스트의 각 요소를 변환한다. 이러한 함수형 메소드는 익명 함수나 람다 표현식과 결합하여 매우 강력하게 사용할 수 있다.

#### 클로저와 메모리 관리

클로저는 Dart에서 강력한 기능을 제공하지만, 사용 시 메모리 관리에도 신경을 써야 한다. 클로저는 함수가 참조하는 변수를 포함한 환경을 유지하기 때문에, 예상치 못하게 메모리가 계속 사용될 수 있다. 클로저가 참조하는 변수는 클로저가 살아있는 한 메모리에서 해제되지 않기 때문이다.

이러한 메모리 관리는 다음과 같은 상황에서 주의가 필요하다:

1. **장기적인 상태 유지**: 클로저가 자주 호출되면서 내부 상태를 계속 유지하는 경우, 필요 이상으로 메모리를 차지할 수 있다.
2. **변수를 참조하는 클로저**: 클로저가 외부 변수를 참조할 때, 해당 변수가 더 이상 필요하지 않아도 메모리에 남아있을 수 있다.

**메모리 관리 주의사항**

클로저는 매우 유용하지만, 메모리 문제를 방지하기 위해 다음 사항들을 고려해야 한다:

* 필요하지 않은 클로저는 가능한 한 빨리 참조를 해제하여 메모리가 낭비되지 않도록 해야 한다.
* 함수의 범위(scope) 외부에 있는 큰 데이터를 클로저로 지나치게 많이 참조하지 않도록 주의한다.

#### 익명 함수와 람다 표현식의 성능 최적화

Dart에서 익명 함수와 람다 표현식은 유연하고 간결한 코드 작성을 가능하게 하지만, 성능 면에서는 몇 가지 주의가 필요하다. Dart는 JIT(Just-In-Time) 컴파일러와 AOT(Ahead-Of-Time) 컴파일러를 모두 사용하는 언어로, 함수 호출은 JIT 환경에서는 빠르게 실행되지만, 필요 이상의 함수 호출이나 중첩된 클로저의 사용은 성능 저하를 일으킬 수 있다.

**성능 최적화를 위한 팁**

1. **익명 함수와 람다 표현식을 남용하지 말라**: 불필요하게 많은 익명 함수나 람다 표현식을 사용할 경우, 특히 성능이 중요한 코드에서는 가독성과 성능 저하가 발생할 수 있다. 복잡한 작업이 필요한 경우에는 명시적인 함수를 정의하여 사용한다.
2. **복잡한 계산 피하기**: 람다 표현식은 단일 명령어로 간결하게 표현하는 것이 목적이므로, 복잡한 논리나 많은 연산을 람다 표현식에서 처리하는 것은 바람직하지 않는다. 복잡한 연산은 일반 함수로 처리하는 것이 좋다.
3. **중첩된 클로저 조심**: 중첩된 클로저는 메모리 사용량과 성능에 영향을 미칠 수 있다. 필요할 때에만 클로저를 사용하고, 불필요한 중첩은 피하는 것이 좋다.

#### 람다 표현식과 고차 함수 조합

람다 표현식은 Dart에서 고차 함수와 함께 사용하면 매우 강력한 도구가 된다. 특히, Dart의 `List`와 같은 컬렉션 클래스는 고차 함수 메소드를 많이 제공하며, 이를 활용하여 데이터를 효율적으로 처리할 수 있다.

**고차 함수의 예시**

람다 표현식과 고차 함수의 조합은 코드의 간결성과 가독성을 크게 향상시킬 수 있다. 다음 예시는 Dart에서 고차 함수 `map`을 사용하여 리스트의 값을 제곱하는 간단한 예이다:

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

또한, 고차 함수 `where`와 람다 표현식을 조합하여 리스트에서 짝수만 필터링하는 예시도 자주 사용된다:

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

이처럼 람다 표현식과 고차 함수는 리스트와 같은 컬렉션을 쉽게 처리할 수 있는 도구이다.
