티스토리 뷰

해당 포스트는 Functors in JavaScript 를 번역하고 각색하여 작성되었습니다. 원문 링크는 본문 하단에 있습니다.

프로그래밍에서는 본질적인 데이터의 복잡성 때문에 논리적 정확성이 불가능할 때가 있습니다. 데이터를 추상화 시키는 것은 데이터의 단순한 표현을 만드는 데 도움이 되는 매우 유용한 도구입니다.

 

이를 위해 'Container' 를 만드는 방법이 있습니다. 'Container'는 오직 우리의 데이터만 가지고 있으며, 그 외에 다른 책임을 가지지 않습니다. 즉, OOP 처럼 'Container' 에 프로퍼티나 메소드를 제공하지 않습니다. 

 

변수를 가져오고 Container 안으로 넣습니다. 그리고 Container 는 함수형 로직을 통과시키는 동안 변수를 안전하게 지키며 필요할 때 즉시적으로 변수를 가져올 수 있습니다. 따라서 Container 는 아래의 두 가지 책임이 있다는 것을 알 수 있습니다.

 

  • 스스로 내부에 변수를 저장하고 있다.
  • 오직 우리가 필요로 할 때 변수를 되돌려 준다

또한 절대 컨테이너 내부의 값은 변경되지 않습니다.

 

함수형 프로그래밍을 할 때 컨테이너는 함수형 구조의 기초에 기여하고 순수 함수형 에러 핸들링과 비동기 액션등 일반적인 기술들을 지원하므로 매우 강력합니다. 하지만 이 컨테이너라는 개념은 전혀 새로운 개념이 아닙니다 이미 JavaScript 를 사용하면서 많이 접해 봤을 개념입니다.

 

컨테이너에 대해 자세하게 살펴보기 전에, Functor - 펑터라 불리는 특별한 타입의 컨테이너에 대해 간단히 설명하자면 'map' 함수와 함께 사용되는 컨테이너 입니다. 물론 이 말고도 다른 특징이 있지만 아래에서 자세하게 다뤄보도록 하겠습니다.

Arrays


배열은 프로그래밍에서 항상 사용될 정도로 가장 일반적인 컨테이너입니다. 배열은 모든 데이터 추상화 한것 중 가장 단순하지만 강력합니다.

const arr = [ 8, 10, 23, 35, 54 ];
const b = a[1]; // 변수를 이렇게 꺼내옵니다.
arr.push(45) ❌
arr[1] = 45 ❌
// 원본 배열을 수정하는 것 대신 아래와 같이 새로운 배열을 만들어 사용하세요.
const arr2 = [ ...arr, 38, 52 ]
const even = filter(x => x%2 === 0, arr)

 

함수형 프로그래밍에서 배열의 값을 변경할 수 있는 메소드를 사용해서는 안됩니다. 값을 가져오기만 하거나 배열의 수정이 필요하다면 새로운 배열로 만들어야 합니다.

 

위에서 펑터는 'map' 함수를 사용할 수 있는 컨테이너라고 이야기 했습니다. 하지만 더 자세하게 이야기 하자면 펑터는 단항 함수로 '매핑'할 수 있는 컨테이너입니다.

 

여기서 '매핑'이란 컨테이너가 담고 있는 모든 데이터에 단항 함수를 적용하고 그 결과 값으로 다른 컨테이너를 반환하는 함수 (ex: fmap, map)로 처리할 수 있음을 의미합니다.

배열의 경우 이 특별한 함수를 'map' 함수라고 부릅니다.

The Map Function

배열의 Map 함수는 배열을 가져오고 특정한 함수를 모든 요소 하나하나에 적용하고 그 결과값으로 새로운 배열을 반환합니다.

[1,2,3,4].map(multiplyBy2) 
//=> [2,4,6,8] 또는 map(multiplyBy2, [1,2,3,4]) 
//=> [2,4,6,8] where multiplyBy2 = x => x * 2 and map = (fn, arr) => arr.map(fn)

항상 map 으로부터 또 다른 배열을 가져오기 때문에 map 을 다시 연결 하여 여러 단항 함수를 매핑하는 과정을 수행하는 체인을 만들 수 있습니다.

[1,2,3].map(x => x * 3).map(x => x * 2).map(x => x / 6)

Map 함수는 이터레이터 함수 이상의 기능을 제공합니다. 값이 컨테이너 안에 있을 때 함수를 직접 적용 할 수 없으며 값이 변경 될 것으로 예상 할 수 있습니다. 예를 들어

const a = [1, 2, 3]
String(a) = ‘[1 ,2, 3]’
String(a) != [‘1’, ‘2’, ‘3’]

map 함수는 함수가 컨테이너 안의 값들에 접근할 수 있도록 해줍니다.

map(String, [1, 2, 3]) = [‘1’, ‘2’, ‘3’]

또한, map 함수는 절대 기존 컨테이너를 변경하지 않고 내부의 값에 따라 동작합니다.

map 은 컨테이너의 타입을 변경시키지 않지만 컨테이너가 담고 있는 내용의 타입은 변경할 수 있습니다.

 

컨테이너가 담고 있는 데이터의 타입은 바뀔 수 있습니다. 그리고 그 타입 변경을 map 함수의 정의를 통해 알 수 있습니다.

map :: (a -> b) -> [a] -> [b] or fmap :: (a -> b) -> F a -> F b 

여기서 a, b 는 같은 타입일 수도 있고 다른 타입일 수도 있습니다.

 

이제 간단하게 살펴보면 map 함수가 a -> b 에서 함수를 가져오고 Fa -> Fb 에서 함수를 반환 하는 것을 볼 수 있습니다.

여기서 a -> b 는 a를 가져와 b를 반환하는 단항 함수를 나타냅니다.

multiplyBy2(3) = 6 // is a -> b as 3 -> 6

그리고 Fa -> Fb 는 a 가 들어있는 컨테이너를 가져오고 b 가 들어있는 컨테이너를 반환합니다.

multiplyArrBy2([1]) = [2] // is Fa -> Fb as [1] -> [2], F is []

이에 대한 자세한 내용은 아래 글을 참조해 주세요

 

Function Type Signatures in Javascript

When a Javascript Developer starts to explore the deepest secrets of the Functional Programming, he often finds these weird arrow notations with type written above the functions and feels ‘What the hell is this?’. After all, he’s a master of dynamic typed

hackernoon.com

Functions

모든 함수는 Functor 이기 때문에 컨테이너 이기도 합니다.

컨테이너는 데이터를 가지고 있지만 함수들은 순수한 로직만 포함하고 있기 때문에 어떻게 함수가 컨테이너가 될 수 있는지 의문이 들 수 있습니다.

 

잘 생각해보면 함수는 호출되었을 때 변수를 반환합니다. 그래서 어느 방법으로든 변수를 가지고 있어야 합니다. 오직 다른 컨테이너와 다른 차이점은 그 변수들이 동적으로 계산된다는 것입니다.

aFunction(45) // => 90 So aFunction gives the value 90, when it is passed 45

함수를 무한한 숫자의 배열처럼 생각해봅시다. 그리고 이 무한한 숫자 중 특정한 변수를 원한다면, 특정한 인자와 함께 함수를 호출해야 합니다.

Functions are arrays with Infinite Values

배열이 인덱스가 전달되면 변수를 주는 것처럼 함수도 인자가 전달되었을 때 결과를 줍니다.

const a = [ 8, 10, 23, 35, 54 ]
const f = z => z * 2 a[1] = 10
f(2) = 4

배열은 그저 정해진 정수형 인덱스에 대한 결과값만 주지만, 함수는 어떠한 타입의 인자든 제한 없이 받을 수 있습니다. 또한 다른 함수를 인자를 받을 수도 있습니다.

 

함수는 무한한 값을 가진 컨테이너 입니다.

 

그럼 함수가 Functor 라면 map 함수도 가지고 있나요?

 

맞습니다. 함수에 대한 map 을 다음과 같이 정의할 수 있습니다.

const fnMap = (f, mappingFn) => (x => f(mappingFn(x)))

map 함수가 배열을 가져와 새로운 배열을 만드는 것과 마찬가지로 fnMap은 함수를 가져와서 그 함수의 결과에 다른 함수를 적용하고 두 함수를 결합하는 방식으로 새로운 함수를 반환합니다. 따라서 첫 번째 함수의 결과는 두 번째 함수의 인자가 됩니다.

 

이를 사용해보면 아래와 같습니다.

const multiplyBy6 = fnMap(multiplyBy2, multiplyBy3)

 

따라서 함수에도 map 함수가 있으며 한 함수를 다른 함수에 매핑하는 경우 두 함수를 융합하는 것입니다. 이를 Compose Function 이라고 부릅니다.

tl;dr

  • multiplyBy2 함수는 인자로 주어진 값에 2를 곱하여 반환합니다.
  • multiplyBy2 로부터 값을 꺼내거나 호출하기 전에 multiplyBy3 으로 매핑했습니다.
  • 따라서 multiplyBy2 의 결과 값들은 3이 곱해집니다.
  • 이제 multiplyBy2 를 호출 할 때마다. x 가 원본 값이면 x * 3 * 2 를 얻게 됩니다.
const fnMap = (f, mappingFn) => (x => f(mappingFn(x)))

함수는 데이터 추상화 타입인 배열과 같습니다. 그리고 함수는 데이터를 요구사항에 따라 연산하기만 합니다.

반대로 배열은 '[]' 를 사용하면 바로 결과를 알려주는 함수와 같다고 할 수 있습니다.

 

이 글을 통해 기억해야 할 중요한 점은 함수형 프로그래밍에서 절대로 데이터를 그대로 사용하지 말고 항상 컨테이너로 감싸서 사용해야 한다는 것 입니다.

 

마치며


함수형 프로그래밍의 Functor 는 컨테이너의 값을 매핑하여 또 다른 컨테이너를 만들어 낼 수 있는 특별한 컨테이너라는 사실을 알게 되었습니다. 사실 기존까지 매우 어렵거나 복잡한 개념이라고 막연한 두려움을 가지고 있었지만, 알고보니 이미 많이 사용되고 있던 개념이여서 싱겁게 느껴지기 까지 하는 것 같습니다.
요즘 영어로 된 문서를 번역하는 재미에 빠져 있어서 자꾸 번역 포스트만 작성하게 되는 것 같습니다. (영여공부도 하고 프로그래밍 공부도 하고 1석 2조..) 조만간 개인적인 이야기도 들고 오겠습니다.

원문


 

Functors in JavaScript

where F is a Functor “A mathematician, like a painter or a poet, is a maker of patterns. If his patterns are more permanent than theirs, it is because they are made with ideas.” — G.H. Hardy, A Mathematician’s Apology Sometimes, logical accuracy is not pos

hackernoon.com

댓글
댓글쓰기 폼
Total
491,241
Today
40
Yesterday
424
링크
«   2020/09   »
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      
글 보관함