코딩쌀롱

2021.1.19(tue)_함수형 프로그래밍을 해보면서 했던 고민들 본문

회고/하루 기록

2021.1.19(tue)_함수형 프로그래밍을 해보면서 했던 고민들

이브✱ 2021. 1. 19. 21:51

 

함수형 프로그래밍에 대해 공부하면서 고민했던 과정들을 정리해보려고 한다.

1. 클래스를 사용해야 할까? 

처음에 했던 생각

 ➙ this.prop은 클래스 내 속성으로 함수에서 사용하면 외부 변수를 사용한 게 된다. 순수 함수를 작성하기 위해서는 외부 변수를 사용하면 안 되므로, 클래스 내 속성을 함수의 인자로 받는 것으로 모두 바꿨다. 속성을 사용하지 않게 되면서 굳이 class의 사용 필요성을 못 느껴 클래스를 없애고 함수표현식만으로 작성했다.

 

더 생각해보면,

 ➙ 클래스 내에 다른 메서드를 사용할 경우 this를 쓸 수 밖에 없는데, this를 쓴다는 것은 인스턴스를 참조한다는 것이고, 이 자체가 외부 변수를 사용한다는 것이 되므로, 클래스 내 메서드에서 순수함수를 만든다는 것은 쉽지 않을 것 같다. 메서드 내에서 다른 함수나 속성을 사용하지 않고, 인자로 input, 반환값으로 output하면 가능하겠지만 이것만으로 클래스 내 메서드를 모두 쓰긴 쉽지 않을테니까.

 

2. 외부 함수를 사용하려면 함수도 모두 인자로 받아야 할까? 

처음에 했던 생각

 ➙ 미션을 풀면서 가장 혼란스러웠던 부분이다. 함수형 프로그래밍에서 순수 함수는 가장 중요한 키워드라고 할 수 있다. 함수가 input만 받아, input만 사용해 output을 계산해서 반환하는 것, output에 영향을 주는 건 오직 input이어야 하는 것..그러니까 외부 변수는 사용하면 안 되는데, 외부의 함수를 사용할 때도 인자로 다 받아야 하느냐 말이다. 이 부분을 찾아보려고 검색을 해도 딱히 나오는 건 없었다.  그래서 사용하는 외부의 함수가 순수 함수라는 전제 하에 사용했다. 

 

 혼자만의 생각으로 넘어갔는데, 이후에 더 생각해봤다. 슬랙에 다른 분들의 대화를 보면서 확실히 정리할 수 있었다.

 ➙ 외부의 함수를 사용했을 때의 고민은 '외부 함수가 순수 함수라고 하더라도 바뀔 가능성이 있다면 그것을 그대로 쓰는 게 문제가 될 수 있지 않는가'하는 부분이다.

 (Isaac님 의견 옮김) 함수형 프로그래밍을 통해 막을 수 있는 사이드 이펙트가 '런타임 중에 외부 변수가 변경됐을 때 그 영향으로 함수가 의도와 다르게 동작하는 경우'라고 했을 때, 가져다 쓰는 외부 함수가 런타임 중에 변경될 일이 없다면, 내부에서 써도 될 것 같다. 매개변수로 넘겨주던 내부에서 가져다 쓰던 개발자가 함수를 변경함으로써 발생하는 상황이나 해킹으로 함수가 변경되는 상황은 막을 수 없으니까. 써도 되지만 가져다 쓸 함수가 변경될 일이 없게 하기 위해서 함수 표현식으로 쓸 때 변수의 재할당을 막기 위해 const를 사용하자.

 

 

 3. 어떻게 짜야 함수형 프로그래밍이지? 

 ➙'순수 함수', '불변성'은 이해가 돼서 이 부분들은 최대한 맞춰서 구현하려고 했다. 명령형이 아닌 '선언형 함수'를 사용하는 부분이 좀 이해가 잘 안 됐는데 더 공부해야겠다. 내가 지금까지 명령형으로 코드를 짜왔다는 건 확실히 알겠다. 지금까지 이해한 바로는 input, output이 명확하고 같은 input에는 항상 같은 output을 반환하는 순수 함수는 변수처럼 a는 b다라고 선언할 수 있다는 것?으로 이해했는데 그래서 순수함수랑 선언형 함수랑 뭐가 다른거지? 더 공부해야겠다

 

4. pipe함수를 사용하면서 바보같은 실수로 go함수까지 배움 

 ➙ 함수형 프로그래밍을 찾아보다 pipe함수에 대해 알게 됐다. 그래서 함수들을 매개변수로 받는 pipe 함수를 만들어 앞 함수의 output이 다음 함수의 input으로 계속 연달아 실행하는 함수를 반환하도록 했다.

 

처음에는 pipe함수를 실행시키고 return을 안 적었다. 그러니 당연히 undefined 반환이 된다.

return값 없는 함수는 undefined를 반환하니까. 근데 나는 return을 안 적어놓고 왜 undefined가 나오는지 한참을 씨름했다. 아니 바본가 까먹을 게 따로 있지 return을 까먹어...?🤷🏻‍♀️ 미친다 진짜

 

이걸 블로그 정리하는 지금에서야 알았다. return을 앞에 쓰면 값이 반환이 안 될 수가 없는데?라고 생각해서 해보니까 되네..당연히 되겠지.. 그래서 방금 다시 gist랑 코드를 수정했다. 

 

 그럼 어제는 어떻게 해결했었느냐. go함수를 만들어 사용했다. 처음에는 isPerfect함수 안에 let result 변수 선언해서 pipe의 마지막 파라미터의 함수 내에서 result 변수에 값을 저장하고 isPerfect 마지막에 return result했다. 이렇게 해서 구현이 되긴 했는데 당연히 지저분해진다. 화살표 함수로 한 줄에 쓸 수 있는 것들도 못 쓰게 되고..

 

 그래서 go함수를 사용했었다. go함수는 첫 번째 파라미터로 인자를 받고, 두 번째 파라미터부터는 함수들을 받는다. pipe함수와의 차이점은 첫 번째 파라미터로 인자를 받는다는 것, 그리고 실행시킨다는 것. pipe(m1, m2, m3)(num)이렇게 실행을 한 번 더 하지 않고, go(num, m1, m2, m3)만 하면 된다. 

 즉, return pipe(m1, m2, m3)(num)return go(num, m1, m2, m3)와 같다. 어제는 pipe앞에 return 값을 안 써놓고 안 되길래 "이유는 아직 모르겠지만 go 함수 써야지~"라고 생각했다. 오늘 정리를 하면서 왜 안 됐었지를 생각해보니까  내 실수였다. 

 

 아무튼 그래서 go함수는 필요 없기 때문에 go함수는 없앴고, pipe함수만 사용하는 걸로 방금 고쳤다. 어이없는 실수에 당황스럽지만, 다신 실수하지 않겠다는 것과 go 함수를 배웠다고 생각해야지.

 

 그리고 pipe함수는 함수를 반환하기 때문에 자주 쓰이는 함수 묶음들을 변수에 미리 저장해놓을 수도 있고, 연속된 함수들을 실행시킬 수도 있다. go함수는 반환값 없이 실행시키는 함수이기 때문에 미리 저장하는 것은 못 한다. 

 

5. pipe 함수의 파라미터로 들어가는 함수들이 필요로 하는 인자의 개수가 다를 때는 커링함수!! 

 ➙ 코어 자바스크립트를 공부하면서 커링함수와 클로저를 내가 직접 써볼 수 있다면 정말 좋겠다라고 생각했다. 이번 미션에서 쓸 수 밖에 없는 상황이라 구현하게 됐는데 매우 뿌듯하다. 물론 과정이 쉽지 않았다. 

 

커링함수를 쓸 수 밖에 없는 상황에 대해 설명하자면,

line 함수는 pipe함수의 반환값인 여러 함수를 이어 실행시켜주는 함수를 반환한다. 반환된 함수는 인자인 num값을 func1의 input으로 넘겨주고 반환값인 output이 func2의 input이 된다. 이렇게 연달아 func4까지 실행시킨다. 이렇게 의도대로 실행되면 좋겠지만, func1~func4가 필요로 하는 input이 하나가 아니라면? *output은 무조건 하나다. 그러므로 위와 같은 경우에는 input이 하나일 때만 의도대로 실행될 수 있다. 

*두 값을 담은 배열 arr를 넘겨주고 다음 함수에서 배열을 input으로 받고, 함수 내부에서 arr[0] , arr[1]의 값을 변수에 할당해서 사용할 수도 있긴 하지만 추가적인 변수 할당을 필요로 하므로 일단 제외했다.

 

✱ 커링 함수

func2가 func1의 반환값과 line 함수의 인자인 num, 이 두 인자를 input으로 받아야 한다. 그래서 func2를 커링함수로 작성했다. 아래의 함수들은 미션과 상관없이 예시로 적었다. num은 line 함수의 인자, arr는 func1의 output이라 가정.

func2(num)을 하면 arr를 인자로 받는 함수를 반환한다. func1의 함수가 arr를 output으로 넘겨주면 그 값만 input으로 받으면 된다. 그리고 filter 콜백함수에서 num변수를 사용하는데 이는 클로저다. (클로저 원리 이해 글 링크)

위처럼 pipe함수 안에서 fun2에 num을 인자로 넘기면서 호출하면, arr를 인자로 받는 함수가 그 자리에 남아 func1에 이어 실행할 수 있다.  

 

✱ bind 메서드

커링함수말고 부분적용함수의 성격을 갖는 bind 메서드를 사용해서 해결할 수도 있다. (부분적용함수 글 링크)

line함수는 아래 코드와 같이, func2 함수는 arr 인자 하나만 받는 함수이고, 화살표 함수이면 안 된다! 화살표 함수는 prototype 프로퍼티를 갖지 않기 때문에 bind, call, apply와 같은 메서드를 사용할 수 없다. 

 bind도 함수를 반환한다. 첫 번째 인자값으로 this를 바인딩하고 나머지 인자값들을 인자로 받은 함수를 반환한다. 그러니까 line 함수는 위의 커링함수 때와 똑같이 동작한다.

 

하지만  bind 메서드를 사용하려면 화살표 함수를 사용할 수 없고, this를 굳이 바인딩 해줘야 한다. 그리고 이는 bind 메서드의 원래 목적(this 바인딩)과도 다르게 활용하는 방법이기도 하다. 가장 중요한 단점은 순수 함수가 아니게 될 수 있다. 예시에서는 output에 영향을 끼치지 않지만, func2의 this를 다른 함수인 line 함수에서 바꾸는 것이므로 상황에 따라서 output에 영향을 주는 요인이 될 수 있다.

 


원래는 1, 2번 내용만 고민들로 적으려고 했는데 pipe함수, 커링함수를 사용하면서 어려웠던 것들, 생각했던 것들도 정리하고 싶어서 적다보니 글이 엄청 길어졌다. 그래도 정리하면서 머릿속도 정리된 것 같다.

 

혹시 제가 잘못 생각한 부분이 있다거나 보충해주실 의견이 있다면 댓글로 적어주신다면 정말 감사하겠습니다🙇🏻‍♀️

 

 

'회고 > 하루 기록' 카테고리의 다른 글

2021.2.23(tue)_express 싸우자🤬  (2) 2021.02.23
2021.2.22(mon)_express, sass 설치  (3) 2021.02.23
2021.2.10(wed)_공부할 키워드  (0) 2021.02.09
2021.1.15(fri)  (0) 2021.01.15
Comments