함수형프로그래밍

Benza·2022년 4월 28일
0

🔎

목록 보기
5/11

프로그램의 모든걸 함수로 실행한다

함수는 인풋을 받아 아웃풋을 낸다.
고로 인풋과 아웃풋 데이터 흐름을 고려해야 한다.

명령형에서처럼 객체가 어떻게 상호작용하고 조작하는지 등 단계별로 생각을 하는것과 다르다

/**
* Non funtional
*/

var name = "Jonas"
var greeting = "Hi, I,m "

console.log(greetring + name)

"Hi, I'm Jonas"

명령형에서 사고 흐름

  • 먼저 이거, 다음엔 이거 순서가 있다

인풋과 아웃풋의 개념으로 표현하지 않았다.

/**
* funtional
*/

function greet(name) {
  return "Hi, I'm " + name
}

greet("Jonas")

"Hi, I'm Jonas"

순수 함수만 사용해 부작용 피하기 (Avoid side effects use "pure" function)

부작용이란 함수가 주어진 인풋에서 아웃풋을 계산하여 반환하지 않는 상황을 의미한다.
예를 들자면 콘솔에 무언가를 print 하는건 아웃풋 반환이 아니다. 함수와 관련된 다른 작업이다.

또한, 아웃풋 계산에 전역 변수를 사용하는 함수라서 인풋에 의존하지 않아도 된다면 순수 함수가 아니다.
함수 외부의 것을 가지고 함수 결과에 영향을 주기 때문이다.

그러므로 부작용이 없는 순수 함수란 동일한 인풋을 받고 아웃풋을 낼때 항상 동일한 결과를 내야한다.

/**
* Not Pure
*/

let name = "Jonas"

const greet () => {
  console.log("Hi, I'm "+ name)
}

위의 코드는 전역변수 name이 사용되고 인풋은 없다.
아규먼트가 없고 전역상태에서 뭔가를 읽어 오고 있다.
또한, 함수의 반환값이 아니라 순수하지 않다.
순수함수는 반환값 대신에 뭔가를 바꾸는 것이다.

/**
* Pure
*/

const greet (name) => {
  return "Hi, I'm " + name
}

위 코드에서 아웃풋에 영향을 주는 건 인풋 뿐이다.
우리가 넘겨주는 매개변수는 아웃풋을 반환하게만 한다.

함수형 프로그래밍에서 가장 중요한 포인트는 최대한 순수하게 생각해야 한다는 것이다.

고차함수(higher order function) 사용하기

고차 함수는 다른 함수를 인풋으로 받거나 아웃풋으로 반환하는 함수를 말한다.
즉, 함수를 일종의 객체로 취급하는 것이다.
그러므로 함수 내에 함수가 있는 레이어를 만들수 있다.

const makeAdjectifier(adjective){
  return function (string) {
	return adjective + " " + string;
  };
}

const hotifier = makeAdjectifier("Hot");
coolifier("Pizza");

Hot Pizza

makeAdjectifier()는 함수를 반환한다. 즉, 고차함수이다.

이런 고차함수의 특징은 함수형 코드의 중요한 부분이므로 염두 해야한다.

Iterate(for, while)을 피하고 map, reduce, filter를 사용하자

map, reduce, filter와 같은 고차함수를 사용해야한다.
인풋으로 원하는 방식의 리스트만이 아니라 함수도 받는 함수이기때문이다.

map과 reduce를 간단하게 이해하는 예제로 mapReduce sandwich가 있다.

![[Pasted image 20220428155627.png]]

데이터 리스트가 있다고 가정할 때 (위그림에서는 빵과 채소 원재료)
이 데이터를 어떤 형태로 바꾸고 싶을 것이다. (다듬어서 포션이된 야채와 빵)

map을 사용하여 우리가 빵 데이터를 '썰기', '자르기' 함수로 원하는 형태를 만들 수 있을것이다.
reduce를 사용하여 샌드위치를 만들 수 있다.
또한 추가로 filter를 사용하여 오이를 뺀다던지 할 수 있다.

이렇게 목록의 각 항목에 고차함수를 적용할 수 있다.

재료목록.map(자르기).filter(🥒,야채빼기).reduce(조합)

고차함수를 사용하므로 for나 while을 반복을 피할 수 있다.

mutability(변하기 쉬움) 피하기

mutability란 객체 위치 변경을 의미한다
불변(immutable) 데이터라고 하믄, 위치를 변경할 수 없는 데이터를 뜻한다.
일단 고정하면 절대 바뀌지 않는다.

/**
* Mutation case
*/

let rooms = ["r1","r2","r3"]
rooms[2] = "r4"

console.log(rooms)

["r1","r2","r4"]

room[2]를 다른 데이터로 대체했을 때 원형 데이터가 바뀐다.
이는 함수형 프로그래밍에서 피하는 방식이다.

/**
* Immutation case
*/

const rooms = ["r1","r2","r3"]

const newRooms = rooms.map((rm) => {
	if(rm === "r3") return r4
	else return rm
});

console.log(newRooms)
console.log(rooms)

["r1","r2","r3"]
["r1","r2","r4"]

map에 함수를 주어 newRooms 변수를 지정해 새로운 배열을 만든다.
그리고 rooms은 원형 데이터를 유지한다.

원형데이터를 유지함으로써 error가 생기는걸 막을 수 있다.

효율적인 불변(efficient immutability)을 위한 영속 데이터 구조(persistent data structures)

불변성의 문제는 배열 같은 데이터들이 계속 사본이 발생한다는 것이다.
리스트에서 room 하나를 바꾸려고 해도 새로운 배열 전체 사본을 만들어야한다.
객체가 크고 복잡할수록 효울은 떨어질 것이다.
공간도 많이 차지하고 error 노출도 커지게 될 것이다.

이를 해결하기 위해 함수형 프로그래밍에서는 영속 데이터 구조(persistent data structures)를 사용한다.

이 이론은 Phil Bagwell의 이상적 해시트리(ideal hash tree)라는 논문에서 시작된다.
그리고 Clojure를 개발한 Rich Hickey가 Bagwell의 아이디어를 data structure에 도입했다.
Clojure의 함수형 작업을 불변 데이터 구조로 효율적으로 만들었다.

영속 데이터 구조(persistent data structures)는 어떻게 돌아갈까?

배열 r1, r2, r3가 있다고 가정해보자
위의 예시와 같이 세번째 배열의 요소를 r4으로 바뀐 새로운 배열을 만드는 작업을 할 거이다.

기존의 방식은 이러하다.
새로운 배열을 만들어 요소들을 복사한다.
바뀌는 부분은 복사하는 대신 r4를 넣는다.

이를 대체하기 위해 배열을 트리로 표현한다.
트리의 각 리프 노드는 저장하려는 항목이 된다.
특정 값을 바꾸고자 할때 새로운 노드를 만들어 새 요소에 연결한다.
이렇게 data structure를 이용하므로 기존 구조와 데이터를 유지하고 필요한 것만 새로 추가할 수 있다.
이를 구조 공유(structural sharing)라고 한다.
기존 버전과 새 버전을 부분적으로 공유해 뭔가를 추가, 변경, 이동하는 작업이 더 효율적으로 동작할 수 있다.
이러한 방법은 map, hashmap에도 사용 가능하다.

영속 데이터 구조를 도와주는 여러가지 라이러리가 있다.

https://openbase.com/categories/js/best-javascript-immutable-libraries

대표적으로 facebook에서 발표한 immutable.js 가 있다.

출처(참고문헌)

profile
Understanding the impression

0개의 댓글