타입스크립트 기초 - 23

Stulta Amiko·2022년 8월 18일
0

타입스크립트 기초

목록 보기
23/24
post-thumbnail

Maybe 모나드 구현

Maybe는 오류일때와 정상일때 모두를 고려하면서도 사용하는 쪽 코드를 간결하게 작성할 수 있게 해준다.
데이터의 유무에 따라 코드가 적절하게 동작하도록 설계했다.
Maybe는 하스켈 Prelude 표준 라이브러리에서 제공하는 모나드이다.

Maybe 모나드는 Option의 Some,None과 비슷한 의미를 가진 Just와 Nothing 이라는 두가지 타입을 제공한다.

Maybe 자체가 모나드가 아니고 Just<T>와 Nothing 타입이 모나드라고 한다.

export class Maybe<T> {
	static Just<U>(value: U) {return new Just<U>(value)}
    static Nothing = new Nothing
}

Maybe의 설계 목적은 코드의 안정성을 함수형 방식으로 보장받기 위해서이다.
예를들어 타입스크립트는 수를 0으로 나누면 0으로 나눌 수 없다는 예외를 발생시키는 것이 아니라 Infinity 값이 발생한다.

Infinity값은 number 타입의 값이므로 비정상 종료되는것이 아니라 로직에 혼동을 줄 수 있는 값이므로 피하는 것이 좋다.
따라서 undefined,null,Infinity등의 값을 유발할때 Maybe를 사용하면 효율적인 방식으로 코드를 작성할 수 있다.

import {Maybe,IMaybe} from '../classes/Maybe'
const devide (a: number) => (b: number): IMaybe<number> =>
b ? Maybe.just(a/b) : Maybe.Nothing

위 코드는 b의 값이 undefined null 0 이 아닐때는 Maybe.just(a/b)가 반환되지만 반대일때는 Maybe.Nothing이 반환된다.

따라서 divide 함수는 간결하면서도 안정성을 해지지 않도록 작성된것이다.

Maybe 클래스 구조

import { Just } from "../interface/Just";
import { Nothing } from "../interface/Nothing";
import { IMonad } from "../../../ch11/src/interface";
import { _IMaybe } from "./_IMaybe";

export class Maybe<T>{
    static Just<U>(value: U) {return new Just<U>(value)}
    static Nothing =  new Nothing
}

export type IMaybe<T> = _IMaybe<T> & IMonad<T>

Just 모나드 구현

Just 모나드는 앞에서 구현한 Identity 모나드에 _IMaybe 인터페이스를 구현한 다음과 같은 내용으로 구현한다.
Identity와 다르게 ISetoid는 구현하지 않는데
이는 Just 가 Nothing 일때를 고려해 value()가 아닌 getOrElse(0) 와 같은 형태로 동작하는것을 염두한 것이다.

import { _IMaybe } from '../classes/_IMaybe';
import {IMonad} from '../../../ch11/src/interface'

export class Just<T> implements _IMaybe<T>,IMonad<T>{
    constructor(private _value: T) {}
    value(): T {return this._value}

    //IApplicative
    static of<T>(value: T): Just<T> {return new Just<T>(value)}

    //IMaybe
    isJust(): boolean {return true}
    isNothing(): boolean {return false}
    getOrElse<U>(defaultValue: U) {return this.value()}

    //IFunctor
    map<U,V>(fn: (x:T) => U): Just<U> {return new Just<U>(fn(this.value()))}

    //IApply
    ap<U>(b: U){
        const f = this.value()
        if(f instanceof Function)
            return Just.of<U>((f as Function)(b))
    }
    //IChain
    chain<U>(fn: (T) => U):U {return fn(this.value())}
}

Nothing 모나드 구현

Nohing 모나드는 Just 모나드와 다르게 코드를 완벽하게 실행시키지 않는것이 설계 목적이다.
다음 코드는 divide(1)(0).map(R.add(1)).getOrElse(0) 형태의 코드가 비정상적으로 동작하지 않고 마지막에 호출된 메서드의 기본값을 반환하는것을 목적으로 작성했다.

import { IMonad } from "../../../ch11/src/interface";
import { _IMaybe } from "../classes/_IMaybe";

export class Nothing implements _IMaybe<null>,IMonad<null>{
    //IApplicative
    static of<T>(value: T = null):Nothing {return new Nothing}

    // IMaybe
    isJust(): boolean {return false}
    isNothing(): boolean {return true}
    getOrElse<U>(defaultValue: U) {return defaultValue}

    //IFunctor
    map<U,V>(fn: (x)=> U):Nothing {return new Nothing}

    //IApply
    ap<U>(b: U){return new Nothing}

    //IChain
    chain<U>(fn: (T) => U):Nothing{return new Nothing}
}

Just와 Nothing 모나드 단위 테스트

Just는 정상적일때 동작하는 모나드 이므로 항상 자신의 실제값을 반환해야한다.
다음 코드는 Just가 정상적으로 작동하는것 처럼 보이면서 _IMaybe 인터페이스 기능을 추가로 제공하는 것을 보여준다.

import * as R from 'ramda'
import { Just } from '../interface/Just'

console.log(
    Just.of(100).isJust(), //true
    Just.of(100).isNothing(), //false
    Just.of(100).getOrElse(1), //100
    Just.of(100).map(R.identity).getOrElse(1), //100
    Just.of(R.identity).ap(100).getOrElse(1), //100
    Just.of(100).chain(Just.of).getOrElse(1) //100
)

다음 Nothing 모나드 테스트에서는 Just와 달리 자신의 모나드 관련 코드를 동작시키면 안된다.
또한 undefined나 null Nan Infinity와 같은 값을 반환해서도 안된다.

import {Nothing} from '../classes/Nothing'
import {Just} from '../classes/Just'

console.log(
    Nothing.of().isJust(), //false
    Nothing.of().isNothing(), //true
    Nothing.of().getOrElse(1), //1
    Nothing.of().map(x => x+1).getOrElse(1), //1
    Nothing.of().ap(1).getOrElse(1), //1
    Nothing.of().chain(Just.of).getOrElse(1) //1
)

0개의 댓글