Functional Programming Concepts

choonghee-lee·2020년 8월 11일
0

WeCode

목록 보기
17/20

서론

Functional Programming(이하 FP) 이 뭔지 모른다고 하더라도, 우리는 이미, 예를 들어, 자바스크립트의 map(), reduce()와 같은 기능을 통해 FP를 하고 있다. 오늘은 FP의 주요 컨셉과 예제를 통해 알아본다.

First-Class

First-Class 를 가장 간단히 말하자면, 변수의 기능을 함수도 똑같이 할 수 있다는 의미이다. 물론 이것만 보면 무슨 소리인지 모르기 때문에 예제를 살펴봐야한다 ㅋㅋ 😼. 아래는 First-Class 함수가 되기 위한 조건이다.

1. 함수를 변수에 할당 할 수 있다.

var log = function(message) {
  console.log(message);
};

2. 다른 함수의 인자로 넘겨줄 수 있다.

const insideFn = logger => {
  logger("They can be sent to other functions as arguments");
};

insideFn(message => console.log(message))

3. 다른 함수의 리턴값이 될 수 있다.

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

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

변경되지 않는 성질을 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

순수 함수 (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를 발생시키지 않는다.

Data Transformations

immutable로 취급되는 데이터를 어떻게 변경할 수 있을지 의문이 들것이다 🤔. FP는 데이터를 하나의 형태에서 다른 형태로 변경하는 것이 전부라고 봐도 무방하다. 우리는 함수를 이용하여 변형된 복사본을 만들어 낼 수 있다.

const schools = ["Yorktown", "Washington & Liberty", "Wakefield"];

console.log(schools.join(", "));

Array.join()은 오리지널 데이터를 변경하지 않는 함수이며, 디테일한 구현을 들여다보지 않아도 함수명에서 기능을 파악 할 수 있다. Array.filter(), Array.map() 같은 함수들도 마찬가지이다.

Higher-Order Functions

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

Recursion(재귀) 은 자기 자신을 호출하는 함수이다. FP에 대해 검색해 볼 수준이면 재귀에 대해 많이 들어봤을테니 예제만 간단히 적도로 하겠다.

const countdown = (value, fn) => {
	fn(value);
	return value > 0 ? countdown(value - 1, fn) : value;
};

countdown(10, value => console.log(value));

Composition

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를 발생시키지 않으며, 하나의 기능만 수행하는 함수를 만드는 일이 쉬워졌으면 좋겠다. 또 내공을 쌓으러 이만...

profile
뭐든지 열심히하는 타입 😎

0개의 댓글