람다식

덤프버전 :



1. 개요
2. 특징
2.1. 장점
2.2. 단점
2.3. 유의사항
3. 예제


1. 개요[편집]


람다식, 또는 람다 함수라 부른다.
프로그래밍 언어에서 사용되는 개념으로, 익명 함수(匿名函數, Anonymous functions)를 지칭하는 용어이다.


2. 특징[편집]


프로그래밍 언어학적으로 파고들면 이것만 한 달 이상 배우는 경우도 많으며, 실제로 여러 대학들에서 사용하는 프로그래밍 언어 교재에서도 꽤나 많은 분량을 차지하는 개념이다. 물론 결점이 없는 개념이 결코 아니기에 람다식을 까는 논문 또한 해마다 나온다.

실무적으로는 코드를 간결하게 만들고, 지연 연산으로 성능을 높이고, 반복 관련 코드의 불필요한 부분들을 제거할 수 있으므로 중요하다. 람다식은 주로 고차 함수에 인자(argument)로 전달되거나 고차 함수가 돌려주는 결과값으로 쓰인다. 필요한 적재적소에 람다식을 투입하는 프로그래머는 효율과 결과 모두를 가져오는 훌륭한 프로그래머라고 볼 수 있다.

예제에 나오듯, 1부터 10까지 1씩 증가하면서 이 코드를 순차적으로 실행해라고 지시하는 것보다는, 여기 있는거 다 해라고 지시하는 것이 더욱 직관적이고 간결하다. 이러한 방식을 Tell, Don't Ask 원칙이라 하며, '묻지 않고 시키기'이다.


2.1. 장점[편집]


  • 코드의 간결성 - 효율적인 람다 함수의 사용을 통하여 불필요한 루프문의 삭제가 가능하며, 동일한 함수를 재활용할 수 있는 여지가 커진다.[1]
  • 필요한 정보만을 사용하는 방식을 통한 퍼포먼스 향상 - 지연 연산을 지원하는 방식[2]을 통하여 효율적인 퍼포먼스를 기대할 수 있다. 이 경우 메모리상의 효율성 및 불필요한 연산의 배제가 가능하다는 장점이 있다. 아래 예제에서 3과 5만 필요없는 경우를 생각해보자.


2.2. 단점[편집]


  • 어떤 방법으로 작성해도 모든 원소를 전부 순회하는 경우는 람다식이 조금 느릴 수밖에 없다. (어떤 방법으로 만들어도 최종 출력되는 bytecode나 어셈블리 코드는 단순 반복문보다 몇 단계를 더 거치게 된다.)
  • 익명함수의 특성상 함수 외부의 캡처를 위해 캡처를 하는 시간제약, 논리제약적인 요소도 고려해야 하며, 디버깅 시 함수 콜스택 추적이 극도로 어렵다.
  • 람다식을 남용하면 오히려 코드를 이해하기 어려울 수 있다. 따라서 람다식을 사용할 경우 주석을 다는 것이 권장된다.


2.3. 유의사항[편집]


모든 언어에서 제공되지는 않는다: 대부분의 유명한 언어들은 지원하지만, 지원하지 않는 언어도 가끔씩 있다. 특히 고전적인 문법들의 경우 거의 모든 언어에서 제공됨을 보장할 수 있는 부분과는 차별된다. 대표적으로 C, Fortran, Pascal 등이 지원하지 않는 언어. Java의 경우 8부터 지원하며, C++은 C++11부터 지원한다.

Microsoft .NET은 이미 Framework 2.0부터 대리자, 메서드 참조, 제너릭을 통해 비슷하게나마 지원하고 있었지만, 본격적으로 람다식이 지원되기 시작한 건 LINQ가 추가된 Framework 3.5부터이다. 사실상 람다식의 대유행을 야기한 장본인.[3] 물론 대부분은 굳이 람다식을 쓰지 않고도 사용할 수는 있다.

언어에 따라서는 람다식 로직의 일부를 재활용할 수 없는 경우도 있다. Java의 단말 연산(Terminal Operation)이 이에 해당하는데, 이런 언어에서는 단말 연산 수행 즉시 결과가 '닫히며', 그 이전의 중간 결과에서 람다식 연산을 다시 하려고 하면 오류가 발생한다. 따라서, 이런 언어에서는 미리 연산을 수행하여 중간 결과를 도출한 뒤, 그 결과에서 람다식을 다시 사용해야 한다. .NET에는 이런 제약이 덜하기에 람다식 로직의 재활용이 얼마든지 가능하다.


3. 예제[편집]


0부터 9까지의 숫자를 출력하는 코드를 각 언어로 설명하였다.

1. 전통적인 방법 - for문 등을 이용한 아주 기초적인 코드이다.
각각의 요소들을 하나하나 검증하며 순차적으로 값을 확인하여 조건절이 끝날 때까지 진행한다. 특별한 경우가 아니라면 최적화되지 않고 들어오는 순서대로 진행된다.참고 자료

2. 람다 함수를 사용한 방법
for문과 i등의 변수를 사용하는 방식과 다르게, 매번 같은 동작이 보장되어 병렬 처리가 보다 수월해진다.


3.1. C++[편집]


c++ 11부터 지원한다. [캡처 블록](매개변수) {표현식} 형태로 작성한다. 캡처는 복사(=)와 참조(&) 중 선택할 수 있으며, 전달할 변수마다 캡처 형식을 다르게 지정할 수 있다.

전통적인 방법
for (int i = 0; i < 10; i++) {
    std::cout << i;
}


람다식을 사용하여 만드는 방법
  • 람다식을 포인터
    std::function
    으로 참조할 수 있다.
std::array<int, 10> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::for_each(std::begin(v), std::end(v), [&](const int &i) { std::cout << i; });


  • 컴파일러의 추론을 통해 파라미터의 자료형을 생략한 코드(C++14부터 람다의 인자에
    auto
    사용이 가능해졌다.).
std::for_each(std::begin(v), std::end(v), [](auto n) { std::cout << n; });


  • 이 경우는 람다보다 "Range-based for loop" 가 가독성이 더 좋다.
for (auto n : v) std::cout << n;



3.2. C#[편집]


전통적인 방법
for (int i = 0; i < 10; i++)
{
    System.Console.Write(i);
}

람다식을 사용하여 만드는 방법
  • .NET Framework 3.5부터 지원되는 람다식을 명시한 코드
Enumerable.Range(0, 10).ToList().ForEach((int i) => System.Console.Write(i));


  • 위의 람다식을 컴파일러의 추론을 통해 파라미터의 자료형을 생략한 코드.
Enumerable.Range(0, 10).ToList().ForEach(i => System.Console.Write(i));


  • 델리게이트를 이용한 코드, 델리게이트는 2.0부터 지원하고 있다.
Enumerable.Range(0, 10).ToList().ForEach(System.Console.Write);



3.3. Go[편집]


전통적인 방법
for i := 0; i < 10; i++ {
    println(i)
}

람다식을 사용하여 만드는 방법
foreach := func(slice []int, f func(int)) {
    for _, i := range slice {
        f(i)
    }
}

foreach(
    []int{0,1,2,3,4,5,6,7,8,9},
    func(i int) { println(i) },
)

imperative한 방식(습관적 방법)보다 코드 길이가 더 길어진 건 Go가 함수형 라이브러리를 제공하지 않아 foreach를 임시변통했기 때문이다. 함수형 코드가 임페러티브형 코드보다 간결하고 직관적임에는 변함이 없다.


3.4. Haskell[편집]


람다식을 사용하여 만드는 방법
하스켈은 일반적인 상황에서 사용되는
map
외에도, 모나딕 함수 전용의
mapM
이 있다. 이는
map
을 모나딕 함수에 사용할시 결과의 타입이 모나딕 타입의 리스트
Monad m => [m a]
가 되기 때문이다.
mapM
은 리스트의 각 원소에 인자로 받은 함수를 적용한 결과를 순차적으로 bind (
>>=
) 하여 리스트의 모나드
Monad m => m [a]
를 만든다.

다음은
map
을 사용해 리스트의 각 원소에 1을 더하는 코드이다.
map (\x -> x+1) [0..9]

mapM
을 사용한 입출력은 다음과 같다.
mapM print [0..9]

그런데 IO출력에 사용되는
putStrLn
등 결과값이 의미를 가지지 않는 함수도 있다. 이처럼 결과값이 필요하지 않은 경우 일반적으로
mapM_
을 사용한다.
mapM_
의 결과의 타입은
Monad m => m ()
로 아무런 정보를 담고있지 않다.
mapM_ print [0..9]

mapM
mapM_
은 리스트 뿐만 아니라 각각 임의의
Traversable
Foldable
타입에 적용가능하다.

3.5. Java[편집]


전통적인 방법
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

람다식을 사용하여 만드는 방법
  • Java 8부터 지원되는 람다식을 사용한 코드
IntStream.range(0, 10).forEach((int value) -> System.out.println(value));


  • 컴파일러의 추론을 통해 파라미터의 자료형을 생략한 코드
IntStream.range(0, 10).forEach(value -> System.out.println(value));


  • 메서드 참조를 사용한 코드
IntStream.range(0, 10).forEach(System.out::println);



3.6. JavaScript[편집]


람다식이 아니라 화살표 함수(arrow function)라고 부른다.

전통적인 방법
for (let i = 0; i < 10; i++) {
    console.log(i);
}

람다식을 사용하여 만드는 방법
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(i => console.log(i));




3.7. Kotlin[편집]


전통적인 방법
for (i in 0 until 10) {
    println(i)
}

람다식을 사용하여 만드는 방법
(0 until 10).forEach { println(it) }

  • Kotlin에서는 Java 람다식에서 필요한 변수 이름을 생략할 수 있다. 생략시에 Kotlin은 자동으로 변수 이름을 "it"(그거)로 지정한다.


3.8. PHP[편집]


전통적인 방법 for 또는 foreach
for ($i = 0; $i < 10; $i++) {
    echo "$i\n";
}

//또는 foreach 를 사용
foreach (range(0, 9) as $x) {
    echo $x;
}



람다식을 사용하여 만드는 방법

array_map(fn($x) => print($x), range(0, 9));


map 함수 인 array_map을 이용한 코드

익명함수는 PHP 5.3에서, 화살표 함수는 PHP 7.4에서 도입되었다.


3.9. Python[편집]


전통적인 방법
for i in range(10):
    print(i)

람다식을 사용하여 만드는 방법
list(map(lambda x: print(x), range(0, 10)))
과 같이 map 함수를 사용하거나, [* lambda를 쓰지 않고 {{{ list(map(print, [1,2,3,4,5,6,7,8,9]))
}}}와 같이 써도 상관없다.]

[print(x) for x in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
과 같이 list comprehension[4]을 사용할 수도 있다.

[print(x) for x in range(10)]

range()를 이용해 이렇게 줄일 수도 있다. 0부터 시작하는 예제 특성상 아예 시작점을 뺄 수 있다.

물론, JavaScript와 비슷하게
print("\n".join(map(str, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])))
join()을 사용할 수도 있다.


3.10. Ruby[편집]


전통적인 방법
for x in 0...10
  puts x
end

람다식을 사용하여 만드는 방법
(0...10).each { |x| puts x }



3.11. Rust[편집]


전통적인 방법
for i in 0..10 {
    println!("{i}");
}

사실 for문을 사용하긴 했지만, 러스트에선 for이 내부적으로 이터레이터를 사용하는 문법적 설탕일 뿐이기에 진정한 의미의 c와 비슷한 for루프는 존재하지 않는다.

대신 while문을 사용할 수 있다.
let mut i = 0;

while i < 10 {
    println!("{i}");

    i += 1;
}

람다식을 사용하여 만드는 방법
(1..10).for_each(|i| println!("{i}"));
이 경우, |i| {...} 부분이 클로저(람다)이다.[5]

3.12. Scala[편집]


전통적인 방법
var i: Int = 0
while (i < 10) {
    println(i)
    i += 1
}

람다식을 사용하여 만드는 방법
(0 until 10) foreach println



3.13. Swift[편집]


전통적인 방법
for x in 0..<10 {
 print(x)
}

Swift에서는 람다 함수를 모두 클로저(Closure)라고 통칭한다.

클로저 사용:
(0...9).forEach({(i: Int) -> Void in
   print(i)
})

더 간결한 방법:
(0...9).forEach{print($0)}




파일:크리에이티브 커먼즈 라이선스__CC.png 이 문서의 내용 중 전체 또는 일부는 2023-11-25 04:54:57에 나무위키 람다식 문서에서 가져왔습니다.

[1] Java의 경우 Predicate절을 이용하여 조건을 넘기는 방식으로 재활용성을 극대화할 수 있다.[2] 스트리밍, 또는 언어에 따라서는 체인으로 부르기도 하는 방식[3] 람다식(익명 함수) 자체는 LISP에서도 사용된, 꽤 오래된 개념이다. LISP 는 그 자체가 함수형 언어이기도 하고, 현재 아주 메이저하게 사용되는 언어라고 보긴 무리가 다소 있지만 워낙 역사가 길어서 다른 언어에 미친 영향이 크다. 그러나 객체지향 언어나 스크립트 언어 등에서 적극적으로 람다를 사용하는 경향이 나타나게 된 것은 이 이후로 봐도 무방하다. [4] 정수 리스트 부분을 i for i in range(0, 10)으로 대체하면 직관성이 올라갈 것이다.[5] 참고로, 이 코드는 rustc 1.58버전 이상에서만 컴파일 된다. 그 이하의 버전에서는 println!("{}", i)와 같은 형태로 작성해야 하니 주의.