함수형 프로그래밍 언어
함수형 프로그래밍 언어는 함수형 프로그래밍 패러다임의 사용을 용이하게 하는 언어입니다. 간단히 말해서, 함수형 프로그래밍에 필요한 특성을 갖고 있다면 함수형 언어라고 부를 수 있습니다. 대부분의 경우 프로그래밍 스타일은 실제로 프로그램이 기능하는지 여부를 결정합니다.
언어를 기능적으로 만드는 것은 무엇입니까?
C언어에서는 함수형 프로그래밍을 구현할 수 없습니다. 함수형 프로그래밍은 Java로 구현할 수 없습니다(많은 해결 방법을 통해 함수형 프로그래밍에 근접한 프로그래밍 제외). 이러한 언어에는 함수형 프로그래밍을 지원하는 구문이 포함되어 있지 않습니다. 이는 순전히 객체 지향적이고 엄격하게 기능적이지 않은 언어입니다.
동시에 객체지향 프로그래밍은 Scheme, Haskell, Lisp와 같은 순수 함수형 언어에서는 사용할 수 없습니다.
그러나 일부 언어에서는 두 가지 모드를 모두 지원합니다. Python이 유명한 예이지만 Ruby, Julia, 그리고 가장 흥미롭게도 Javascript도 있습니다. 이 언어들은 이렇게 서로 다른 두 가지 디자인 패턴을 어떻게 지원합니까? 여기에는 두 프로그래밍 패러다임 모두에 필요한 기능이 포함되어 있습니다. 하지만 Javascript의 경우에는 기능적인 특징이 숨겨져 있는 것 같습니다.
그러나 실제로 기능적 언어에는 위보다 조금 더 많은 것이 필요합니다. 함수형 언어의 특징은 무엇입니까?
特点 | 命令式 | 函数式 |
---|---|---|
编程风格 | 一步一步地执行,并且要管理状态的变化 | 描述问题和和所需的数据变化以解决问题 |
状态变化 | 很重要 | 不存在 |
执行顺序 | 很重要 | 不太重要 |
主要的控制流 | 循环、条件、函数调用 | 函数调用和递归 |
主要的操作单元 | 结构体和类对象 | 函数作为一等公民的对象和数据集 |
기능적 언어의 구문은 유형 추론 시스템 및 익명 함수와 같은 특정 디자인 패턴을 고려해야 합니다. 일반적으로 언어는 람다 계산을 구현해야 합니다. 그리고 인터프리터의 평가 전략은 엄격하지 않은 요구 시 호출(지연 실행이라고도 함)이어야 하며, 이를 통해 불변 데이터 구조와 엄격하지 않고 지연 평가가 가능합니다.
译注:这一段用了一些函数式编程的专业词汇。lambda演算是一套函数推演的形式化系统(听起来很晕), 它的先决条件是内部函数和匿名函数。非严格求值和惰性求值差不多一个意思,就是并非严格地按照运算规则把所有元素先计算一遍, 而是根据最终的需求只计算有用的那一部分,比如我们要取有一百个元素的数组的前三项, 那惰性求值实际只会计算出一个具有三个元素是数组,而不会先去计算那个一百个元素的数组。
장점
함수형 프로그래밍을 마침내 마스터하면 큰 영감을 얻게 될 것입니다. 이러한 종류의 경험은 실제로 풀타임 기능 프로그래머가 되든 아니든 프로그래머로서의 미래 경력을 더 높은 수준으로 끌어올릴 것입니다.
하지만 지금 우리는 명상하는 법을 배우는 방법에 대해 이야기하는 것이 아닙니다. 우리는 당신을 더 나은 프로그래머로 만들어 줄 매우 유용한 도구를 배우는 방법에 대해 이야기하고 있습니다.
전반적으로 함수형 프로그래밍을 사용하면 실제로 어떤 이점이 있나요?
클리너 코드
함수형 프로그래밍이 더 깔끔하고, 더 간단하고, 더 작습니다. 디버깅, 테스트 및 유지 관리가 단순화됩니다.
예를 들어 2차원 배열을 1차원 배열로 변환하는 함수가 필요합니다. 명령형 기술만 사용한다면 다음과 같이 작성할 것입니다.
function merge2dArrayIntoOne(arrays) { var count = arrays.length; var merged = new Array(count); var c = 0; for (var i = 0; i < count; ++i) { for (var j = 0, jlen = arrays[i].length; j < jlen; ++j) { merged[c++] = arrays[i][j]; } } return merged }
이제 함수형 기법을 사용하면 다음과 같이 작성할 수 있습니다.
merge2dArrayIntoOne2 = (arrays) -> arrays.reduce (memo, item) -> memo.concat item , []
var merge2dArrayIntoOne2 = function(arrays) { return arrays.reduce( function(p,n){ return p.concat(n); }, []); };
译注:原著中代码有误,调用reduce函数时少了第二个参数空数组,这里已经补上。
두 함수 모두 동일한 입력을 받고 동일한 출력을 반환하지만 기능적 예가 더 깔끔합니다.
모듈형
함수형 프로그래밍을 사용하면 큰 문제를 작은 사례로 나누어 동일한 문제를 해결할 수 있습니다. 즉, 코드가 더욱 모듈화됩니다. 모듈식 프로그램은 설명이 더 명확하고 디버그하기 쉽고 유지 관리가 더 간단합니다. 또한 각 모듈의 코드가 올바른지 독립적으로 확인할 수 있으므로 테스트도 더 쉬워집니다.
재사용성
함수형 프로그래밍에는 모듈식 특성으로 인해 공통 보조 기능이 많이 있습니다. 여기에 있는 많은 기능을 다양한 응용 프로그램에서 재사용할 수 있다는 것을 알게 될 것입니다.
다음 장에서는 가장 일반적인 기능 중 다수를 다룰 것입니다. 그러나 함수형 프로그래머로서 당신은 필연적으로 계속해서 사용될 함수 라이브러리를 직접 작성하게 될 것입니다. 예를 들어, 행 사이의 구성 파일을 검색하는 데 사용되는 기능은 잘 설계된 경우 해시 테이블을 검색하는 데에도 사용할 수 있습니다.
커플링 줄이기
결합은 프로그램의 모듈 사이에 존재하는 수많은 종속성입니다. 함수형 프로그래밍은 전역 변수에 부작용이 없고 서로 완전히 독립적인 일급, 고차 순수 함수 작성을 따르기 때문에 결합이 크게 줄어듭니다. 물론, 기능은 필연적으로 서로 의존하지만 입력과 출력의 일대일 매핑이 올바르게 유지되는 한 하나의 기능을 변경해도 다른 기능에 영향을 미치지 않습니다.
수학적 정확성
마지막 요점은 좀 더 이론적입니다. 함수형 프로그래밍은 람다 미적분학에 뿌리를 두고 있기 때문에 수학적으로 정확하다는 것이 입증될 수 있습니다. 이는 성장률, 시간 복잡성 및 수학적 정확성을 증명하기 위한 프로그램이 필요한 일부 연구자들에게 큰 이점입니다.
피보나치 수열을 살펴보겠습니다. 개념 증명 문제 외에는 거의 사용되지 않지만 개념을 설명하는 좋은 방법입니다. 피보나치 수열을 평가하는 표준 방법은 다음과 같은 재귀 함수를 만드는 것입니다.
fibonnaci(n) = fibonnaci(n-2) + fibonnaci(n–1)
일반적인 상황도 추가해야 합니다.
return 1 when n < 2
이렇게 하면 재귀가 종료되고 재귀 호출 스택의 각 단계가 여기에서 누적될 수 있습니다.
자세한 단계는 다음과 같습니다
var fibonacci = function(n) { if (n < 2) { return 1; }else { return fibonacci(n - 2) + fibonacci(n - 1); } } console.log( fibonacci(8) ); // Output: 34
그러나 지연 실행 함수 라이브러리의 도움으로 전체 시퀀스의 멤버를 수학 방정식을 통해 정의하여 무한 시퀀스를 생성하는 것이 가능합니다. 최종적으로 필요한 멤버만 마지막에 계산됩니다.
var fibonacci2 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci2.length()); // Output: undefined console.log(fibonacci2.take(12).toArray()); // Output: [1, 1, 2, 3, 5,8, 13, 21, 34, 55, 89, 144] var fibonacci3 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci3.take(9).reverse().first(1).toArray()); //Output: [34]
第二个例子明显更有数学的味道。它依赖Lazy.js函数库。还有一些其它这样的库,比如Sloth.js、wu.js, 这些将在第三章里面讲到。
我插几句:后面这个懒执行的例子放这似乎仅仅是来秀一下函数式编程在数学正确性上的表现。 更让人奇怪的是作者还要把具有相同内部函数的懒加载写两遍,完全没意义啊…… 我觉得各位看官知道这是个懒执就行了,不必深究。
非函数式世界中的函数式编程
函数式和非函数式编程能混合在一起吗?尽管这是第七章的主题,但是在我们进一步学习之前, 还是要弄明白一些东西。
这本书并没要想要教你如何严格地用纯函数编程来实现整个应用。这样的应用在学术界之外不太适合。 相反,这本书是要教你如何在必要的命令式代码之上使用纯函数的设计策略。
例如,你需要在一段文本中找出头四个只含有字母的单词,稚嫩一些的写法会是这样:
var words = [], count = 0; text = myString.split(' '); for (i=0; count < 4, i < text.length; i++) { if (!text[i].match(/[0-9]/)) { words = words.concat(text[i]); count++; } } console.log(words);
函数式编程会写成这样:
var words = []; var words = myString.split(' ').filter(function(x){ return (! x.match(/[1-9]+/)); }).slice(0,4); console.log(words);
如果有一个函数式编程的工具库,代码可以进一步被简化:
함수를 보다 기능적인 방식으로 작성할 수 있는지 확인하는 방법은 이전 예의 "words" 및 "count" 변수와 같은 루프 및 임시 변수를 찾는 것입니다. 종종 루프와 임시 변수를 고차 함수로 대체할 수 있는데, 이에 대해서는 이 장의 뒷부분에서 살펴보겠습니다.
Javascript는 함수형 프로그래밍 언어인가요?
이제 우리 자신에게 물어봐야 할 마지막 질문이 있습니다. Javascript는 기능적 언어인가요, 아니면 비기능적 언어인가요?
Javascript는 틀림없이 세계에서 가장 인기가 있지만 가장 잘 이해되지 않는 함수형 프로그래밍 언어입니다. 자바스크립트는 C를 기반으로 한 함수형 프로그래밍 언어입니다. 구문은 확실히 C와 유사합니다. 즉, C의 블록 구문과 중위어 순서를 사용한다는 의미입니다. 그리고 그것은 살아있는 언어 중 최악의 이름을 가지고 있습니다. Javascript와 Java의 관계에 대해 얼마나 많은 사람들이 혼란스러워하는지 상상할 필요는 없습니다. 마치 그 이름이 그것이 무엇인지 암시하는 것처럼 말입니다! 그러나 실제로는 Java와 공통점이 거의 없습니다. 그러나 Javascript를 객체 지향 언어로 강제하는 몇 가지 아이디어는 여전히 있습니다. 예를 들어 Dojo 및 easy.js와 같은 라이브러리는 Javascript를 객체 지향 프로그래밍에 적합하게 추상화하려는 많은 작업을 수행했습니다. Javascript는 전 세계가 객체지향 프로그래밍을 요구하던 1990년대에 탄생했습니다. 우리는 Javascript가 객체지향 언어가 되기를 원했기 때문에 객체지향 언어라고 들었지만 실제로는 그렇지 않았습니다.
이 프로그램의 진정한 정체성은 두 가지 고전적인 함수형 프로그래밍 언어인 Scheme과 Lisp의 프로토타입으로 거슬러 올라갑니다. 자바스크립트는 언제나 함수형 프로그래밍 언어였습니다. 그 함수는 일급 시민이고 중첩될 수 있으며 클로저와 복합 함수를 가지며 합리화와 모나드를 허용합니다. 이 모든 것이 함수형 프로그래밍의 핵심입니다. Javascript가 함수형 언어인 이유는 다음과 같습니다.
• Javascript의 어휘에는 함수를 매개변수로 전달하는 기능이 포함되어 있으며 유형 추론 시스템이 있으며 익명 함수, 고차 함수, 클로저 등을 지원합니다. 이러한 특성은 함수형 프로그래밍을 구성하는 구조와 동작에 매우 중요합니다.
• Javascript는 순수한 객체지향 언어가 아닙니다. 객체지향 디자인 패턴의 대부분은 프로토타입 객체를 복사하여 완성됩니다. 이것은 약한 객체지향 프로그래밍 모델입니다. Javascript의 공식 형식이자 표준 구현인 유럽 컴퓨터 제조업체 협회 스크립트(ECMAScript)에는 사양 버전 4.2.1에 다음과 같은 설명이 있습니다.
"Javascript에는 C, Smalltalk 및 Java와 같은 실제 클래스가 없지만 객체 생성을 위한 생성자를 지원합니다. 일반적으로 클래스 기반 객체 지향 언어에서 상태는 인스턴스에 의해 전달되고 메소드는 클래스에 의해 전달되며 상속은 EMACScript에서는 상태와 메소드가 객체에 의해 전달되고 구조, 동작 및 상태가 상속됩니다.
즉, Javascript는 실제로 순수한 기능적 언어가 아닙니다. 지연 평가 및 내장된 불변 데이터가 부족합니다. 이는 대부분의 통역사가 요구에 따라 호출되기보다는 이름으로 호출되기 때문입니다. Javascript는 tail call을 처리하는 방식으로 인해 재귀를 처리하는 데에도 능숙하지 않습니다. 그러나 이러한 모든 문제는 몇 가지 사소한 고려 사항을 통해 완화될 수 있습니다. 무한 시퀀스와 지연 평가가 필요한 비엄격 평가는 Lazy.js라는 라이브러리를 통해 달성할 수 있습니다. 불변성은 단순히 프로그래밍 기술을 통해 달성할 수 있지만 언어 수준에 의존하지 않고 프로그래머의 자기 훈련이 필요합니다. 꼬리 재귀 제거는 Trampolining이라는 방법을 통해 달성할 수 있습니다. 이러한 문제는 6장에서 설명됩니다.
Javascript가 함수형 언어인지, 객체 지향 언어인지, 둘 다인지 아니면 둘 다인지에 대한 많은 논쟁이 있어 왔으며 이러한 논쟁은 앞으로도 계속될 것입니다.
마지막으로 함수형 프로그래밍은 기발한 변경, 조합, 함수 사용을 통해 간결한 코드를 작성하는 방법입니다. 그리고 Javascript는 이를 달성하는 좋은 방법을 제공합니다. Javascript의 잠재력을 최대한 활용하고 싶다면 Javascript를 기능적 언어로 사용하는 방법을 배워야 합니다.