<T>(f: HKT<F, (a: A) => T>): (fa: HKT<F, A>) => HKT<F, T>
f:HKT<F, (a:A) => T>)
import * as O from 'fp-ts/Option';
const value1 = O.some(1);
const value2 = O.some(2);
const add = (a: number, b: number) => a + b;
const res = pipe(
[value1, value2],
O.fromNullable,
O.map((data) => {
const one = pipe(
data[0],
O.getOrElse(() => 0)
);
const two = O.getOrElse(() => 0)(data[1]); // pipe 사용 대신 직접 호출
return add(one, two);
})
);
expect(res).toEqual(O.some(3));
const curriedAdd = (a: number) => (b: number) => a + b;
const appRes = O.Applicative.ap(
O.Applicative.map(value1, curriedAdd),
value2
);
expect(appRes).toEqual(O.some(3));
// Array.js
var sequence = function (F) {
return function (ta) {
return _reduce(ta, F.of((0, exports.zero)()), function (fas, fa) {
return F.ap(F.map(fas, function (as) { return function (a) { return (0, function_1.pipe)(as, (0, exports.append)(a)); }; }), fa);
});
};
};
sequence(O.Applicative)([O.some(1), O.some(2)])
// Option.js
/**
* @category instances
* @since 2.7.0
*/
exports.Applicative = {
URI: exports.URI,
map: _map,
ap: _ap,
of: exports.of
};
/**
* @category mapping
* @since 2.0.0
*/
var map = function (f) { return function (fa) {
return (0, exports.isNone)(fa) ? exports.none : (0, exports.some)(f(fa.value));
}; };
/**
* @since 2.0.0
*/
var ap = function (fa) { return function (fab) {
return (0, exports.isNone)(fab) ? exports.none : (0, exports.isNone)(fa) ? exports.none : (0, exports.some)(fab.value(fa.value));
}; };
sequence(O.Applicative)([O.some(1), O.some(2), O.none]) // None
⚠️ 아래 코드 및 설명은 의미론적로만 접근했을뿐 실제 interface 등은 제대로 보지 않았기 때문에 다를 수 있습니다. 이런것이구나 정도로만 받아들이면 좋겠습니다.
pipe(
{ email: 'my@mail.com', age: 44, gender: 'male'},
({ email, age, gender }) =>
sequenceS(E.Applicative)({
email: validateEmail(email),
age: validateAge(age),
gender: validateGender(gender),
}),
console.log
);
이전 포스팅에서 validate 하는 code 를 작성하였습니다.
validate 는 3개를 하므로 3가지의 결과 값이 나와야하는데 만약 이 중 1개만 left 가 될 경우 sequence 는 이를 하나의 left 로만 만들어 버립니다.
이렇게 될 경우 3가지 모두 잘못된 값을 주고받는 상황에서의 최악의 case 는 아래와 같을 것 입니다.
0. 잘못된 data 입력
1. 전송 -> email fail
2. email 수정 후 전송 -> age fail
3. age 수정 후 전송 -> gender fail
최초 data 를 바탕으로 한번에 validation 을 했다면 이런 case 가 발생하지 않았을 겁니다.
이는 단순히 validation case 뿐만 아니라 어떠한 작업을 나눠서 실행 후 결과값을 모으는 case 에 모두 적용할 수 있겠습니다.
그렇기에 우리는 이러한 fail case 의 결과값을 모으기 위해서 sequence 와 applicative 를 사용할 수 있습니다.
it('using case: accumulate error', () => {
const res = pipe(
{ email: 'email', age: 20, gender: 'm' },
({ email, age, gender }) =>
sequenceS(E.getApplicativeValidation(getSemigroup<string>()))({
email: E.left(['email error']), // left가 return 되었다는 가정
age: E.left(['age error']),
gender: E.left(['gender error']),
}),
E.getOrElseW((err) => err)
);
expect(res).toEqual(['email error', 'age error', 'gender error']);
});
E.left('error') // X
E.left(['error']) // O, Either<NonEmptryArray<string>>, unknown>
type errorType = string | number
getSemigroup<errorType>()
const validateEmail = (email: string) => {
if (!email.includes('@')) {
return E.left({ reason: 'not proper email' });
}
return E.right(email);
};
const validateAge = (age: number) => {
console.log(age);
if (age <= 20) {
return E.left({ reason: 'not adult' });
}
return E.right(age);
};
const validateGender = (gender: string) => {
console.log(gender);
if (!['male', 'female'].includes(gender)) {
return E.left({ reason: 'not proper gender type' });
}
return E.right(gender);
};
it('using case: accumulate error', () => {
const wrapper = (
value: E.Either<any, unknown>
): E.Either<NonEmptyArray<string>, any> => {
return pipe(
value,
E.mapLeft((error) => [error.reason])
);
};
const res = pipe(
{ email: 'email', age: 20, gender: 'm' },
({ email, age, gender }) =>
sequenceS(E.getApplicativeValidation(getSemigroup<string>()))({
email: wrapper(validateEmail(email)),
age: wrapper(validateAge(age)),
gender: wrapper(validateGender(gender)),
}),
E.getOrElseW((err) => err)
);
expect(res).toEqual([
'not proper email',
'not adult',
'not proper gender type',
]);
});
이번에 학습한 개념들(sequence, applicative, semigroup) 은 정말 어렵긴 했습니다. 아직 더 배워야하는게 많은데 걱정이네요.
읽어주셔서 감사합니다.