Functional Programming(이하 FP) 이 뭔지 모른다고 하더라도, 우리는 이미, 예를 들어, 자바스크립트의 map()
, reduce()
와 같은 기능을 통해 FP를 하고 있다. 오늘은 FP의 주요 컨셉과 예제를 통해 알아본다.
First-Class 를 가장 간단히 말하자면, 변수의 기능을 함수도 똑같이 할 수 있다는 의미이다. 물론 이것만 보면 무슨 소리인지 모르기 때문에 예제를 살펴봐야한다 ㅋㅋ 😼. 아래는 First-Class 함수가 되기 위한 조건이다.
var log = function(message) {
console.log(message);
};
const insideFn = logger => {
logger("They can be sent to other functions as arguments");
};
insideFn(message => console.log(message))
const createScream = function(logger) {
return function(message) {
logger(message.toUpperCase() + "!!!");
};
};
const scream = createScream(message => console.log(message));
scream("functions can be returned from other functions");
제목과 코드만 보면 이해할 수 있는 정도라 따로 설명을 달아두지는 않는다!
Declarative Programming 이란, "How it should happen" 보다는 "What should happen" 을 우선적으로 생각하여 코딩을 하는 프로그래밍 스타일이다.
const loadAndMapMembers = compose(
combineWith(sessionStorage, "members"),
save(sessionStorage, "members"),
scopeMembers(window),
logMemberInfoToConsole,
logFieldsToConsole("name.first"),
countMembersBy("location.state"),
prepStatesForMapping,
save(sessionStorage, "map"),
renderUSMap
);
영어를 잘 읽어보면 알겠지만 (영어와 친하지 않은 사람들에게는 미안한 말이지만 개발자에게 영어에 대한 선택권은 없는 것 같다), 코드의 흐름을 이해하기 쉽다는 장점이 있고, 읽으면 이해가 되기 때문에 굳이 내부 로직을 들여다 볼 필요가 없다. 이것을 함수로 이룰 수 있기 때문에 FP에서 중요한 컨셉이다.
Declarative Programming의 반대격을 찾으라면 Imperative Programming 스타일이 있다. 이 부분은 안 읽어도 된다.
const string = "Restaurants in Hanalei";
const urlFriendly = "";
for (var i = 0; i < string.length; i++) {
if (string[i] === " ") {
urlFriendly += "-";
} else {
urlFriendly += string[i];
}
}
console.log(urlFriendly); // "Restaurants-in-Hanalei"
위의 프로그래밍 구조는 오직 결과만을 위해 작성되었다. 바로 이해하기 힘드며, 코드를 해석해야만 한다.
변경되지 않는 성질을 Immutability라고 한다. FP에서 데이터는 절대로 변하지 않는다. 다음은 Immutablility를 지키지 않는 상황이다.
let color_lawn = {
title: "lawn",
color: "#00FF00",
rating: 0
};
function rateColor(color, rating) {
color.rating = rating;
return color;
}
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 5
자바스크립트같은 프로그래밍 언어들은 오리지널 데이터를 변경시키는 것 대신, 데이터의 복사본을 만들어 그것을 수정하는 함수 또는 메서드들을 제공한다. 위의 예제를 Immutable하게 변경시켜보면,
const rateColor = function(color, rating) {
return Object.assign({}, color, { rating: rating });
};
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 0
Object.assign()
은 복사 기능을 한다. 위의 예제에서 empty 객체를 받고, color 인자를 복사한 후 해당 복사본에 rating을 덮어씌우고 있다. 이제 오리지널 데이터를 변경시키는 일을 막을 수 있다.
순수 함수 (Pure Functions) 는 항상 최소 하나 이상의 인자를 받으며, 항상 하나의 값 또는 함수를 리턴하는 함수를 말한다. 글로벌 변수나 어플리케이션의 상태를 변경하는 일이 없으며, 인자를 위에서 말한 immutable 로 취급한다. 아래는 순수 함수의 예시이다.
const frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
const selfEducate = person => ({
...person,
canRead: true,
canWrite: true
});
console.log(selfEducate(frederick));
console.log(frederick);
위의 예제는 앞서 언급한대로 순수 함수이다. 하나의 인자를 받으며, 새로운 객체를 기존의 인자의 변경없이 리턴하며, 다른 side-effect를 발생시키지 않는다.
immutable로 취급되는 데이터를 어떻게 변경할 수 있을지 의문이 들것이다 🤔. FP는 데이터를 하나의 형태에서 다른 형태로 변경하는 것이 전부라고 봐도 무방하다. 우리는 함수를 이용하여 변형된 복사본을 만들어 낼 수 있다.
const schools = ["Yorktown", "Washington & Liberty", "Wakefield"];
console.log(schools.join(", "));
Array.join()
은 오리지널 데이터를 변경하지 않는 함수이며, 디테일한 구현을 들여다보지 않아도 함수명에서 기능을 파악 할 수 있다. Array.filter()
, Array.map()
같은 함수들도 마찬가지이다.
Higher-Order Functions은 다른 함수들을 조작할 수 있는 함수이다. 함수를 인자로 받으며, 함수를 리턴한다.
const userLogs = userName => message =>
console.log(`${userName} -> ${message}`);
const log = userLogs("grandpa23");
log("attempted to load 20 fake members");
getFakeMembers(20).then(
members => log(`successfully loaded ${members.length} members`),
error => log("encountered an error loading members")
);
userLogs
함수는 내부의 함수 message => console.log(`${userName} -> ${message}`);
를 리턴하는 함수이다. 이것을 "grandpa23" 이라는 값과 함께 호출하여 내부 함수를 log
변수에 넣어주었다. 그리고 Promise의 then
함수안에서 log
변수에 있는 함수를 호출하였다. 그러면 다음과 같은 결과가 나온다.
// grandpa23 -> attempted to load 20 fake members
// grandpa23 -> successfully loaded 20 members
Recursion(재귀) 은 자기 자신을 호출하는 함수이다. FP에 대해 검색해 볼 수준이면 재귀에 대해 많이 들어봤을테니 예제만 간단히 적도로 하겠다.
const countdown = (value, fn) => {
fn(value);
return value > 0 ? countdown(value - 1, fn) : value;
};
countdown(10, value => console.log(value));
FP는 큰 흐름을 작은 로직으로 나누고, 특정 작업만 수행하는 순수 함수들을 만드는 일이다. 이런 작은 함수들을 모아서 사용하는 것이 Composition 이다. 이런 함수들을 합치고 합쳐서 애플리케이션을 만들 수 있는 것이다. 여러가지 테크닉이 있지만, 가장 많이 본 것은 함수 chainning 일 것 이다.
const template = "hh:mm:ss tt";
const clockTime = template
.replace("hh", "03")
.replace("mm", "33")
.replace("ss", "33")
.replace("tt", "PM");
console.log(clockTime);
프로그래밍 할 때, 이런 것들을 직접적으로 생각하며 작성하는 사람이 얼마나 될까 궁금하다. 나는 용어 정리 차원에서 이 글을 포스팅했다. 다른 것들도 중요하겠지만, 특정 작업만 수행하는 순수 함수 를 만드는게 가장 중요해 보인다. 다른 side-effect를 발생시키지 않으며, 하나의 기능만 수행하는 함수를 만드는 일이 쉬워졌으면 좋겠다. 또 내공을 쌓으러 이만...