Do-notation 알아보기

김장훈·2023년 5월 3일
0

fp-ts function 탐구

목록 보기
5/5

1. interface

  • TBD

2. 어디에 쓰이는가

Both Haskell and PureScript languages provide syntactic sugar for working with monads in the form of do notation.

  • pipe 를 좀 더 효율적으로 사용할 수 있도록 해줍니다.

2.1. validate user

2.1.1. 일반 case

  • 상황: (이미 앞에서 많이 했지만) user 가 가입한다던가 또는 어떤 특정 data 군을 validate 한다고 할 경우 보통 아래와 같이 사용하게 될 것 입니다.
type userInput = {
  email: string;
  age: number;
  name: string;
};

const combinedFunction = (data: userInput) => {
  // 각 data 를 받아서 처리 function 의 집합
  return '';
};

export const validateUserInput = (data: userInput) => {
  return pipe(data, combinedFunction);
};


it('normal case: not using do', () => {
  const data = { email: 'email', age: 30, name: 'name' };
  const res = validateUserInput(data);
  expect(res).not.toBeNull();
});
  • pipe 는 이미 아시다시피 data 를 순차적으로 function 에 적용합니다.
    -- 예) A > funcA(A) > funcB(func(A))
  • 따라서 우리는 pipe 를 사용하는 경우 data 가 channing 되는 형태로 작성해야합니다.
  • 예시에서 받은 data 를 보면 3가지 property 를 가지고 있으므로 이를 pipe 에서 처리하려면 해당 function 은 object 를 받아서 처리하는 형태로 작성 되어야했습니다.
  • 이렇게 될 경우 combinedFunction 은 data 내에 있는 3가지 property 를 모두 validate 하는 역할을 가지게 되며 다소 여러가지의 책임을 지닌 function 이 되어버렸습니다.
  • 이럴때 function 의 역할을 분리하면서 pipe 를 적용할 수 있는 방법이 do-notation 입니다.

2.1.2. do-notation

import * as E from 'fp-ts/Either';
import { pipe } from 'fp-ts/lib/function';

const validateEmail = (email: string) => {
  return email.includes('@') ? E.right(email) : E.left('wrong email');
};
const validateAge = (age: number) => {
  return age >= 20 ? E.right(age) : E.left('wrong age');
};
const validateName = (name: string) => {
  return name !== '' ? E.right(name) : E.left('wrong name');
};

type userInput = {
  email: string;
  age: number;
  name: string;
};
export const validateUserInfo = (data: userInput) => {
  return pipe(data, ({ email, age, name }) => {
    return pipe(
      E.Do,
      E.bind('vEmail', () => validateEmail(email)),
      E.bind('vAge', () => validateAge(age)),
      E.bind('vName', () => validateName(name)),
      E.map(({ vEmail, vAge, vName }) => {
        return { vEmail, vAge, vName };
      })
    );
  });
};
  • 하나의 data 를 받아서 이를 각각의 property 로 구분하여 function 을 적용하였습니다.
  • pipe 가 두번이나 쓰였으므로 아래처럼 작성할 수도 있습니다.
export const validateUserInfoTwo = (data: userInput) => {
  return pipe(
    E.Do,
    E.bind('vEmail', () => validateEmail(data.email)),
    E.bind('vAge', () => validateAge(data.age)),
    E.bind('vName', () => validateName(data.name)),
    E.map(({ vEmail, vAge, vName }) => {
      return { vEmail, vAge, vName };
    })
  );
};
  • 중요한것은 pipe를 사용할때 인자로 받는 data 를 하나의 object 가 아니라 개별 property 그대로 사용 & 적용할 수 있다는 점 입니다.
  • 그렇게 do-notation 을 사용하게 되면 function 을 하나의 역할만 담당하는 목적 그대로 사용할 수 있습니다.
  • 다만 한가지 아쉬운 점은 short-circuit 을 따른다는 점, 즉 에러(left 등) 발생시 다른 로직을 타지 않습니다.
  • 따라서 특정 로직의 결과물(성공/실패)들을 모두 모으기 위해 사용하는 것이 아닌 모두 성공 또는 실패와 같은 원자적 로직이 필요한 경우에 사용하면 좋을것 같습니다.
profile
읽기 좋은 code란 무엇인가 고민하는 백엔드 개발자 입니다.

0개의 댓글