함수형 프로그래밍을 배워보자!

teo.yu·2023년 1월 13일
84

테오의 프론트엔드

목록 보기
36/45
post-thumbnail

함수형 프로그래밍을 어떻게 시작해야할지 모르겠어요!

프롤로그

평소에 관심이 많은 함수형 프로그래밍과 함께 사실 저는 한번도 해보지 못했던 스터디 라는 형태의 활동이었기에 너무나 즐거운 마음으로 기꺼이 함께하기로 하였습니다.

함수형 프로그래밍은 이론들의 설명들이 난해한데 반해 깨달음(?)을 얻고 나면 코딩을 하는 감각이 달라지게 되는데 그건 학습으로 되는 영역이라기 보다 직접 경험을 통해서 느껴봐야하는 부분이었기에 실습형 체험형 학습방법인 스터디라는 점이 너무 좋았습니다.

어떻게 하면 함수형 프로그래밍의 감각을 잘 느끼게 할 수 있을지 선택한 교재를 바탕으로 적절한 실습형 커리큘럼을 고민을 해보았고 크게 1부와 2부 각 4번의 코스로 구성해보았습니다.

스터디에서 전달하고자 했던 내용들은 다른 함수형 프로그래밍에 관심이 있고 공부를 해보려는 분들에게 도움이 되는 것 같아 내용들을 정리해서 글로 정리해보았습니다.

함수형 프로그래밍에 대해서 이론을 공부해도 감이 잘 오지 않는 분들에게 어떻게 함수형 프로그래밍에 접근을 해나가면 좋을지 도움이 되고자 1부 파트였던 함수형 사고에 대한 이론과 실습과정을 담은 스터디 내용을 바탕으로 정리한 내용입니다.

1부: 함수형 사고

  1. 함수형 프로그래밍 맛보기 -> 함수형 프로그래밍을 해야하는 이유 느끼기
  2. 순수함수(계산) 이해하기 -> 액션에서 계산 빼내기
  3. 함수형 프로그래밍 시각화하여 이해하기 -> 함수간 계층도를 그려보기
  4. 함수형 프로그래밍 파이프라인 이해하기 -> 파이프라인 그려보기

참고로 해당 스터디의 교재는 "쏙쏙 들어오는 함수형 코딩" 이라는 교재를 사용하였으며 설명하는 개념이나 코드 예제들은 책을 참고하였습니다. 이 글에서는 책이 없어도 읽어 볼 수 있도록 충분히 설명을 포함하려고 있지만 더 자세히 알고 싶은 분들은 책을 읽어보셔도 좋고 제가 작성한 글을 한번 읽어보셔도 좋을 거 같아요.

다시 쓰는 함수형 프로그래밍
https://velog.io/@teo/functional-programming


들어가기에 앞서...

함수형 프로그래밍 스터디에 앞서 함수형 프로그래밍에 대해 궁금한 것들을 물어보는 시간을 가져보았습니다. 그중에서 모두가 함께 알면 좋을 내용들을 다시 한번 정리해보았습니다.

Q. FE개발자가 함수형 프로그래밍을 해야 하는 이유가 무엇인가요?

FE 개발자는 좋든 싫든간에 javascript라는 언어를 사용해야만 합니다. 그리고 이 javascript는 그동안의 주류언어와는 다른 언어적인 특성이 있기 때문에 그 특성을 잘 이용해야 곧 FE개발을 잘 할 수 있게 됩니다. (저는 그런 javascript가 좋아서 FE개발자가 되었습니다 :) 그리고 그 특성중에 하나가 바로 함수형 프로그래밍입니다.

1. Javascript는 태생이 함수형 프로그래밍을 지향한다.

javascript의 창시자는 당시 주류였던 객체지향에 대해 의문을 품고 있었고 함수형 언어에 관심이 많았기 때문에 javascript를 함수형 언어로 만들고 싶어했습니다. 그렇지만 함수형 언어는 비주류였기에 당시 주류였던 java를 따라서 문법체계를 만들게 됩니다. 그래서 이름 역시 javascript가 되었습니다.

그러다 보니 언어의 문법은 java와 유사하지만 그래도 내부 매커니즘은 함수형 프로그래밍을 기초로 만들어지게 됩니다. 클로저라던가 익명함수와 같은 개념들이 그러합니다. 이러한 함수형 체계들은 javascript의 DOM을 처리하거나 이벤트 핸들러를 지정하는 부분들을 굉장히 간소화하는데 큰 역할을 합니다.

* java나 android 등의 객체지향 개념에서 Component의 EventHandler를 처리하는 방식과 javascript에서 함수를 이용한 addEventListener을 비교해보면 재밌을거에요!

또한 javascript가 맨 처음 생겼을 때에는 class라는 키워드도 아예 존재하지 않았고 prototype을 이용한 객체지향 방식과 this의 동적 바인딩이라는 개념이 함수형 프로그래밍 기반에서 객체지향를 흉내낼수 있도록 만들었기 때문에 자바스크립트는 온전한 객체지향적 방식으로 개발을 하기에 좋은 언어는 아니었습니다.

이후 많은 객체지향러버(?)들의 요구들로 인해 class라는 문법이 정식 문법이 되었고, private한 field나 그리고 Typescript등의 등장으로 인해서 객체지향적인 측면이 보강이 되고 있지만 근간은 여전히 함수형 언어에 기반하고 있기 때문에 JS를 잘하기 위해서는 함수형 프로그래밍에 대한 이해가 필요합니다.

2. 함수형 프로그래밍은 Javascript의 번들크기를 줄이는데 용이하다.

이 역시 javascript가 가지고 있는 언어적 특성에 기인합니다. 웹이라는 특성으로 인해 javascript는 설치되어 있지 않고 그때 그때 코드를 받아와서 실행을 시키는 형태로 되어 있습니다. 이렇게 javascript는 스크립트 언어기 때문에 스크립트의 길이가 곧 파일의 크기이며 내려받아서 실행을 시켜야 하는 만큼 코드의 크기는 작을수록 좋습니다.

그렇지만 코드의 글자수를 줄이게 되면 코드의 가독성은 떨어지기 때문에 유지보수가 힘들어집니다. 따라서 기존 코드에서 코드를 변경해도 문제가 없거나 불필요한 코드들을 삭제하는 minify기법을 통해서 번들의 크기를 줄이는 방법을 사용했습니다.

let verylonglongName = 1;
let verylonglonglonglongName = 2;
let veryverylonglonglonglongName = verylonglongName + verylonglonglonglongName;
console.log("veryverylonglonglonglongName", veryverylonglonglonglongName)

// minified: 짧은 이름으로 대체해도 결과는 동일하기에 배포에는 이렇게 줄여진 코드를 사용한다!
let a=1,b=2,c=a+b
console.log("veryverylonglonglonglongName", c)

또한 자바스크립트는 동적 타입언어입니다. 정해진 타입이나 스키마가 없이 언제든 객체에 속한 변수나 메소드가 있다면 외부에서 호출이 가능한 구조입니다.

const me = {
  name: "teo.yu"
  speak() { console.log("hello ", name) }
}

// 선언 시 등록된 메소드는 당연히 호출이 가능하다.
me.speak()

// 또한 언제든 동적으로 메소드 등록이 가능하다.
me.age = NaN
me.test = function() { console.log("test", this.age) }
me.test()

// 객체의 필드나 메소드는 모두 String을 통해서 접근이 가능하다.
me["name"]
me["age"]
me["test"]()

위 예시에서 처럼 객체의 속성이나 메소드에 접근하기 위해서는 정확한 String이 필요합니다. 따라서 객체를 외부에서 사용을 할 경우에 속성명이나 메소드명을 작성한 이름 그대로 사용을 해야만 동작을 합니다. 이러한 이유로 메소드나 속성명은 String이기에 minify를 할 수 없게 됩니다.

class Test {
  veryverylonglongMethod () {}
}

let veryverylonglongInstance = new Test()
veryverylonglongInstance.veryverylonglongMethod()
veryverylonglongInstance["veryverylonglongMethod"]() // String으로 호출해도 동일하다.

/// 잘못된 minify
class T{m(){}}
let v = new T();
v.m() // 이름을 줄여도 이 코드 동작에는 문제가 없다.
v["veryverylonglongMethod"]() // 그러나, 이 코드는 런타임에러가 발생한다.

위와 같은 이유로 스크립트는 작성된 코드의 수만큼 데이터에 포함이 되므로 번들의 크기를 줄이기 위해서는 객체와 메소드보다는 함수가 유리합니다.

뿐만 아니라 함수는 번들을 크기를 줄이기 위해서는 사용하지 않는 코드들은 아예 포함을 시키지 않는 트리쉐이킹이라는 기법이 가능합니다. 반면 클래스로 작성된 코드의 경우 메소드별로 트리쉐이킹을 할 수가 없고 객체단위로 진행이 되기 때문에 덩치가 큰 객체의 일부분만 사용을 하더라도 모두 번들에 포함이 됩니다.

정리하면, 자바스크립트는 이러한 언어의 구조상 객체 메소드나 필드명은 minify를 할 수 없고 트리쉐이킹을 할 수 없기 때문에 함수로 작성하는 것에 비해 번들크기를 줄이는데 있어 불리한 구조입니다.

3. 클래스형 컴포넌트에서 함수형 컴포넌트로 발전하는 과정으로 진화했다. (정확히는 this를 쓰지 않는 방향으로...)

자바스크립트의 this는 예전부터 문법적으로 불편했던 존재였습니다. function의 scope마다 this가 만들어지고 this는 호출한 객체로 취급되는 동적바인딩이라는 개념은 생소했고, 특히 Arrow Function이 만들어지기 전에는 외부에 있는 this를 이벤트 핸들러에서 사용하기 위해서 클로저를 이용해서 작성을 하는 기법들은 대단히 불편한 부분이었습니다.

// ES5 시절 class와 this와 anonymous을 쓰던 시절 this 중첩을 막기 위해 다음과 같이 코딩을 해야했다.
Test.prototype.method = function() {
  var that = this
  el.addEventListener(function(event) => { that.xxx })
}

뿐만 아니라 동적바인딩의 개념은 조금만 꼬아버리면 this가 어떤 값인지 알기 어렵게 만들었습니다.

// 각 this의 값는 어떤 값일까요?
var obj = {
	a: console.log(this),  		// --- ①
	fn: function() {
		console.log(this); 		// --- ②
		function fn() {
			console.log(this); 	// --- ③
		}
		fn();
	}
}
obj.a;
obj.fn();

분명 this의 개념은 javascript가 함수형 매커니즘을 기반으로 하면서도 객체지향 패러다임도 가능하게 만들어주는 중요한 역할을 합니다. 하지만 문법적으로 불편하는 점이 있다는 점도 주요했습니다.

이런 기능을 보완하고자 ES6에서는 Arrow Function이 만들어지긴 했지만, 개발자들은 불편한 this을 쓰지 않는 방향으로 진화했습니다.

결국 대세 프레임워크 React에서도 초창기 class를 기반으로 하던 문법에서 발생하는 불편함인 this.setState()의 문제와 그걸 bind를 통해서 이를 해결하도록하는 팁 아닌 팁 대신, 완전히 this를 제거할 수 있고 훨씬 더 간결한 형태의 모습으로 개발을 할 수 있게 되면서 React가 1등 프레임워크가 될 수 있는 기반을 마련해줍니다.

this와 setState기반의 class Component

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {someState:0};
  }
  
  methodOne(event) {
	this.setState({ someState: this.state.someState + 1})
  }

  render() {
    return (
      // 이러면 에러가 난다.
      <button onClick={this.methodOne}>Click me!</button>

      // bind를 해야 되는데 왜 이런지 알려주려면 상당한 부가설명이 필요하다.
      <button onClick={this.methodOne.bind(this)}>Click me!</button>    
    ) ;
  }
}

this와 상태가 사라진 더 간결한 함수형 Component

const MyComponent = (props) => {
  const [value, setValue] = useState(0)

  const methodOne = (event) => setValue(value + 1)
  
  return (
    <button onClick={methodOne}>Click me!</button>
  ) ;
}

진화의 관점에서 보면 한번 변화를 겪은 이후에는 다시 예전의 방식이 더 나았다고 되돌아가는 경우는 거의 없습니다. 모두가 class가 필요하다고 생각해서 ES6에는 class가 만들어졌고 Angaulr, React, Vue 등의 많은 프레임워크들은 class를 중심으로 하는 라이브러리를 만들었습니다. 이후 class 문법의 복잡함을 느끼고 functional 방식이 유행이 되면서 이제는 class를 기반을 하는 프레임워크는 찾아보기가 힘들어졌습니다.

이러한 변화는 FE에서 프레임워크 다음으로 중요하게 된 상태관리 라이브러리에도 영향을 주게 됩니다. class기반에서 이제는 모두 함수형태의 라이브러리로 변화하고 있습니다. 이러한 변화에는 자바스크립트가 class를 쓰는 것보다는 함수를 잘 사용해서 만드는 것이 문법적으로 더 낫다는 언어적인 특성에 기인합니다.

Array와 비동기를 다루기 위한 콜백 또한 함수형 프로그래밍

javascript를 다루다 보면 Array를 다루기 위한 map, filter, reduce 나 비동기를 다루기 위한 Promise의 then, 그리고 이벤트를 다루기 위한 Element의 addEventListener 등의 핵심 API들이 모두 함수형 매커니즘을 기반으로 하고 있음을 알게됩니다.

'Array와 Promise, Element 는 객체가 아닌가요?' 라고 반문할수도 있는데, 맞습니다. Array나 Promise나 Element 자체는 객체가 맞습니다. 하지만 각 객체가 가지고 있는 핵심 메소드들은 모두 함수를 인자로 받아서 활용하는 메소드이며 함수를 직접 값으로 넣을 수 있고 또 반환값으로도 사용할 수 있다는 개념인 1급함수를 적극적으로 활용하고 있다는 것이 특징입니다.

또한 Array나 Promise의 경우에는 함수형 프로그래밍에서 Functor 라고 불리는 개념으로 구성이 되어 있죠.

javascript의 API는 워낙 직관적이기 때문에 함수형 프로그래밍에 대한 개념이 없어도 사용하는데에는 지장이 없지만 함수형 프로그래밍 매커니즘을 이해하면 javascript를 더 깊이 이해하는데 크게 도움이 됩니다.

함수형 프로그래밍은 여러분의 javascript를 더 클린코드로 만들어줍니다

자바스크립트는 순수 함수형 언어가 아닙니다. 함수형 언어의 매커니즘을 기본으로 하고 있으나 객체지향적인 개념도 섞여있고 기존의 절차형 프로그래밍적인 성격이 강합니다. 언어 자체가 이것저것을 다 지원하는 멀티 패러다임의 특성을 가지고 있다보니 내가 특정 체계에 대한 이해가 없이 작성을 하게 되면 각 특성들의 단점만 섞여있는 스파게티형 코드가 될 가능성이 높습니다.

자바스크립트의 문법적인 특성으로 인해 class보다 function이 유리한 점을 나열했지만 function으로만 개발한다고 무조건적으로 더 나은 것은 아닙니다.

함수형 프로그래밍은 단순히 class를 쓰지 않고 function을 이용해서 개발을 한다는 개념이 아닙니다.

함수형 프로그래밍은, 부수효과를 멀리하고 가급적 순수함수를 만들어 1급함수의 특징들을 이용해서 함수들을 인자과 반환값으로 사용하는 활용을 통해서 단순하고 결함이 적은 단방향성의 파이프라인을 함수들의 조립을 통해 구축한다는 개념입니다.

결국 FE개발자는 javascript를 쓸 수 밖에 없는데 이 javascript가 함수형 프로그래밍을 하기 좋게 그리고 하면 더 유리하도록 만들어져 있는 언어이다보니 함수형 프로그래밍에 대한 이해를 하게 되면 우리는 javascript를 더 잘 쓸 수 있게 됩니다.


Q. 그러면 객체지향 프로그래밍과 함수형 프로그래밍의 차이가 뭔가요?

컴퓨터는 0과 1의 조합으로 이루어져있고 이것을 우리는 데이터라고 부릅니다. 이러한 데이터가 자극과 정해진 로직에 의해 계속 변해가는 것이 프로그램이라고 볼 수 있습니다. 이렇게 변하는 데이터의 순간순간을 우리는 상태(state)라고 부릅니다.

결국 프로그래밍이란 이 상태의 변화를 다루는 것인데 프로그램의 덩치가 커지고 복잡해지면 너무나 많은 상태들을 관리하는 것에 대해 혼란스러워집니다. 문제가 생길때마다 어떤 값이 문제인지 다 들여야 봐야 할테니까요. 문제가 될 후보지가 많아질수록 우리는 어려움을 느끼게 될 것입니다.

생각없이 작성한 스파게티 코드가 왜 디버깅을 하기 어려운지 한번 떠올려보세요!

객체지향 프로그래밍함수형 프로그래밍은 각자의 방식으로 이러한 상태들을 어떤 식으로 구성하고 정리하면 더 나은 프로그래밍을 만들 수 있을지에 대한 관점들입니다.

객체지향 프로그래밍(Object-Oriented Programming, OOP)은 프로그램을 객체들의 모임으로 구성하는 프로그래밍 패러다임입니다. 객체는 속성(property)과 메서드(method)로 구성되어 있으며, 속성은 객체가 가지는 상태를 나타내고 메서드는 객체가 할 수 있는 행동을 나타냅니다.

함수형 프로그래밍(Functional Programming, FP)은 프로그램을 함수들로 구성하는 프로그래밍 패러다임입니다. 함수는 인자를 받아 결과를 반환하는 것으로 구성되며, 상태와 상태의 변화가 없는 함수를 사용하는 것을 권장합니다.

객체지향은 서로 관심이 있는 것끼리 모아서 관리를 하자는 입장입니다. 문제가 발생했을 때 관심사가 같은 것끼리 묶어서 객체로 관리하게 되면 문제의 범위를 각 객체로 좁힐 수 있고, 동일한 관심사가 모인 객체를 작은 프로그램으로 취급하여 조립하고 서로 소통할 수 있도록 하자는 관점입니다.

함수형은 상태를 변화하는 것과 그렇지 않은 것들을 분리해서 관리하자는 관점입니다. 결국 상태관리가 어려운 이유는 상태가 변화하기 때문인데 상태와 무관한 함수들을 만들면 문제가 발생하지 않거나 검증된 함수가 만들어지고 이를 조립한 함수 역시 문제가 없을 것이기에 문제가 발생할 수 있는 범위를 부수효과에 한정할 수 있도록 하자는 것입니다.

위와 같은 사전지식을 바탕으로 실제 코드를 보면서 직접 느껴봅시다. 동일한 코드를 객체지향의 관점과 함수형 관점으로 작성을 해보았습니다.

객체지향 프로그래밍의 코드 예시

class BankAccount {
  constructor(balance) {
    this.balance = balance;
  }

  deposit(amount) {
    this.balance += amount;
  }

  withdraw(amount) {
    if (amount > this.balance) {
      console.log("Insufficient funds");
    } else {
      this.balance -= amount;
    }
  }
}

let account = new BankAccount(100);
account.deposit(50);
console.log(account.balance);  // 150

account.withdraw(75);
console.log(account.balance);  // 75

함수형 프로그래밍의 코드 예시

const deposit = (balance, amount) => balance + amount;

const withdraw = (balance, amount) => {
  if (amount > balance) {
    console.log("Insufficient funds");
    return balance;
  }
  return balance - amount;
};

let balance = 100;

balance = deposit(balance, 50);
console.log(balance);  // 150

balance = withdraw(balance, 75);
console.log(balance);  // 75

위 두 코드는 기능적으로는 같은데 객체지향적인 코드와 함수형적인 코드의 차이를 볼 수 있습니다.

객체지향적인 코드에서는 상태를 저장하는 객체와 객체의 메서드를 통해 상태를 제어하는 것을 볼 수 있고 함수형 코드는 함수만 이용하여 상태를 제어하는 것을 볼 수 있습니다.

함수형 프로그래밍은 객체지향에 비해 다음과 같은 이점을 가집니다.

  1. 순수 함수를 사용하기 때문에 코드가 단순해집니다. 상태를 저장하는 객체가 없기 때문에, 코드를 이해하기가 쉽습니다.
  1. 순수 함수를 사용하기 때문에 테스트가 쉬워집니다. 함수의 출력을 예측할 수 있기 때문에, 테스트 작성이 간단해집니다.
  1. 순수 함수를 사용하기 때문에 코드가 부작용이 없어집니다. 순수 함수는 외부 상태에 의존하지 않기 때문에, 부작용을 일으키지 않습니다.

객체지향은 함수형 프로그래밍에 비해 다음과 같은 이점을 가집니다.

  1. 완성된 객체는 관심사가 같은 것으로 묶여있기 때문에 이해하기 쉽고 훨씬 더 사용하기 쉽습니다. 일례로 IDE에서 객체에 .만 찍어봐도 무슨 일을 할 수 있고 무슨 값이 있는지 알 수 있는 반면에 함수형 프로그래밍은 각 함수들을 다 알고 있어야 하며 함수간 관련도를 인자만 가지고 파악하기가 쉽지 않습니다.
  1. 캡슐화로 인해서 외부에서 몰라도 될 코드를 숨길 수 있도록 하여 더 간결한 형태의 코드를 만들어내기에 용이합니다. (반대로 테스트와 커스텀에 불리하다는 말이기도 합니다.)
  1. 객체지향 프로그래밍은 일반적으로 더 익숙한 프로그래밍 방식이며 관심사를 분리하기 쉬워서 코드의 가독성과 유지보수성이 높은 프로그래밍을 가능 하게 합니다.

Q. 그러면 함수형 프로그래밍을 하면 객체지향을 안해도 되는 건가요?

함수형 프로그래밍과 객체지향 프로그래밍의 목적은 모두 더 나은 체계와 관점을 통해서 프로그래밍을 잘하기 위한 것이기에 함수형 프로그래밍을 사용하면 객체지향 프로그래밍을 사용하지 않아도 될 수도 있습니다.

사실 이러한 프로그래밍 패러다임의 선택과 사용은 그전까지는 언어차원에서 지원을 하는가의 문제였습니다. 가령 초창기 C언어에는 객체지향을 다루기 위한 문법적인 지원이 없었고 C++에서 언어에서 이러한 패러다임을 지원할 수 있는 문법체계가 만들어지면 함께 개념이 성장하였습니다. C++로는 객체지향 프로그래밍만 할 수 있었습니다.

함수형 프로그래밍의 경우에도 언어적으로 그러한 패러다임이 가능하도록 일급함수, 클로저, 불변성등의 함수형 프로그래밍 패러다임으로 작성된 언어를 쓴다면 함수형 프로그래밍으로 해야만 했었습니다.

하지만 언어에서 두가지 패러다임을 모두 제공하고 있다면 둘 다 해도 됩니다. 그러므로 함수형 프로그래밍을 사용하더라도 객체지향 프로그래밍을 완전히 피하지는 않아도 되고, 객체지향 프로그래밍과 함수형 프로그래밍을 결합하여 사용 할 수도 있습니다. 예를들어, 객체지향 패러다임을 사용하여 애플리케이션의 객체를 캡슐화 하고, 함수형 패러다임을 사용하여 로직을 구성할 수 있다는 것입니다. 그리고 실제로 이러한 방식은 모던 FE 개발에서는 너무 익숙한 개념이기도 합니다.

어떤 패러다임을 선택하더라도 애플리케이션을 개발 할 때 가장 중요한 것은 가독성과 유지보수성이 좋은 코드를 작성하는 것입니다. 어떤 패러다임이든 코드를 작성할 때 이러한 측면을 고려 하면서 사용하면 더 좋은 애플리케이션을 개발 할 수 있을 것입니다.

그래서 결론이 뭔가요?

결국 우리는 자바스크립트를 사용하고 있다는 것이고 자바스크립트는 함수형 프로그래밍 패러다임의 일부와 객체지향 패러다임의 일부를 동시에 제공을 하고 있는 언어입니다.

함수형 코딩을 설명하기 위한 자리이다보니 함수형 프로그래밍의 장점을 어필하고 있지만 함수형 프로그래밍이 객체지향보다 더 나은 개념은 아니라는 것이며 얼마든지 상호보완될 수 있는 개념입니다. 일례로 자바스크립트가 나오기 전까지 함수형 프로그래밍 언어 중에 대중적인 언어는 하나도 없었으며 자바스크립트가 순수 함수형 언어였다면 지금만큼 대중성을 가지기도 힘들었을거라고 생각합니다.

객체지향 프로그래밍은 이미 역사도 오래되었고 C++, Java등을 통해서 이미 충분히 알려지고 발전되고 익숙한 프로그래밍 방식입니다. 객체지향의 개념을 배우고 적용을 하는 것은 javascript를 하는데 큰 도움이 됩니다.

반면 javascript는 언어에서 함수형 프로그래밍을 기반으로 하고 있고 잘 지원을 합니다. 그렇다면 함수형 프로그래밍의 장점을 함께 녹여낼 수 있다는 점이지요. 하지만 함수형 프로그래밍은 객체지향 프로그래밍만큼 자료가 많이 있다거나 친숙한 방법은 아닙니다.

자바스크립트를 잘 하기 위해서는 자바스크립트의 특징도 잘 알아야 하겠지만 그 특징이 멀티 패러다임 언어라는 것을 감안하면 각 패러다임의 특징을 이해해서 장점만 적절히 쓸 수 있다면 자바스크립트를 더 잘 쓸 수 있다는 것입니다.

그리고 이 글은 함수형 프로그래밍 패러다임을 학습하기 위한 이론과 실습에 대한 가이드를 제공하려고 하고 있답니다. :)

함수형 프로그래밍을 배우는 이유 그리고 방향성

'실무에서 함수형 프로그래밍을 안 쓰고 있는 것 같은데 아닌가요?'
'함수형 프로그래밍은 그냥 이론적인 부분 아닌가...'
'매니아들만 쓰고 있는 그런거?'
'배워도 딱히 쓸데가 없던데?'
'어차피 객체지향으로 개발해야 되는 거 아냐?'
'Ramda나 lodash 그런거 쓰면 모르는 사람은 코드 복잡해서 못써!'

함수형 프로그래밍으로의 대전환을 하기 위해서 함수형 프로그래밍을 배우는 것이 아닙니다. 함수형 라이브러리를 다루는 것을 배우기 위해서 함수형 프로그래밍을 배우는 것도 아니어야 합니다. 그렇게 하려고 하면 오히려 함수형 프로그래밍을 하면서 위와 같은 회의감만 들게 됩니다.

실무에서 함수형 프로그래밍으로의 대전환이 아니더라도 우리가 쓰는 자바스크립트의 다른 축이 되는 큰 방향으로써 그리고 프로그래밍을 볼 수 있는 새로운 감각과 시각 만으로도 충분히 함수형 프로그래밍은 배우고 이해하고 느껴볼만한 가치가 있습니다.

무엇보다 함수형 프로그래밍은 이론을 공부하는 것보다 결국은 실습을 통해서 말하고자 하는 체계를 몸으로 느껴보는 것이 가장 중요합니다.

이를 위해 스터디는 이론의 비중보다는 실습팀을 구성하여 함께 코딩하고 최대한 상호간의 설명을 하고 또 공감을 하여 몸으로 체감하는 이해를 할 수 있도록 많이 신경을 썼습니다.

함수형 프로그래밍에 관심이 생기게 되면 지금까지의 이론적 지식들에 대한 이야기들은 정말로 많이 들어봤을 거라고 생각합니다. 이 후에는 그래서 어떻게 이것들을 실습해서 느껴볼 것인가 하면서 작성해 본 실습과제와 방법들을 알려드리려고 합니다.

실습과제는 입문자를 대상으로 했기에 어렵지 않고 단순합니다.

그러니 꼭 글로만 읽고 그냥 넘어가지 마시고 직접 타이핑해보고 같이 토론하고 설명해보면서 몸소 함수형 프로그래밍이 전하는 감각을 체감하시길 바랍니다.


1주차 - 함수형 프로그래밍 맛보기

함수형은 상태를 변화하는 것과 그렇지 않은 것들을 분리해서 관리하자는 관점입니다. 결국 상태관리가 어려운 이유는 상태가 변화하기 때문인데 상태와 무관한 함수들을 만들면 문제가 발생하지 않거나 검증된 함수가 만들어지고 이를 조립한 함수 역시 문제가 없을 것이기에 문제가 발생할 수 있는 범위를 부수효과에 한정할 수 있도록 하자는 것입니다.

책의 저자인 에릭 노먼드는 순수함수의 정의등이 함수형 프로그래밍을 어렵게 만든다고 보고 부수효과와 순수함수라는 용어 대신 액션 - 계산 - 데이터 로 표현하기도 합니다. 혼용해서 쓰겠지만 액션은 부수효과 계산은 순수함수와 같은 개념이라고 생각해주세요.

계산(=순수함수)은 입력과 출력이 존재하고, 같은 입력에는 항상 같은 출력값을 나타내며 함수를 여러번 실행을 해도 외부세계에 영향을 주지 않는 함수입니다.

반면, 액션(=부수효과)은 실행시점과 횟수에 따라 달라지는 함수를 말합니다. 액션이 많을수록 테스트 하기 어렵고 다루기 어렵습니다.

함수형 프로그래밍의 시작은 액션에서 계산을 빼내어 테스트 하기 쉽고 조립하기 쉬운 구조로 쪼개는 것입니다. 하지만 액션과 계산의 분리에 대해서 먼저 이해를 하기 전에 왜 이렇게 해야하는지를 먼저 느껴야합니다.

함수형 프로그래밍에서 제시하는 대로 코드를 쪼개고 있다보면 이게 정말로 더 나은 방식인지 의문이 들 때가 있습니다. 이렇게 했을때 코드가 더 나아지는구나라는 스스로의 체감이 없으면 '그냥 이렇게 하는게 좋다고 하니까...' 하면서 기계적으로 코드를 분리하고 오히려 더 코드를 이해하기 어렵게 해체만 시켜놓는 형태가 됩니다.

그래서 첫번째 시간에는 함수형 프로그래밍 관점이 적용되지 않는 코드가 왜 좋지 않은지를 먼저 느껴보는 것을 목표로 하여 다음과 같은 과제를 선정하였습니다.

실습 과제

여기에 2명의 개발자 제나 그리고 조지가 있습니다.
제나는 기획의 요구사항을 개발하는 개발자이며, 조지는 테스터의 역할을 하고 있습니다.

아래 코드는 제나가 작성한 코드입니다. 잘 동작하고 있는 코드라서 제나는 만족하고 배포했지만 테스터 역할인 조지는 이 코드에 불만이 많습니다. 조지는 해당 코드 테스트를 하기 매우 어렵다고 말하고 있습니다.

여러분들은 조지의 입장이 되어 이 프로그램을 테스트하는 코드를 작성해보세요. 그리고 어떤 부분에서 테스트 코드를 작성하기 어려운지 느껴보고 원래 코드를 어떻게 수정하면 테스트 하기 좋을지 함께 이야기를 나눠보세요.

/// 예시 코드
var shopping_cart = [];
var shopping_cart_total = 0;

function add_item_to_cart(name, price) {
  shopping_cart.push({
    name: name,
    price: price
  });
  calc_cart_total();
}

function update_shipping_icons() {
  var buy_buttons = get_buy_buttons_dom();
  for (var i = 0; i < buy_buttons.length; i++) {
    var button = buy_buttons[i];
    var item = button.item
    if (item.price + shopping_cart_total >= 20) button.show_free_shopping_icon();
    else button.hide_free_shopping_icon();
  }
}

function update_tax_dom() {
  set_tax_dom(shopping_cart_total * 0.1);
}

function calc_cart_total() {
  shopping_cart_total = 0;
  for (var i = 0; i < shopping_cart.length; i++) {
    var item = shopping_cart[i];
    shopping_cart_total += item.price;
  }
  set_cart_total_dom();
  update_shipping_icons();
  update_tax_dom();
}

과제 진행 순서

  1. 기존 코드의 테스트 코드를 실제로 작성을 해본다.
  2. 왜 위 코드가 테스트 코드가 작성하기 어려운 코드인지 팀원들에게 설명해본다.
  3. 테스트를 하기 쉽게 만들기 위해서 리팩토링을 함께 고민해본다.
  4. 리팩토링한 코드를 통해 함수형 프로그래밍의 액션 - 계산 구분의 필요성을 느껴본다.

꼭! 실제로 해보시길 바랍니다. 동료와 함께 하면 더욱 좋습니다!

1주차 정리

이번 스터디의 핵심은 테스터의 입장이 되어 "돌아가기는 하지만 좋지 않은 코드" 는 왜 그런것이지 그리고 어디가 문제인지를 느끼고 어떻게 하면 더 좋은 코드가 될 수 있는지를 함께 이야기를 나눠보는 것이었습니다.

다들 느끼셨겠지만 액션은 테스트 코드를 작성하기가 매우 어렵습니다. 누군가 "이 함수는 undefined를 반환하는 데 어떻게 테스트 코드를 작성해요?" 라고 물어보기도 했습니다.

맞습니다! 액션은 반환값도 없고 결과를 증명할 방법도 어려우며 실행할때마다 조건을 만들기도 결과를 예측하기도 어려운 코드입니다. 당연히 테스트를 할 수 없는 코드는 좋지 않은 코드입니다.

계산은 액션과 달리 입력값과 반환값이 존재하고 언제나 같은 입력에 대해서는 같은 결과를 반환합니다. 그렇기에 테스트를 하기 용이하고 테스트 코드를 작성하기도 쉽습니다. 그래서 우리는 액션보다는 계산의 형태로 코드를 작성해야 합니다.

그렇지만 모든 코드를 계산형식으로만 작성할 수는 없습니다. 액션이 없으면 프로그램은 완성되지 않습니다.

그래서 액션 안에서 계산으로 뽑을 수 있는 부분들을 분리하여, 데이터 -> 액션 -> 계산 -> 계산 -> 계산 -> 액션 -> 데이터 과 같은 계층 구조를 만들어 내는 것이 함수형 프로그래밍이고 함수형 사고입니다.

이번주에는 우리가 무엇을 배우고 있는 것인지 그리고 이걸 왜 하는 것인지 라는 것을 몸소 느꼈으니 다음주에는 그래서 "어떻게 더 좋은 코드로 바꾸지?" 라는 부분에 조금 더 포커스를 맞춰서 설명을 드리고 다 같이 코드를 작성하는 시간을 가져보려고 합니다.

다음주 스터디 전에 꼭 알아야할 핵심 개념 주제 3가지는 꼭 책을 통해서 숙지를 해오시면 좋을 것 같아요.

  1. 액션 - 계산 - 데이터 (혹은 순수함수와 부수효과 이해하기)
  2. 암묵적 입출력 vs 명시적 입출력
  3. 불변성과 카피-온-라이트

다음주에도 오늘 함께 했던 우리 즐거운 팀원들과 함께 재미난 스터디가 되기를 바랍니다.
모두 모두 수고 많으셨습니다.


2주차 - 액션에서 계산 빼내기

1주차에는 함수형 코딩의 핵심인 액션 - 계산 - 데이터라는 개념을 이해했고 이렇게 구분하여 작성을 하지 않는 코드에서 어떠한 문제가 있는지를 테스터라는 관점에서 코드를 보는 연습을 해보았습니다. 그리고 이러한 이유를 바탕으로 액션 - 계산 - 데이터로 코드를 나누게 되면 좋다는 것을 알려드렸습니다.

2주차에서는 "어떻게" 액션으로 부터 "계산"을 빼낼 수 있는지에 대해서 배워보고 한번 실습을 해보았습니다.

책에서는 액션에서 계산을 빼내는 작업은 암묵적인 입력과 암묵적인 출력을 명시적인 입력과 명시적인 출력으로 바꾸는 데 있다고 말합니다.

암묵적 입력

암묵적인 입력은 함수인자가 아닌 형태로 사용되는 데이터나 함수내부에서 선언한 데이터 등을 의미합니다.

암묵적 출력

암묵적인 출력은 함수의 반환값이 아닌 외부세상에 변화를 주는 것(DOM, console, 전역변수에 대입)을 의미합니다.

명시적인 입력과 출력

명시적인 입력이란 함수의 인자를 의미하고 명시적인 출력이란 함수의 반환값을 의미합니다

액션에서 계산 빼내기 - 리팩토링 방법

1주차에서 느꼈을 함수를 테스트를 쉽게 하기 위해서는 함수가 반드시 반환값을 가져야 하며, 함수 내에서 사용되는 모든 데이터를 외부에서 주입이 가능해야 한다는 것을 느꼈을거에요.

그래서 이러한 암묵적인 입/출력들을 "함수인자와 반환값인 명시적인 입출력으로 바꾸는 것" 이 액션에서 계산을 빼내는 방법입니다.

그래서 우리는 다음과 같은 전략을 취해볼 수 있었습니다.

  1. 액션에 암묵적인 출력의 개수에 따라 계산 함수로 빼낸다.
  2. 해당 값이 관련된 코드 조각을 모아 함수의 형태로 만든다.
  3. 계산함수에 반환값이 없다면 반환값을 만든다. (대개 마지막에 쓰이고 있는 변수)
  4. 함수내에서 사용되고 있는 모든 데이터를 함수의 인자에서 받을 수 있도록 리팩토링 한다.
  5. 외부세상에 영향을 주고 있지 않도록 필요하다면 카피온라이트나 방어적복사를 적용한다.

끝으로 계산은 여러번 수행이 되어도 외부 세계에 아무런 영향을 끼쳐서는 안됩니다.
그렇기에 계산은 함수 외부의 값을 변경해서는 안됩니다.

그래서 전역변수의 값이나 객체 혹은 배열로 넘어온 인자의 값을 직접 수정하는 행위를 하지 말아야 합니다.

카피온라이트와 방어적 복사

그러지 않기 위해서 값을 변경해야 하는 일이 발생한다면 해당 값을 복사해서 조작하여 반환하는 "카피온라이트", "방어적 복사" 라는 기법을 사용하게 됩니다.

"카피온라이트", "방어적 복사"에 대한 자세한 내용은 https://velog.io/@teo/functional-programming 링크를 참조해주세요

그리고 이러한 방식은 ES6에서 spread operator [...arr], {...obj} 와 같은 문법적인 요소가 되었기에 적극적으로 사용해주시면 좋을 것 같아요.

어떻게 액션에서 계산을 빼낼 수 있을까요?

  1. 액션에 암묵적인 출력의 개수에 따라 계산 함수로 빼낼 수 있다.
// 액션에서 암묵적 출력이 2개이므로 각 모듈을 계산으로 빼낼 수 있다.
function add_item_to_cart(name, price) {
  shopping_cart.push({
    name: name,
    price: price
  });
  calc_cart_total();
}
  1. 해당 값이 관련된 코드 조각을 모아 함수의 형태로 만든다.
// 함수로 분리
function add_item(name, price) {
  shopping_cart.push({
    name: name,
    price: price
  });
}

function add_item_to_cart(name, price) {
  add_item(name, price) // 함수로 대체
  calc_cart_total();
}
  1. 계산함수에 반환값이 없다면 반환값을 만든다. (대개 마지막에 쓰이고 있는 변수)
// 계산은 명시적인 출력이 필요하므로 반환값을 만든다.
function add_item(name, price) {
  shopping_cart.push({
    name: name,
    price: price
  });
  return shopping_cart
}

function add_item_to_cart(name, price) {
  const new_cart = add_item(name, price)
  calc_cart_total();
}
  1. 함수내에서 사용되고 있는 모든 데이터를 함수의 인자에서 받을 수 있도록 리팩토링 한다.
// 계산은 명시적인 입력이 필요하므로 전역변수가 아니라 인자에서 받아 올수 있도록 한다.
function add_item(shopping_cart, name, price) {
  shopping_cart.push({
    name: name,
    price: price
  });
  return shopping_cart
}

function add_item_to_cart(name, price) {
  const new_cart = add_item(shopping_cart, name, price)
  calc_cart_total();
}
  1. 외부세상에 영향을 주고 있지 않도록 필요하다면 카피온라이트나 방어적복사를 적용한다.
// 계산은 외부세계에 영향을 주면 안되므로 카피 온 라이트를 적용한다.
// *push는 원래 Array의 값을 변화시킨다.
function add_item(shopping_cart, name, price) {
  return [...shopping_cart, {
    name: name,
    price: price
  }]
}

function add_item_to_cart(name, price) {
  const new_cart = add_item(shopping_cart, name, price)
  calc_cart_total();
}
// ES6 Style
const add_item = (shopping_cart, name, price) => [...shopping_cart, {name, price}]

function add_item_to_cart(name, price) {
  const new_cart = add_item(shopping_cart, name, price)
  const total = calc_cart_total(new_cart); // 이 코드는 스스로 해보세요!
}

과제 진행 순서

  1. 액션에서 계산 빼내기 방법에 의거해서 기존의 코드를 리팩토링 해보세요.
  2. 리팩토링한 코드를 바탕으로 다시 한번 테스트 코드를 작성해보세요.
  3. 첫째날에 작성했던 코드와 비교해서 어떤 점이 달라졌는지 설명해보세요.

NOTE:

  • 책에서는 function을 쓰고 있지만 Arrow Function을 쓰세요. const fn = () => {}
  • 책에서는 slice()를 통해서 Copy on Write를 하고 있지만 [...] spread 연산자를 사용하세요.

과제 수행 예시

1주차에 받은 코드를 한번 위 공식에 맞춰서 코드를 리팩토링을 한번 해보았습니다. 책의 예시에서 조금 더 ES6스타일로 정리해보았습니다.

혹시 지난 코드에서 연습을 스스로 하지 않았다면 예시 코드를 보지 말고 꼭! 스스로 연습을 해보고나서 한번 맞춰보시길 바랍니다.

// 데이터
let shopping_cart = [];

// 계산
const make_cart_item = (name, price) => ({name, price})

// 계산
const add_item = (shopping_cart, item) => [...shopping_cart, item]

// 계산
const calc_cart_total = (shopping_cart) => shopping_cart.reduce((total, item) => total + item.price, 0)

// 계산
const calc_tax = (shopping_cart_total) => shopping_cart_total * 0.1

// 액션
// - 테스트 하기 쉬운 함수들의 조립으로 리팩토링이 되었습니다.
const add_item_to_cart = (name, price) => {
  const item = make_cart_item(name, price)
  shopping_cart = add_item(shopping_cart, item)

  const total = calc_cart_total(shopping_cart);
  const tax = calc_tax(total)
  
  set_cart_total_dom(total);
  set_tax_dom(tax);

  update_shipping_icons(total);
}


const add_item_to_cart = (name, price) => {
  shopping_cart = add_item(shopping_cart, makeItem(name, price))
  update_shopping_cart(shopping_cart);
}



// 계산
const is_free_shipping = (item_price, shopping_cart_total) => item.price + shopping_cart_total >= 20

// 액션
// - 책에서는 get_buy_buttons_dom()나 button객체에 대한 추가내용이 없어서 리팩토링을 진행하지 못했습니다. 
// - 이러한 방식의 코드는 여전히 테스트 하기 어렵다는 점을 느껴주세요.
function update_shipping_icons(total) {
  var buy_buttons = get_buy_buttons_dom();
  
  for (var i = 0; i < buy_buttons.length; i++) {
    const button = buy_buttons[i];
    const item = button.item
    if (is_free_shipping(item.price, total)) {
      button.show_free_shopping_icon();
    }
    else button.hide_free_shopping_icon();
  }
}

정답(?)을 보고나면 별거 아닌 거 같고 당연해보이지만 연습이 되어 있지 않다면 조금 해맬수도 있습니다.

액션과 계산으로 분리된 코드에 대해서도 테스트 코드를 한번 작성해보세요. 첫번째 코드와는 어떻게 다른지도 한번 생각해보세요. 체계가 없던 첫번째 코드와 액션과 계산이 분리되어 정리된 코드는 어떤 차이가 있는 지도 한번 느껴보고 얘기를 나눠보세요.

나아가 이번 스터디를 통해서 액션에서 계산을 분리하는 방법을 익히셨다면 꼭 내가 가지고 있는 코드를 가지고 한번 이러한 관점으로 리팩토링을 해보는 연습을 해보기 바랍니다.

수고 많으셨습니다!


3주차 - 계층적 구조 시각적으로 이해하기

두 번의 실습을 통해서 코드를 함수형 사고에서 말하는 액션 / 계산 / 데이터의 3가지 영역으로 나눠보고 이런 체계로 코딩을 하면 어떤 점이 더 좋아지는지 느껴보는 시간을 가져보았습니다.

우리는 같은 function 을 사용하지만 function이 다 같은 계층이 아니라는 것을 직감적으로 알 수 있었습니다. 적어도 계산과 액션은 전혀 다른 성격을 가지고 있다는 것을 말이죠.

계산과 액션도 자세히 들여다보면 저 마다의 계층이 존재합니다. 계산은 크게 스키마 / 비지니스 로직 / 유틸 이런식으로 나눠 볼 수 있습니다.

  • 비지니스 로직은 우리가 구현을 해야할 요구사항입니다.
  • 스키마는 우리가 사용하는 데이터의 구조에 의존하는 함수이며,
  • 유틸은 프로젝트와 전혀 관계없는 함수를 말합니다.

이러한 계층적 구조를 알고 있다면 자연스레 어떤 코드를 어디에 두어야 할지 머리속에 그려지고 나아가 요구사항의 추가나 변화가 발생했을때 어떤 계층에 어떤 액션과 계산과 데이터가 추가가 되어야 할지 머리속으로 그려지게 됩니다.

그래서 이번 시간에는 계층적 구조를 시각적으로 이해하고 코드를 시각적으로 받아들일 수 있는 연습을 해보는 실습 과제를 준비해보았습니다.

코드를 계층별로 시각화 하는 과정을 직접 그려보고 배치를 해보면서 좋은 구조는 어떻게 생겼는지 시각적으로 이해하는 시간을 가져보려고 합니다.

과제 진행 순서

  1. 피그잼을 열어 함수 이름만 따로 블록으로 만들어보세요.
  2. 함수 콜 스택을 중심으로 콜 트리를 한번 그려보세요.
  3. 같은 계층에는 유사한 함수들로 구성이 되어 있는지 확인해보세요.
  4. 성격이 비슷한 함수끼리 계층을 나눠보세요.
  5. 계층을 한꺼번에 뛰어넘는 구간이 보이면 중간 계층의 적절한 함수를 한번 고민해보세요.

결과 예시

  1. 일단 콜 그래프를 그려본다.

  1. 유사한 성격의 함수끼리 같은 위치에 배치를 해본다.

  1. 계층을 한꺼번에 뛰어넘는 구간이 보이면 적절한 중간 계층의 함수를 만들어보세요.
const add_item_to_cart = (name, price) => {
  const item = make_cart_item(name, price)
  shopping_cart = add_item(shopping_cart, item)
  update_shopping_cart(shopping_cart);
}

const update_shopping_cart = (shopping_cart) => {
  const total = calc_cart_total(shopping_cart);
  set_cart_total_dom(total);

  const tax = calc_tax(total)
  set_tax_dom(tax);

  update_shipping_icons(total);  
}

그래프의 계층을 나누는 방식이나 중간계층을 끼워넣어보면서 리팩토링을 데에는 정해진 정답같은 것은 없습니다. 정답에 연연하지 말고 스스로 한번은 이런 고민을 해보고 코드를 시각적으로 그릴 수 있는 훈련과 시각을 가졌다는 데에 의의를 두세요.

책에 나온 예시의 update_shipping_icons 관련 쪽 코드가 함수형 프로그래밍 패러다임을 따르고 있지 않다보니 뭔가 딱 맞아 떨어지는 결과가 나오지 않네요;;

해당 스터디에서는 방법을 익혀보는 것이니, 꼭 실제로 본인의 최근 코드를 가지고 한번 동일한 방식으로 콜 스택을 중심으로 그래프를 그려보고 계층을 나눠보는 실습을 해보시길 바랍니다. 방법을 알고 있는 거와 실제로 체득은 전혀 다른 이야기이니까요.

오늘도 수고 많으셨습니다!!


4주차 - FE pipeline 이해하기

4주차 실습과제는 책에는 없는 내용으로 지금까지 배운 함수형 프로그래밍의 감각을 "FE실무에서는 함수형 프로그래밍을 어떻게 적용해야하는가?" 라는 것들을 느끼고 배울 수 있는 시간을 준비해보았습니다.

함수형 프로그래밍 학습에서 지적을 받는 부분들이 바로 실무에 바로 적용을 하기에 어렵다는 것이기에 함수형 라이브러리의 적용이 아니라 지금까지 학습했던 함수형 사고를 어떻게 적용을 하면 되는지에 대해서 알려드리고 합니다.

또한 함수형 프로그래밍의 핵심 개념인 "pipeline of input -> output" 이라고 하는 개념에 대해 시각적으로 이해할 수 있는 시간이 되기를 바랍니다.

지금까지 프로그램을 액션 / 계산 / 데이터의 계층으로 나누고 또 세부적인 성격에 따른 계층들로 나누면서 코드를 이해하기 쉽고 테스트하기 쉽게 쪼개고 분리하는 연습을 해보았습니다.

결국 이렇게 함수들을 분리하는 것은 최종적으로 잘 조립을 하기 위함이고 이 조립을 잘 하기 위해서는 흐름이라는 관점에서 프로그래밍을 바라볼 수 있어야 합니다.

함수형 사고에 대한 흐름에 앞서 우리는 FE이기 때문에 FE의 관점에서 먼저 대략적인 흐름을 떠올려보는 것으로 하겠습니다.

출발점은 사용자의 액션으로 시작하면 좋습니다. 우선 이벤트 핸들러로 부터 시작을 해봅시다.

FE의 요구사항이란,

1) 사용자가 어떤 동작을 하면 (Action, Event Handler)
2) 이러저러한 기존 데이터를 가져와서 (State, Select, fetch)
3) 이러이러한 조건에 맞게 (Condition, If)
4) 기존 데이터를 이렇게 가공주시고 (Business logic, Post)
5) 그 결과를 다시 사용자가 보기 편하게 바꿔서 (formatter, pipe, ...)
6) 이러저러한 화면이 나오도록 해주세요. (View, DOM, render)
의 큰 틀을 가지게 됩니다.

괄호안의 키워드는 유사한 것들을 떠올려보라고 관련 키워드들을 한번 적어보았습니다.

FE의 요구사항을 사용자의 행동을 입력으로 화면으로 출력되는 큰 흐름이라고 한번 생각해 보면 하나의 거대한 함수를 떠올릴 수 있을 거에요.

UI = action(state)

pipeline of input -> output

함수형 프로그래밍의 관점에서는 함수는 콜스택의 개념보다는 데이터의 단방향 흐름으로 바라보는게 맞습니다. 특히 명시적인 입력과 명시적인 출력을 가지는 순수함수들의 결합은 콜스택이 깊어진다한들 이들의 조립은 결국 거대한 입출력을 가지는 하나의 함수가 되는 것이며 데이터는 아래 그림과 같이 입력의 값이 출력의 값으로 전달되는 파이프라인의 형태를 띄게 됩니다.

함수에서 함수를 콜을 하는 것이지만 계층적인 구조라기 보다는 결국 데이터의 Input -> output 이라는 흐름으로 바라볼 수 있어야 합니다.

함수의 조립은 결국 거대한 함수를 1개 만들어 내는 것입니다.

이러한 개념을 바탕으로 지금까지 예시 코드를 바탕으로 파이프라인을 그려보는 연습을 해보도록 하겠습니다. 최종 출발은 사용자의 액션이 되고 최종 Output은 화면이 됩니다. 이제 FE 개발에서는 웹 프레임워크를 사용하는 것이 당연한 수순이며 화면을 바꾸기 위해서는 DOM이 아니라 데이터만 있으면 되기 때문에 DOM과 관련된 함수를 제외하도록 하겠습니다.

사용자의 액션(INPUT) -> -> 화면에서 바뀌어야 할 데이터

와 같은 형태의 파이프라인을 그려보도록 하겠습니다.

과제 진행 순서

  1. 맨 왼쪽에 사용자의 행동과 인자를 배치합니다.
  2. 맨 오른쪽에 화면에서 변경되어야 할 데이터를 적어봅니다.
  3. INPUT -> f -> OUTPUT 의 개념으로 중간에 있어야 할 함수와 데이터를 적절히 배치하고 연결해보세요.
  4. 최종적인 파이프라인이 그려졌다면 유사한 성격을 가지는 계층을 만들어 시각적으로 구조를 이해해보세요.

예시 화면

  1. 사용자의 행동과 만들어야 할 데이터를 중심으로 INPUT -> OUTPUT의 파이프라인을 그려봤습니다. 어떠한 흐름으로 프로그래밍이 구성되는 모양인지 눈으로 확인해주세요.

  1. 위 파이프흐름을 유사한 계층으로 묶어서 그려보았습니다. 흐름과 계층을 복합적으로 이해해주세요.

이 역시 마찬가지로 꼭 본인의 코드를 가지고도 연습을 해보는 시간을 가져보시기 바랍니다. 결과를 보고나면 쉬워보이지만 막상 해보지 않으면 배웠고 안다는 착각 속에서 모르는 사람이 됩니다.

이걸 보고 나면 너무 당연해보이는 것들이지만 전혀 모르는 상태에서 시행착오를 겪어가면서 접하지 않으면 마치 우리가 지식 유투브를 보고나서 똑똑해지고 뭔가를 안거 같지만 돌아서면 그런 착각만 남고 아무 기억도 남지 않는 상태가 됩니다. 그렇기에 오늘 겪었던 시도들을 바탕으로 제가 전달 드리는 이 내용들이 훨씬 더 와닿고 내 것이 되기를 바랍니다.

1부의 내용은 여기까지입니다. 1부는 함수형 프로그래밍이 필요한 이유 그리고 함수형사고와 설계 그리고 계층적 구조와 흐름에 관한 이야기였습니다. 일반적으로 함수형 프로그래밍을 검색하면 나오는 커링, 파이프, 일급함수와 같은 기법들은 2부에서 진행할 예정입니다.

함수형 프로그래밍을 본격적으로 사용하기 위해서는 기법을 배우는 것이 중요하겠지만 맨 처음 언급했던 실무에서 자바스크립트를 잘 작성하고 좋은 코드를 만들기 위한 관점을 익힌다는 측면에서는 지금까지의 학습으로도 충분히 적용할 수 있을 거라고 생각합니다.

수고 많으셨습니다!!

끝으로...

우리는 지금까지 함수형 프로그래밍 패러다임을 이해하기 위해 달려왔습니다.

*패러다임이란 세상을 바라보는 관점입니다.
"천동설"과 "지동설"은 같은 현상을 다른 패러다임으로 바라본 것이며
"물이 반이나 있네" vs "물이 반밖에 없네" 역시 같은 현상을 다른 패러다임으로 바라본 것입니다.

객체지향에서의 패러다임뿐만 아니라 함수형 관점에서 프로그래밍을 바라 보는 관점을 익힌다면 우리는 더 좋은 프로그램을 만들 수 있게 된다고 알려드렸습니다.

1주차 ~ 2주차

우리는 프로그래밍을 작성할 때 "함수형"이라는 관점을 이해하기 위한 토대가 되는 개념과 지식을 배웠습니다.

이론

액션 - 계산 - 데이터라는 개념을 이해했고

  • 왜 액션에서 계산을 분리해야하는 지
  • 액션에서 계산을 분리하기 위해서 중요한 개념인 암묵적인 입출력과 명시적인 입출력에 대해 이해했습니다.
  • 명시적인 입출력의 중요한 조건이 되는 외부세상에 영향을 미치지 않기 위한 불변성을 배웠습니다.
  • 불변성을 만들기 위한 카피-온-라이트와 방어적 복사에 대해서 배웠습니다.

실습

  • 함수형 개념이 잘 적용되지 않던 코드의 테스트 코드를 직접 만들어보며 테스트 하기 힘든 코드가 무엇인지 이해했습니다.
  • 체계가 없던 코드를 함수형 개념인 액션과 계산으로 분리해보는 리팩토링을 해보았습니다.
  • 계산으로 분리된 코드는 확실히 테스트를 하기 쉽다는 것을 느꼈습니다.

3주차 ~ 4주차

함수형 프로그래밍의 함수간 계층을 이해하고 코드를 시각화하여 이해하는 연습을 해보았습니다.

이론

  • 함수형 프로그래밍의 계층적 구조에 대해서 배웠습니다.
  • input -> ouput의 파이프라인의 대한 개념에 대해서 배웠습니다.

실습

  • 함수의 콜 스택을 중심으로 계층을 통해서 코드를 시각적으로 배치해보기
  • 각 계층에 이름을 붙여보면서 나의 코드의 감각체계를 더 풍성하게 만들도록 느껴보기
  • 잘 짜여진 계층의 아름다움과 잘못 짜여진 계층의 부자연스러움을 느끼며 리팩토링으로 연결하기

함수형 프로그래밍에 대한 개론이나 이론적 설명들은 블로그글을 통해서 전달한 바 있으나, 그 글이 실제로 함수형 프로그래밍을 익힐 수 있는 방법들을 제시하고 있다고 생각하지는 않았습니다.

이론을 아는 것과 별개로 실제로 그것을 익히고 써먹기 위해서는 실제로 코드를 작성하면서 그에 맞는 피드백을 하고 체득하는 과정에서 깨닫고 이해하고 성장하는 것이기에 때문에 코치를 하는 과정과 실습을 하는 과정을 글로 남기는 것은 개인차가 있고 환경과 상황이 다르다보니 도움이 되도록 정리를 하는 것이 참 어려웠습니다.

이번에 이렇게 좋은 기회가 되어 직접 많은 사람들과 함께 스터디를 운영하면서 알려주고 실습하고 피드백을 주고 받는 과정을 겪으면서 공통적으로 알려 줄 수 있고 공유할 수 있는 내용들이 이렇게 글로 정리되게 되어 너무 좋습니다.

이 글이 어렵고 막연해보이는 함수형 프로그래밍에 대해서 조금 더 해볼만하다고 느낄 수 있게 하고 직접 해보면서 배울 수 있는 방법이 제시되는 글이었기를 바랍니다.

감사합니다.

좋은 하루 되세요 :)

profile
AdorableCSS를 개발하고 있는 시니어 프론트엔드 개발자입니다. 궁금한 점이 있다면 아래 홈페이지 버튼을 클릭해서 언제든지 오픈채팅에 글 남겨주시면 즐겁게 답변드리고 있습니다.

5개의 댓글

comment-user-thumbnail
2023년 1월 13일

같은 시기에 같은 책을 가지고 스터디를 진행하셨네요 ㅎㅎ 글 하나에 책 한권 내용이 망라되어 있다보니 훑어보며 내용 떠올리기 좋았습니다 :)

1개의 답글
comment-user-thumbnail
2023년 1월 26일

나머지 2~4부 스터디 내용도 포스팅 해주실 수 있나여 테오?ㅎㅎ 포스팅 내용이 넘 유익하고 알찹니당 혼자 함수형 프로그래밍 스터디하는데 귀한 글 작성해주셔서 고맙습니다!🙇🏻‍♀️

1개의 답글
comment-user-thumbnail
2023년 2월 5일

글 잘 읽었습니다!

답글 달기