* [[컴퓨터 관련 정보]] [목차] == 개요 == 람다식, 또는 람다 함수는 [[프로그래밍 언어]]에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어이다. 프로그래밍 언어학적으로 파고들면 이것만 한 달 이상 배우는 경우도 많으며, 실제로 여러 [[대학]]들에서 사용하는 [[프로그래밍 언어]] 교재에서도 꽤나 많은 분량을 차지하는 개념이다. 실무적으로는 코드의 간결함, 지연 연산을 통한 퍼포먼스 향상, 그리고 기존 이터레이션 관련 코드를 구현하는 데 있어 불필요한 부분들을 제거할 수 있다는 점에서 비교적 중요하게 다루어지고 있다. 람다식은 주로 [[고차 함수]]에 인자(argument)로 전달되거나 [[고차 함수]]가 돌려주는 결과값으로 쓰인다. == 장점 == * 코드의 간결성 - 효율적인 람다 함수의 사용을 통하여 불필요한 루프문의 삭제가 가능하며, 동일한 함수를 재활용할 수 있는 여지가 커진다.[* [[Java]]의 경우 Predicate절을 이용하여 조건을 넘기는 방식으로 재활용성을 극대화할 수 있다.] * 필요한 정보만을 사용하는 방식을 통한 퍼포먼스 향상 - 지연 연산을 지원하는 방식[* 스트리밍, 또는 언어에 따라서는 체인으로 부르기도 하는 방식]을 통하여 효율적인 퍼포먼스를 기대할 수 있다. 이 경우 메모리상의 효율성 및 불필요한 연산의 배제가 가능하다는 장점이 있다. == 단점 == 1. 어떤 방법으로 작성해도 모든 원소를 전부 순회하는 경우는 람다식이 조금 느릴 수밖에 없다. (어떤 방법으로 만들어도 최종 출력되는 bytecode나 어셈블리 코드는 단순 반복문보다 몇 단계를 더 거치게 된다.) 1. 익명함수의 특성상 함수 외부의 캡처를 위해 캡처를 하는 시간제약, 논리제약적인 요소도 고려해야 하며, 디버깅 시 함수 콜스택 추적이 극도로 어렵다. 1. 람다식을 남용하면 오히려 코드를 이해하기 어려울 수 있다. == 유의사항 == 모든 언어에서 제공되지는 않는다: 대부분의 유명한 언어들은 지원하지만, 지원하지 않는 언어도 가끔씩 있다. 특히 고전적인 문법들의 경우 거의 모든 언어에서 제공됨을 보장할 수 있는 부분과는 차별된다. 대표적으로 [[C(프로그래밍 언어)|C]], [[포트란|Fortran]], [[Pascal]] 등이 지원하지 않는 언어. [[Java]]의 경우 8부터 지원하며, [[C++]]은 C++11부터 지원한다. .NET Framework는 이미 2.0부터 대리자, 메서드 참조, 제너릭을 통해 비슷하게나마 지원하고 있었지만, 본격적으로 람다식이 지원되기 시작한 건 LINQ가 추가된 3.5부터이다. 그래봤자 대부분은 굳이 람다식을 쓰지 않고도 사용할 수 있기에 큰 의미는 없다. == 예제 == === 습관적 방법 === 기존의 전통적인, 또는 기초 프로그래밍에서 흔히 볼 수 있는 문법을 이용한 코드로, for문을 이용한 아주 기초적인 코드이나 이는 [[http://java.dzone.com/articles/why-we-need-lambda-expressions|해당 글에서 언급하듯이]] 각각의 요소들을 하나하나 일일이 검증하며 순차적으로 값을 확인하여 조건절이 끝날 때까지 진행되고 있으며, 이러한 코드는 특별한 경우가 아니라면 최적화되지 않고 들어오는 순서대로 진행된다. 그러나 빨리 끝나는 일을 먼저 하거나 한번에 여러 가지 일을 하는 것이 당연히 효율적이며 짧은 시간 내에 작업을 끝낼 수 있다. 또한 '''1부터 10까지 1씩 증가하면서 이 코드를 순차적으로 실행해'''라고 '''명령'''하는 것보다는 '''여기 있는거 다 해'''라고 '''설명'''하는것이 더욱 직관적이며 더 간결하다. 이러한 방식을 [[https://pragprog.com/articles/tell-dont-ask|Tell, Don't Ask 원칙]]이라고 한다. 아래 예제는 모두 1~10이 아닌 0~9를 대상으로 했다는 점에 유의할 것. ==== [[Java]] ==== {{{#!syntax java for (int i = 0; i < 10; i++) { System.out.println(i); } }}} ==== [[Scala]] ==== {{{ var i: Int = 0 while (i < 10) { println(i) i += 1 } }}} ==== [[C(프로그래밍 언어)|C]] ==== {{{#!syntax cpp for (int i = 0; i < 10; i++) { //C99표준부터 for문안에서 선언이 가능하다 printf("%d", i); } }}} ==== [[C++]] ==== {{{#!syntax cpp for (int i = 0; i < 10; i++) { std::cout << i; } }}} ==== [[C\#|C#]] ==== {{{#!syntax csharp for (int i = 0; i < 10; i++) { System.Console.Write(i); } }}} ==== [[Go(프로그래밍 언어)|Go]] ==== {{{#!syntax go for i := 0; i < 10; i++ { println(i) } }}} ==== [[JavaScript]] ==== {{{#!syntax javascript for (let i = 0; i < 10; i++) { console.log(i); } }}} ==== [[Python]] ==== {{{#!syntax python for i in range(10): print(i) }}} ==== [[PHP]] ==== {{{#!syntax php for ($i = 0; $i < 10; $i++) { echo $i ; } }}} ==== [[Swift(프로그래밍 언어)|Swift]] ==== {{{ for x in 0..<10 { print(x) } }}} ==== [[Ruby]] ==== {{{#!syntax ruby for x in 0...10 puts x end }}} ==== [[Kotlin]] ==== {{{ for (i in 0 until 10) { println(i) } }}} === 더 나은 방법 === [[https://pragprog.com/articles/tell-dont-ask|Tell, Don't Ask 원칙]]에 따라 람다식으로 재작성한 코드로, 이렇게 할 경우 기초적인 수준에서 생기는 장점은 for문과는 달리 개념적으로 설명이 단순하여 이해가 빠르다는 점이며, 여기서는 잘 드러나지 않지만 복잡한 프로그래밍을 할 때 코드가 간결해지는 장점이 있다. ==== [[Java]] ==== * Java 8부터 지원되는 람다식을 사용한 코드 {{{#!syntax java IntStream.range(0, 10).forEach((int value) -> System.out.println(value)); }}} * 컴파일러의 추론을 통해 파라미터의 자료형을 생략한 코드 {{{#!syntax java IntStream.range(0, 10).forEach(value -> System.out.println(value)); }}} * 메서드 참조를 사용한 코드 {{{#!syntax java IntStream.range(0, 10).forEach(System.out::println); }}} ==== [[Scala]] ==== {{{ (0 until 10) foreach println }}} ==== [[C++]] ==== * C++11부터 지원한다. * 람다식을 [[포인터]]나 {{{std::function}}}으로 참조할 수 있다. {{{#!syntax cpp std::vector<int> 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}}} 사용이 가능해졌다.). {{{#!syntax cpp std::for_each(v.begin(), v.end(), [](auto n) { std::cout << n; }); }}} * 이 경우는 람다보다 "Range-based for loop" 가 가독성이 더 좋다. {{{#!syntax cpp for (auto n : v) std::cout << n; }}} ==== [[JavaScript]] ==== {{{#!syntax javascript [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(i => console.log(i)); }}} javascript는 java와 다르게 forEach의 첫번째 parameter로 실행함수를 넘겨주고 그 함수를 실행하는 방식인데 넘겨준 함수가 단순히 i를 받아서 i를 출력하므로 console.log라는 변수안에 담긴 함수와 하는일이 같다는 점을 이용하여 더 간결하게 줄인 방식 {{{#!syntax javascript [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(console.log); }}} ==== [[C\#|C#]] ==== * .NET Framework 3.5부터 지원되는 람다식을 명시한 코드 {{{#!syntax csharp Enumerable.Range(0, 10).ToList().ForEach((int i) => System.Console.Write(i)); }}} * 위의 람다식을 컴파일러의 추론을 통해 파라미터의 자료형을 생략한 코드. {{{#!syntax csharp Enumerable.Range(0, 10).ToList().ForEach(i => System.Console.Write(i)); }}} * 델리게이트를 이용한 코드, 델리게이트는 2.0부터 지원하고 있다. {{{#!syntax csharp Enumerable.Range(0, 10).ToList().ForEach(System.Console.Write); }}} ==== [[Go(프로그래밍 언어)|Go]] ==== {{{#!syntax go 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(프로그래밍 언어)|Go]]가 함수형 라이브러리를 제공하지 않아 foreach를 임시변통했기 때문이다. 함수형 코드가 임페러티브형 코드보다 간결하고 직관적임에는 변함이 없다. ==== [[Python]] ==== {{{#!syntax python list(map(lambda x: print(x), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) }}} 과 같이 map 함수를 사용하거나, [* lambda를 쓰지 않고 {{{list(map(print, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))}}}와 같이 써도 상관없다.] {{{#!syntax python [print(x) for x in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] }}} 과 같이 list comprehension을 사용할 수도 있다. {{{#!syntax python [print(x) for x in range(0, 10)] }}} range()를 이용해 이렇게 줄일 수도 있다. 물론, [[JavaScript]]와 비슷하게, {{{#!syntax python print("\n".join(str(x) for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) }}} join()을 사용할 수도 있다. list comprehension은 str 형으로 변환하는 데 필요하므로 기억해두자. ==== [[PHP]] ==== {{{#!syntax php foreach (range(0, 9) as $number) { echo $number; } // PHP 5.3 부터 }}} {{{#!syntax php (function(){ foreach (range(0, 9) as $number) { echo $number; } })(); // PHP 7.0 부터 가능 }}} range()로 0에서 9까지 배열을 만들어서 foreach문으로 돌면된다 foreach와는 별개로 익명함수는 PHP 5.3에서 도능 화살표함수는 => PHP 7.4에서 도입되었다. ==== [[Swift(프로그래밍 언어)|Swift]] ==== 클로저 사용: {{{ (0...9).forEach({(i: Int) -> Void in print(i) }) }}} 더 간결한 방법: {{{ (0...9).forEach{print($0)} }}} ==== [[Ruby]] ==== {{{#!syntax ruby (0...10).each { |x| puts x } }}} ==== [[Kotlin]] ==== {{{ (0 until 10).forEach { println(it) } }}} ==== [[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}}} 타입에 적용가능하다. [[분류:프로그래밍 언어 문법]]