파일 입출력, 네트워크 소켓 등 자원을 다룰 때 그 자원을 해제해주는 것은 매우 중요하다. 하지만 코드는 녹이 슬고, 사람들은 실수를 한다. 그래서 많은 디자인 패턴이 이 방법을 강제하기 위해 노력한다.
java
에서 파일을 열고, 닫는 등 어떤 자원을 가지고 하는 행동을 다루는 일반적인 패턴은 다음과 같다.
위 예시 코드는 코드의 실행이 끝난 후 자동으로 close
를 해 준다. 많은 보일러플레이트를 줄일 수 있다.
Rust, C++등은 RAII 패턴을 제시한다. 사실 Java로 작성한 위의 코드도 일종의 RAII 패턴이라고 볼 수 있다. Rust
나 C++
의 RAII
가 bracket pattern보다 좀 더 나은 대안이라는 주장도 있다.
아무튼, 위와 비슷한 일을 하기 위해 scala
같은 함수형 프로그래밍 언어들은 bracket pattern
을 도입한다. 이에 대해 살펴보자.
생명주기는 두 개의 action
으로 이루어진다. 각각 acquire
, release
로 불린다. 앞선 액션 acquire
가 실행된 후에 realease
가 실행됨이 보장되어 있다. 취소나 에러가 발생했을 경우에도 말이다.
MonadCancel
은 취소가 가능한 모나드이다. MonadError
를 상속받아 그 성질들을 이용할 수 있다. cats-effect-3에서는 따로 bracket을 구현하지 않고 여러 타입 클래스의 메소드로 구현되어 있다. MonadCancel
은 다음과 같이 bracket을 구현한다.
둘 다 bracketFull
에 의존하는 구현임을 확인할 수 있다. 이제 bracketFull
을 보자.
우선 safeRelease
라는 취소할 수 없는 release
를 만든 후, 순차적으로 적용하는 것을 확인할 수 있다. 오류가 발생했을 때에는 Outcome.Succeeded
대신 Outcome.Errored
로 주는 것도 확인할 수 있다.
bracketCase
를 bracketFull
을 사용하지 않고 다음과 같이 구현할 수도 있다.