함수형 VS 명령형
특성 | 함수형 접근 방식 | 명령형 접근방식 |
프로그래머 관심사항 | 원하는 정보가 무엇인가 어떤 변환이 필요한가 |
작업을 수행하는 방법(알고리즘) 상태변경 추적 |
상태(값 변경) | 없음(값을 복사) | 상태값 참조가 중요 |
실행절차 | 중요도 낮음 | 중요도 높음 |
제어 흐름 | 재귀를 비롯한 함수 호출 | 반복문, 조건문, 함수 호출 |
구현단위 | 일급 객체와 데이터 콜렉션으로 함수 사용 | 구조체 또는 클래스 |
함수형 프로그래밍
함수형 프로그래밍은, 부수효과를 멀리하고 가급적 순수함수를 만들어 1급함수의 특징들을 이용해서 함수들을 인자과 반환값으로 사용하는 활용을 통해서 단순하고 결함이 적은 단방향성의 파이프라인을 함수들의 조립을 통해 구축한다는 개념입니다.
순수함수
cf.수학에서의 함수 정의- 함수 y = f(x)는, 어떤 입력값 x에 대해 항상 동일한 출력값 y를 가진다.
정의
수학에서의 함수 개념을 프로그래밍으로 옮긴것
동일 입력 시 동일 출력을 보장하며, 부수 효과가 없습니다
순수함수가 아닌 것
순수 함수라면 동일 입력 시 동일 출력을 보장해야 합니다. 하지만 globalVariable
이라는 변수의 변화에 영향을 받고 있습니다.
이처럼 함수의 반환값이 아닌 외부 상태에 영향을 주거나 받는 아래의 함수들은 순수 함수가 아닙니다. 이런 상황은 전역 변수 뿐만 아니라 파일 불러오기, 네트워크 fetching, 예외 처리 등의 경우에도 생길 수 있습니다.
//impureFunction에 대함 입력값(1,2)에 대해 동일한 출력값이 나오지 않고 있다.
let globalVariable = 10;
const impureFunction = (x: number, y: number) => x + y + globalVariable;
function main() {
console.log(impureFunction(1, 2)); // 13
globalVariable = 20;
console.log(impureFunction(1, 2)); // 23
}
main();
let globalVariable = 10;
const impureFunction = (x: number, y: number) => {
globalVariable = y;
return x + y;
};
function main() {
console.log(impureFunction(1, 2)); // 3
console.log(impureFunction(1, 2)); // 3
// 둘의 결과는 같으나, globalVariable의 값이 변경됨.
}
main();
불변성 Immutable
심볼의 값이 변경되지 않는다
가변 변수를 사용하는 대신에 심볼에 값을 할당하면 그 값은 변경되지 않는다
함수형 프로그래밍의 특징
순수 함수로 만들면 함수 외부에 값이나 객체를 참조하거나 의존적으로 동작하지 않기 때문에 참조투명성을 가지고, 부작용이 없다.
반대로 말하면 부작용이 있는 함수는 입력 값이 동일해도 함수 외부에 값에 따라서 다른 값이 리턴한다.
불변성 (Immutable)
정의
- 어떤 값의 상태를(메모리에 이미 담긴 상태를) 변경하지 않는다
- 상태의 변경은 부수 효과를 일으키기 때문에, 함수형 프로그래밍에서는 이를 제한
불변성을 지키지 않는 예시
상태가 변경되어 불변성을 지키지 않고 있습니다.
// henry.age 가 변경됨
interface Person {
name: string;
age: number;
}
function addAge(person: Person) {
person.age += 1;
return person;
}
function main() {
const henry: Person = {
name: '남현욱',
age: 20,
};
console.log(henry); // { name: '남현욱', age: 20 }
addAge(henry);
console.log(henry); // { name: '남현욱', age: 21 }
}
main();
불변성을 지키는 예시:
코드는 값을 변경하는 대신 새로운 객체를 만들어 반환하여 불변성을 유지
덕분에 부수 효과가 사라졌음
interface Person {
readonly name: string;
readonly age: number;
}
function addAge(person: Person) {
const newPerson: Person = {
...person,
age: person.age + 1,
};
return newPerson;
}
function main() {
const henry: Person = {
name: '남현욱',
age: 20,
};
const henry2 = addAge(henry);
console.log(henry); // { name: '남현욱', age: 20 }
console.log(henry2); // { name: '남현욱', age: 21 }
console.log(henry); // { name: '남현욱', age: 20 }
}
main();
참조 투명성 (Referential Transparency)
정의 :
- 프로그램의 변경 없이도 어떤 표현식을 값으로 대체할 수 있다
- 즉 함수 f(x)가 y를 반환할 때, f(x)는 y로 대체할 수 있습니다.
참조 투명성 x 예시
hello()
함수는 console.log()
및 someName
이라는 외부의 값들을 참조하고 있어서 참조에 투명하지 않습니다. someName
이 바뀌면 hello()
의 값도 자연스레 변경되기 때문입니다.
const someName = 'Henry';
function hello() {
console.log(`Hello, ${someName}.`);
}
참조 투명성 O 예시
hello()
함수가 항상 일관적으로 반환해서, 참조에 투명한 함수가 되었습니다.
function hello(name) {
return `Hello, ${name}.`;
}
function main() {
const helloString = hello('Henry');
console.log(helloString);
}
main();
일급 함수 (First-class Function)
프로그래밍 언어에서 어떠한 대상이 일급 시민이 되는 조건:
- 대상을 함수의 매개변수로 넘길 수 있다.
- 대상을 함수의 반환값으로 돌려줄 수 있다.
- 대상을 변수나 자료구조에 담을 수 있다.
일급 함수의 조건(대상 → 함수)
- 함수를 함수의 매개변수로 넘길 수 있다.
- 함수를 함수의 반환값으로 돌려줄 수 있다.
- 함수를 변수나 자료구조에 담을 수 있다. 즉, 함수를 타입으로 지정할 수 있다
예시
type Func = (par: number) => string;
function canBeParameter(func: Func) {
// do Something
}
function canReturn(): Func {
return (par) => par.toString();
}
const arr: Func[] = [
(par) => par.toString(),
];
게으른 평가 (Lazy Evaluation)
Lazy Evaluation 정의: 값이 필요한 시점에 평가
메모리에 접근할 때 계산
ex)
print_length([1+1, 3*2])
0번째 값에 접근할 때 계산
Eager Evaluation 정의: 코드의 실행 즉시 값을 평가
일반적인 언어는 코드의 실행 즉시 값을 평가(Eager Evaluation)하지만 함수형 언어에서는 값이 필요한 시점에 평가(Lazy Evaluation)됩니다. 값이 실제로 필요한 시점까지 실행하지 않기 때문에 시간이 오래 걸리는 작업도 손쉽게 동작시킬 수 있습니다.
예시
값이 필요한 시점에 평가되기 때문에, 이렇게 무한대의 값도 간단히 표현할 수 있습니다.
function* infiniteValue(acc = 0) {
yield acc;
yield* infiniteValue(acc + 1);
}
const iter = infiniteValue();
console.log(iter.next().value); // 0
console.log(iter.next().value); // 1
console.log(iter.next().value); // 2
console.log(iter.next().value); // 3
console.log(iter.next().value); // 4
클로저(Closure)
‼️ 함수형에서 바람직한 방법은 아니다.
정의
<바깥의 것을 참조하는 함수>
자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는 함수
즉 외부함수가 이미 반환되었어도 외부함수 내의 변수는 이를 필요로 하는 내부함수가 하나 이상 존재하는 경우 계속 유지된다. 이때 내부함수가 외부함수에 있는 변수의 복사본이 아니라 실제 변수에 접근한다는 것에 주의하여야 한다.
MDN 정의: 클로저는 자신이 생성될 때의 환경(Lexical environment)을 기억하는 함수다
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
렉시컬 스코핑(Lexical scoping): 스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다.
“그 함수가 선언될 때의 렉시컬 환경(Lexical environment)”: 내부 함수가 선언됐을 때의 스코프를 의미한다.
“함수”: 반환된 내부함수
Context 로 알아보기
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
innerFunc();
}
outerFunc(); // 10
내부함수 innerFunc가 호출되면 자신의 실행 컨텍스트가 실행 컨텍스트 스택에 쌓이고
→ 변수 객체(Variable Object)와 스코프 체인(Scope chain) 그리고 this에 바인딩할 객체가 결정된다. 이때 스코프 체인은 전역 스코프를 가리키는 전역 객체와 함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체(Activation object) 그리고 함수 자신의 스코프를 가리키는 활성 객체를 순차적으로 바인딩한다.
- innerFunc 함수 스코프(함수 자신의 스코프를 가리키는 활성 객체) 내에서 변수 x를 검색한다. 검색이 실패하였다.
- innerFunc 함수를 포함하는 외부 함수 outerFunc의 스코프(함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체)에서 변수 x를 검색한다. 검색이 성공하였다.
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
return innerFunc;
}
/**
* 함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
* 그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
*/
var inner = outerFunc();
inner(); // 10
함수 outerFunc는 내부함수 innerFunc를 반환하고 생을 마감했다.
즉, 함수 outerFunc는 실행된 이후 콜스택(실행 컨텍스트 스택)에서 제거되었으므로 함수 outerFunc의 변수 x 또한 더이상 유효하지 않게 되어 변수 x에 접근할 수 있는 방법은 달리 없어 보인다. 그러나 위 코드의 실행 결과는 변수 x의 값인 10이다.
이미 life-cycle이 종료되어 실행 컨텍스트 스택에서 제거된 함수 outerFunc의 지역변수 x가 다시 부활이라도 한 듯이 동작하고 있다.
이처럼 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데 이러한 함수를 클로저(Closure)라고 부른다.
클로저의 사용
클로저는 자신이 생성될 때의 환경(Lexical environment)을 기억해야 하므로 메모리 차원에서 손해를 볼 수 있다. 하지만 클로저는 자바스크립트의 강력한 기능으로 이를 적극적으로 사용해야 한다.
- 상태유지 : 클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다.
- 전역 변수의 사용 억제
- 정보의 은닉
함수형 프로그래밍의 장점
1. 예측 가능한 코드
함수형 프로그래밍에서는 부수 효과들을 순수한 영역들과 최대한 분리
- 부수 효과가 생기는 영역과 순수한 영역을 최대한 분리하고 참조에 투명하도록 구성
- → 예측 가능한 코드가 되어 버그가 생길 확률이 줄고 더 안전한 프로그램을 만들 수 있습니다.
2. 재사용성
- 일급 함수 덕분에, 기능 추가/수정 시 관련이 없는 모듈을 수정할 필요가 없고, 인터페이스를 통해 쉽게 확장할 수 있습니다
- 클래스나 함수들이 하나의 일만 하기 때문에 재사용성이 높아지며,
- 의존성 주입을 통해 테스트도 수월하게 할 수 있습니다.
Reference
[함수형 프로그래밍, 순수 함수의 정의]
[함수형 프로그래밍 영상]
[immutable]
[클로져]
[js parameter명시]
jsDoc 사용하기 & 사용 예시(param, typeDef, enum 등)
[Currying]
'javascript' 카테고리의 다른 글
node.js .env 사용하기 (0) | 2023.09.07 |
---|---|
for each for of 차이 (0) | 2023.05.23 |