모나드가 중요한 이유?
- 모나드는 사이드 이펙트를 결과 집합과 함께 포함하는 타입
- 함수형 프로그래밍에서 사이드 이펙트가 생기는 경우를 방지하기 위해 사용
f:FileName→FILE 인 경우 file이 존재하지 않는다면 에러를 발생시킨다.
Error의 값을 다음 함수로 전달 하는 것은 다음 함수의 인자와 맞지 않기 때문에 순수 함수성이 깨진다고 볼 수 있다.
(다음 함수는 file이 올거라고 기대하기 때문)
하지만, 모나드를 사용하여 f:FileName→Monad[FILE,Error]와 같이 file과 에러를 같이 포함하는 모나드를 만든다면
다음 함수는 모나드를 받아 처리하고, 그 함수는 또 모나드를 반환한다면 순수 함수성을 지킬 수 있다.
- 사이드 이펙트가 생긴다면 순수 함수의 특성이 깨지고, 함수의 합성이 어려워짐
- 함수의 값을 모나드로 받고, 실행 결과도 모나드가 된다면 순수 함수가 되고 함수의 합성을 통해 흐름을 만들 수 있음
Functor
- 인자를 받아 Functor를 반환
- map 메소드를 가지고 있음.
function Maybe(value) {
this.value = value;
this.map = function (fn) {
return this.isNothing ? this : Maybe.of(fn(this.value));
};
}
Maybe.of = function (value) {
return new Maybe(value);
};
Object.defineProperty(Maybe.prototype, 'isNothing' , {
get: function(){
return this.value === null || this.value === undefined;
}
});
- Maybe functor 예제코드
const log = console.log;
const curry = (f) => (a, ...bs) => (bs.length ? f(a, ...bs) : (...as) => f(a, ...as));
const curryR = (f) => (a, ...bs) => (bs.length ? f(a, ...bs) : (...as) => f(...as, a));
function reduce(f, iter, acc) {
if (arguments.length === 2) {
iter = iter[Symbol.iterator]();
acc = iter.next().value;
}
for (const e of iter) {
acc = f(acc, e);
}
return acc;
}
const go = (a, ...fs) => reduce((a, f) => f(a), fs, a);
function Maybe(value) {
this.value = value;
this.map = function (fn) {
return this.isNothing ? this : Maybe.of(fn(this.value));
};
}
Maybe.of = function (value) {
return new Maybe(value);
};
Object.defineProperty(Maybe.prototype, 'isNothing' , {
get: function(){
return this.value === null || this.value === undefined;
}
});
const map = curry((fn, functor) => functor.map(fn));
const books = [
{ id: "12", name: "the lord of the rings" },
{ id: "34", name: "harry poter" },
];
const findBookById = curry((id, books) => {
log("findBookById");
return books.find((book) => book.id === id)
});
const get = curry((key, obj) => {
log("get");
return obj[key];
});
const upperCase = (str) => {
log("upperCase");
return [...str].map(e => e.toUpperCase()).join('')
};
const _val = (functor) => functor.value;
go(
books,
Maybe.of,
map(findBookById("12")),
map(get("name")),
map(upperCase),
log
);
// 여기서 문제, 값에 예외가 있는 경우?
// get, upperCase에 모두 다 예외처리를 할 수 없다.
log("------------------------------");
go(
books,
Maybe.of,
map(findBookById("56")),
map(get("name")),
map(upperCase),
log
)
Monad
- Functor에서 flatMap이 추가
function Maybe(value) {
this.value = value;
this.map = function (fn) {
return this.isNothing ? this : Maybe.of(fn(this.value));
};
this.toString = function(){
return this.isNothing ? `Nothing` : `Just(${this.value})`;
}
this.flatMap = function(fn){
return this.isNothing ? this : fn(this.value);
}
}
Maybe.of = function (value) {
return new Maybe(value);
};
Object.defineProperty(Maybe.prototype, 'isNothing' , {
get: function(){
return this.value === null || this.value === undefined;
}
});
- flatMap을 통해 중첩된 Functor를 중첩된 구조를 풀어줌
- Maybe monad 예제코드
const log = console.log;
const curry = (f) => (a, ...bs) => (bs.length ? f(a, ...bs) : (...as) => f(a, ...as));
const curryR = (f) => (a, ...bs) => (bs.length ? f(a, ...bs) : (...as) => f(...as, a));
function reduce(f, iter, acc) {
if (arguments.length === 2) {
iter = iter[Symbol.iterator]();
acc = iter.next().value;
}
for (const e of iter) {
acc = f(acc, e);
}
return acc;
}
const go = (a, ...fs) => reduce((a, f) => f(a), fs, a);
const pipe = (...fs) => (a) => reduce((a, f) => f(a), fs, a);
const sum = pipe((a) => a+1, (a) => a+2);
const books = [
{ id: "12", name: "the lord of the rings", author: "J.R.R. Tolkien" },
{ id: "34", name: "harry poter", author: "J.K. Rowling" },
];
function Maybe(value) {
this.value = value;
this.map = function (fn) {
return this.isNothing ? this : Maybe.of(fn(this.value));
};
this.toString = function(){
return this.isNothing ? `Nothing` : `Just(${this.value})`;
}
this.flatMap = function(fn){
return this.isNothing ? this : fn(this.value);
}
}
Maybe.of = function (value) {
return new Maybe(value);
};
Object.defineProperty(Maybe.prototype, 'isNothing' , {
get: function(){
return this.value === null || this.value === undefined;
}
});
const map = curry((fn, functor) => functor.map(fn));
const flatMap = curry((fn, monad) => monad.flatMap(fn));
const findBookById = curry((id, books) => {
return books.find((book) => book.id === id)
});
// f:filename -> Monad[file,Error]
const getBookById = curry((id, books) =>
pipe(
findBookById(id),
Maybe.of,
)(books));
const validateBookAuthor = (book) => book.author.indexOf('Rowling') > -1 ? Maybe.of(book) : Maybe.of(null);
const logProp = curry((key, obj) => {
log(obj[key])
return obj;
});
// 함수가 functor를 인자로 받고 functor를 반환해버리면 functor의 value값이 functor가 된다.
go(
books,
getBookById("34"), // -> Maybe
map(validateBookAuthor), // -> Maybe(Maybe)
map(map(logProp("name"))),
)
// 해결책 functor.value로 풀어주면 된다.
// go(
// books,
// getBookById("34"),
// map(validateBookAuthor),
// functor => functor.value,
// map(logProp("name"))
// )
// 해결책2 flatMap
go(
books,
getBookById("34"), // functor
flatMap(validateBookAuthor), // functor
flatMap(logProp("name")),
log
)
Monad의 종류
- Optional/Maybe
- Either
- Future
참고