FP(Functional Programming) - monad

cfop·2020년 10월 10일
3

이 글은 공부한걸 나름대로 정리 한겁니다. 정답이 아닙니다. 잘못된 부분이 있을 수도 있습니다.

monad

monad는 무엇인가

monad는 endofunctor 입니다.

M<T> -> f: (T->U) -> M<U> // 자기 자신으로 사상(maping) - Fuctor의 일종!
M<Number> -> f: (Number-> String) -> M<String>

함자(functor)에 대해서는 https://en.wikipedia.org/wiki/Functor 여기 가서 자세히..
수학적인 얘기를 알아야만 하는건 아니지만 교양으로라도 살짝 이해하고 있음 좋지요

array 같은게 큰 예이다
["a", "b"]
.map(()=>{}) // Array<T>
.filter(()=>{}) // Array<T>
.reduce(()=>{},"") // Arrray<T>

Array<T> -> map : map -> map : filter -> map : reduce -> Array<T> ..
타입을 지켜 줌으로써 함수를 연결해서 실행할 수 있게 한다.

그래서 어쩌란건가

const a = 100;
const NumberToString = (number) => {...} // Number -> String
const b = NumberToString(b).length // 3 

이렇게 하면 되는거 아닌가? 굳이 복잡하게
아래와 같은 상황 이라면?

const a = await getDataFromServer(); //a가 항상 100이라는 보장이 있나.
const NumberToString = (number) => {...}
const b = NumberToString(a).length // ? 에러가 날 수도 있다.

이 코드를 풀어서 써보면 이렇다
"Number 세상에서 String 세상으로 data를 넘긴다.
String 세상으로 넘어간 data는 String 세상에 있는 함수를 쓸 수 있다."

그러나 이것도 약간의 예외처리를 하면 될 것 같다.
또 아래와 같은 상황을 고민 해보자

const a = await getDataFromServe();
const parsing : Array<String> = (String) => {...}
const mergeFirstAndLast : String = (Array<T>) => {...}
const b = mergeFirstAndLast(parsing(a));
.. b를 가지고 string 처리 로직

데이터를 가져와서 파싱을 하여 Array을 만들고
처음과 끝 데이터를 merge 한다고 하면 다양한 상황에서 에러가 나 빨간 에러 메시지를 볼 수 있다.

  1. a는 string이 확실한가 -> 아니면 error남
  2. parsing은 Array이 확실한가,
    파싱 조건이 안맞으면 Array이 아니다 -> error남
  3. b는 string이 아닐 수도 있지 않나->그 다음 과정에서 error남

아직도 예외처리 잘 하면 무사히 넘길 수 있다고 생각 할수 있긴 하지만 여간 피곤할 것 같다.
왠지 코드가 if문으로 도배 될것만 같은 직감이 든다.
수정 할 때 1, 2, 3을 따로 분리해서 생각할 수 없기 때문에 조심히 수정 하여야 한다.

이 과정을 중간에 데이터가 잘못되든 잘 되든 끝까지 실행을 완주 시키고, 단계를 분리 할 순 없을까?
아래와 같이 생각 해보자

M<T> -> map: parsing -> map : mergeFirstAndLast -> map : b string process.. -> ...

M에는 값이 있는 Some과, 값이 없는 None 타입만 존재 한다(Maybe monad의 얘기이다)고 하자.
그래도 이 모든게 M 인터페이스를 공유한다. 아래와 같이 풀어 쓸 수 있다.

M<T> 
-> 생성 할 때 값이 숫자면 Some, 다른 경우는 None 
-> map: parsing 
  -> Some이면 진행
    -> 결과가 Array<String>인가? -> Some 리턴
    -> 결과가 Array<String>이 아닌가? -> None 리턴
  -> None이면 진행X : 끝
-> map : maergeFirstAndLast
  -> Some이면 진행   
    -> 결과과 String인가? -> Some 리턴
    -> 결과가 String이 아닌가? -> None 리턴
  -> None이면 진행X : 끝
-> map : b string process..
  -> Some이면 진행
  -> None이면 진행X : 끝
  
  코드는 이렇게 될 것이다
  
  Maybe(a).map(parsing).map(mergeFirstAndLast).map(bStringProcessing)

이렇게 구현 되어 있다고 하면 값이 어떻든 간에 에러 나지 않고 진행이 된다.
게다가 parsing, mergeFirstAndLast, b string process.. 가 분리되어
각각 따로 뗴어놓고 로직만 수정하면 된다.(타입은 당연히 맞춰줘야 겠지만..)
각 함수가 데이터가 아니라 타입에만 맞춰서 그 기능만 수행되기 때문에
더 안정성이 올라간다고 볼 수 있다. 문제가 생긴 곳을 찾아가 로직만 바꾸면 되기 때문 이다.

로직이 함수 단위로 분리 되기 때문이 이게 어떤 함수 파이프라인을 따라 진행 되는지도 명확히 파악하기 더 수월할 것 같다는 생각이 든다.

profile
dog발자

0개의 댓글