[FP] Typescript로 알아보는 Either Monad

taez·2024년 1월 2일
post-thumbnail

Typescript Playground
이 글에 작성되어있는 코드는 위 Playground에서 직접 실행해볼 수 있습니다.

📋 Summary

  • 함수형 프로그래밍 개념 정리
  • Either 알아보기 by Typescript

📝 Details

Either란

지난 글에 소개한 Maybe 모나드가 값의 존재 여부에 따른 상태를 나타내는 타입이었다면, 이번 글에 소개 할 Either 모나드는 성공과 실패 같은 두 가지 상태를 나타내는 타입으로, 함수의 결과가 두 가지 가능성 중 하나를 가질 때 유용하게 사용됩니다.

Javascript에 익숙한 분이라면 Promise에서 비동기 작업의 성공(resolve)과 실패(reject)에 따라 then과 catch 메소드로 처리되는데, 이는 Either 모나드의 개념과 비슷합니다.

Either의 구조

Maybe가 Just-Nothing 두 가지 하위 타입을 가진것 처럼
Either는 Left-Right의 하위 타입을 가집니다.

Left는 일반적으로 오류나 예외적인 상황을 나타내며,
Right는 성공적인 실행이나 기대한 값을 나타냅니다.

사용 예시

Either는 함수가 두 가지 유형의 결과를 반환할 수 있음을 명시적으로 나타냅니다.
예를들어 어떤 연산이 실패했을때 Left, 성공했을때는 Right로 반환합니다.

Typescript로 보기

지난 글에서는 Maybe 등을 class로 구현했는데 이번 글에서 Either는 type으로 구현해보겠습니다.

type Left<L> = {
    type: "Left";
    value: L;
};

type Right<R> = {
    type: "Right";
    value: R;
};

type Either<L = any, R = any> = Left<L> | Right<R>;

Left와 Right를 가질 수 있는 Either 타입을 정의했습니다.
Either에서 Left와 Right의 value type은 서로 다를 수 있습니다.

function left<L>(value: L): Left<L> {
	return { type: 'Left', value };
}

function right<R>(value: R): Right<R> {
	return { type: 'Right', value };
}

Left, Right일 때 사용될 함수도 만들어주고 바로 사용해보겠습니다.

function divide(x: number, y: number): Either<string, number> {
    return y === 0 ? left("Division by zero") : right(x / y);
}

divide 함수는 분자와 분모를 받아서 분모가 0이면 오류 메시지를 담은 Left 타입을 리턴하고, 그렇지 않으면 Right 타입의 연산 결과를 리턴합니다.

// Either 출력을 위한 함수
const printResult = ({ type, value }: Either) => {
	if (type === 'Left') {
		console.log(`Error: ${value}`);
	} else {
		console.log(`Success: ${value}`);
	}
}

// 성공적인 연산의 예
const successResult = divide(10, 2);
printResult(successResult); // "Success: 5" 

// 실패한 연산의 예
const failureResult = divide(10, 0);
printResult(failureResult); // "Error: Division by zero" 

Either를 통해 함수의 사용자는 성공 또는 실패를 명확하게 구분하고 적절하게 처리할 수 있습니다.

그럼 지난 글의 마지막 예제였던 문자열을 인자로 받아 두 번째 문자를 대문자로 리턴하는 로직에 Either를 적용해보겠습니다.

Either 미적용

// 기존 함수
const getSecondLetter = (value: string) => value.charAt(1);
const toUpperCase     = (value: string) => value.toUpperCase();

const printValueMsg   = (value: string) => console.log(`value is ${value}`);
const printNothingMsg = () => console.log('There is no value');

Maybe.of<string>("a")
	.bind(getSecondLetter)
	.bind(toUpperCase)
	.match(printValueMsg, printNothingMsg); // "value is "

Either 적용

// Either 모나드를 사용하여 오류 메시지 처리
const getSecondLetterToUppercase = (value: string): Either<string, string> => 
	value.length < 2 ? left("String is too short") : right(value.charAt(1).toUpperCase()) ;

// Maybe와 Either를 함께 사용
function processString(input?: string | null) {
    return Maybe.of(input)
        .bind(getSecondLetterToUppercase)
        .match(
        	printResult,
            () => console.log('Error: No input provided')
        );
}

// 결과
processString();        // "Error: No input provided"
processString("a");     // "Error: String is too short"
processString("hello"); // "Success: E"

Maybe 모나드를 적용한 기존 코드에 Either 모나드를 적용하여 오류 메시지를 보다 명확하게 처리할 수 있도록 했습니다.

Outro

Either 모나드는 함수형 프로그래밍에서 결과의 두 가지 가능성(성공 또는 실패)을 다룰 수 있게 하는 도구입니다. 실패 처리와 성공 처리를 명확히 분리함으로써 코드의 가독성을 높이고, 오류 가능성을 감소시킵니다. 또한, 이를 통해 코드의 안정성과 유지 보수성을 높이는 동시에, 오류 처리를 더 명확하고 체계적으로 할 수 있습니다.

🔗 Links/References

profile
흔하지 않은 개발자

0개의 댓글