: 사실 아직 IO 모나드의 실질적인 쓰임새?를 잘 모르겠다. 내가 Pipe와 Compose에 익숙해져서 굳이 이게 Pipe와 Compose와 뭐가 다른가 싶긴하지만, 결론적으로 단지 함수형 프로그래밍의 두가지 방법론인 함수의 합성과 체이닝 이 두가지 방법론에 따라 Pipe/Compose 와 IO 모나드를 알맞게 활용하면 되는게 아닌가 싶다. 지금은 이정도 이해가 내가 할 수 있는 최선이다.
: 특정 ID 값을 가진 DOM 객체를 가져와서 toUpperCase로 대문자로 바꾼 다음에 다시 해당 객체에 innerHTML에 덮어씌우는 형태의 로직을 짜보았다. 앞에서 말한대로 Pipe, Compose와의 차이를 알아보고자 두버전(IO 모나드를 쓴 버전, 합성 버전)을 모두 만들어봤다.
class IO {
constructor(effect) {
if (!_.isFunction(effect)) {
throw "IO 사용법: 함수는 필수입니다!";
}
this.effect = effect;
}
static of(a) {
return new IO(() => a);
}
static from(fn) {
return new IO(fn);
}
map(fn) {
let self = this;
return new IO(() => fn(self.effect()));
}
chain(fn) {
return fn(this.effect());
}
run() {
return this.effect();
}
}
// 의문점 : 결국은 IO 모나드랑 Pipe or Compose와 같지 않나?. 차이를 모르겠다..
const read = (document, selector) => () =>
document.querySelector(selector).innerHTML;
const write = (document, selector) => {
return (val) => {
document.querySelector(selector).innerHTML = val;
return val;
};
};
const readDom = _.partial(read, document);
const writeDom = _.partial(write, document);
const convertToUpperCase = (value) => value.toUpperCase();
const changeToUpperCase = IO.from(readDom("#student-name"))
.map(convertToUpperCase)
.map(writeDom("#student-name"));
// changeToUpperCase.run();
// of를 쓰면 시작부터 값을 넣어서 시작할 수 있고,
// from을 쓰면 특정 함수에서 어떤 값을 리턴하면서 시작한다.
// map을 쓰면 함수를 실행한 값을 map의 파라미터에 넣고, 그 함수를 this.effect에 보관한다.
// 이 때, chain을 쓰면(map 대신) 바로 실행하고 값을 리턴한다
// map을 쓰고 run을 쓰면 바로 값을 리턴한다.
// 위의 로직을 pipe로 만들어보면
const readPipeVersion = (document, selector) => () =>
document.querySelector(selector).innerHTML;
const writePipeVersion = (document, selector) => {
return (val) => {
document.querySelector(selector).innerHTML = val;
return val;
};
};
const readDomPipeVersion = _.partial(readPipeVersion, document);
const writeDomPipeVersion = _.partial(writePipeVersion, document);
const changeToUpperCasePipeVersion = pipe(
readDomPipeVersion("#student-name"),
convertToUpperCase,
writeDomPipeVersion("#student-name")
);
changeToUpperCasePipeVersion();
// 결국 이렇게 쓰면 pipe랑 IO 모나드랑 사실상 비슷한 것 같다고 생각이드는데..
// 체이닝과 합성의 차이인건가..?
// IO 를 할 때 명시적으로 나타내기 위함이라고 생각하는 것도 좋을듯
// 이에 더해, 앞서 말한 것처럼 함수형 프로그래밍 자체가 체인과 합성, 이렇게 두가지 방법으로 함수를 조합하기 때문에
// Pipe, Compose = 조합
// IO Monad = 체이닝
// 으로 이해해도 괜찮을 것 같다. 아니 사실 지금은 이렇게 밖에 이해가 되지 않기 때문에 이정도로 마무리한다.
// DOM API를 통해 Read, Write하는 것 자체가 부수효과의 여지가 있는 것이기 때문에
// 이런식으로 체이닝 혹은 합성으로 묶어버리면 부수효과는 당연히 일어나지만,
// 그 중간에 다른 여지를 끼워넣지 않는 다는 점에서 최대한 부수효과를 통제하는 방향으로
// 적어도 저 함수 changeToUpperCasePipeVersion or changeToUpperCase 를 호출하거나 run 하면
// 저 로직 대로 읽고 -> upperCase 로 바꾸고 -> write하기 때문에 순수함수 인 것처럼 흉내낼 수 있다.
정리한 내용은 주석에 달아놨다. 함수의 리턴문에 값을 넣어놓고, 다음 함수의 매개변수에 그 함수를 호출하여 얻은 리턴값을 넣어주는 형식.. 그냥 Pipe랑 다를게 없다. run() 을 통해 호출을 지연시킨다? Pipe도 결국 함수를 리턴해서 원하는 시기에 호출을 하기 때문에 똑같다. 체이닝과 조합의 차이라고 밖엔 아직은 생각이 들지 않는다.
체이닝 관련 배운 것(Either, Maybe, IO + lodash logics), 합성 관련 배운 것(Pipe, Compose + 함수 조합기들[alt, sequence, tap, fork etc]) 점점 이런걸 배워나가다 보니 체이닝과 합성을 같이 쓸수도 있을 것 같다는 생각이들고, 이미 한쪽을 쓰다보면 한쪽 안에서 또 융합을 해서 같이 쓰는 아이디어들을 짜게 되는 것 같다.