일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- c#
- 문법 정리
- jsp
- gradle
- hyperledger
- K6
- oracle
- 파이썬 소스
- 백준 알고리즘
- 초대장
- 리눅스
- MongoDB
- 알고리즘
- SQL
- JVM
- 학점
- resilience4j
- auto configure
- 오라클 디비
- 자바 프로젝트
- 오라클
- dynamic query
- smart cast
- spring
- 운영체제
- 자바
- 파이썬
- 유사코드
- 티스토리
- 프로젝트
- Today
- Total
모종닷컴
Kotlin Contract 본문
요즘 Coroutine을 학습 중에 있습니다. 좀 Deep dive를 하자는 의미에서 디버깅을 시작하는데 시작하자마자 턱 막혀버렸습니다. 오늘의 주인공 contract입니다. Coroutine에서 만든 API인 줄 처음에 착각했는데 막상 보니 코틀린 표준 라이브러리에 들어가 있는 친구였습니다.
Kotlin Contract
- Kotlin 1.3 이후부터 지원하기 시작한 실험적인 API
- 코틀린 컴파일러에게 함수의 동작을 명시적으로 설명해주는 것
Kotlin Compiler
- 코틀린으로 작성된 소스 코드를 JVM이 이해할 수 있는 바이트코드(.class 파일)로 변환하는 일을 담당
- 저희가 작성한 코드를 정적 분석을 통해 바이트코드로 전환할 때 유용한 기능들이 있는데 그중 하나가 스마트 캐스트입니다
스마트 캐스트
스마트 캐스트 예제를 보겠습니다.
sealed class User {
fun isAuthenticated(): Boolean {
return this is Authenticated
}
class Anonymous : User() {
fun promptToSignIn() = println("Please sign in.")
}
class Authenticated(val userName: String) : User() {
fun greet() = println("Welcome, $userName!")
}
}
fun onScreenLoaded(user: User) {
when(user) {
is User.Authenticated -> user.greet()
is User.Anonymous -> user.promptToSignIn()
}
}
internal class ContractTest {
@Test
fun test() {
onScreenLoaded(User.Anonymous())
}
}
onScreenLoaded 함수를 보시면 파라미터가 user입니다. 그리고 when 절에서 이 유저의 타입을 체크를 하고 나서 block 안을 보면 이미 타입이 캐스트가 된 것처럼 각 타입에 맞는 메서드를 사용할 수 있게 됩니다.
Intellij IDEA를 사용하시는 분은 이 코틀린 코드를 자바로 변환된 모습을 볼 수 있는데 Shift Key 두 번 연속으로 누른 후 Show Kotlin Bytecode를 선택해줍니다. 그럼 우측에 Kotlin Bytecode 탭이 생기는데 Decompile을 눌러주면 자바로 변환된 파일을 볼 수 있습니다.
변환된 자바 코드의 onScreenLoaded 부분을 보면 타입 체크 이후 강제 형변환을 해주고 있는데, 이 형변환은 코틀린 컴파일러가 만들어준겁니다.
바보 코틀린 컴파일러
코틀린 컴파일러는 한계점이 있습니다. onScreenLoaded 함수를 아래와 같이 수정을 해보겠습니다.
fun onScreenLoaded(user: User) {
when(user.isAuthenticated()) {
true -> user.greet()
false -> user.promptToSignIn()
}
}
수정하고 나면 컴파일러의 스마트 캐스트가 동작하지 않습니다. 공식 문서에 따르면 코틀린 컴파일러는 분리된 function에서 체크되었던 사항은 즉시 사라진다고 합니다.
대표적인 스마트 캐스트가 안 되는 경우
좀 더 흔한 예제를 들자면 isNotNull 같은 확장 함수가 있겠습니다.
fun length(s: String?) {
if (s != null) {
println("length = ${s.length}") // 스마트 캐스트 적용
}
}
fun lengthV2(s: String?) {
if (isNotNull(s)) {
println("length = ${s!!.length}") // 스마트 캐스트 미적용
}
}
fun isNotNull(s: String?): Boolean {
return s != null
}
Contract로 컴파일러에게 친절한 설명을 해주자
그럼 contract를 적용한 모습을 보도록 하겠습니다. 아까 고친 코드를 보면 isAuthenticated 함수를 호출한 이후 체크된 사항들이 즉시 사라진다고 했죠? isAuthenticated를 아래와 같이 수정해 보겠습니다.
@OptIn(ExperimentalContracts::class)
fun isAuthenticated(): Boolean {
contract {
returns(true) implies (this@User is Authenticated)
returns(false) implies (this@User is Anonymous)
}
return this is Authenticated
}
해석을 하자면 이 함수를 호출했을 때 리턴 값이 true인 경우 이 유저는 Authenticated 타입, false면 이 유저는 Anonymous 타입이라는 것을 코틀린 컴파일러에게 알려주고 있는 겁니다.
이제 아까 오류가 났던 onScreenLoaded 함수 쪽을 다시 가보면 이제는 오류가 사라져 있음을 볼 수 있습니다.
isNotNull 확장 함수도 고쳐보자
아까 추가적인 예시로 isNotNull 예제를 봤었죠? 이 함수도 contract를 사용해 보겠습니다.
@OptIn(ExperimentalContracts::class)
fun isNotNull(s: String?): Boolean {
contract {
returns(true) implies (s != null)
}
return s != null
}
그럼 아래와 같이 더 이상 오류가 나지 않고 스마트 캐스트가 잘 되고 있음을 볼 수 있습니다.
'Programming' 카테고리의 다른 글
Git Commit 전에 스크립트를 통해 포맷 체크하기 (0) | 2023.04.23 |
---|---|
Github Action에서 Gradle Build 에러 리포트 파일 받아보기 (0) | 2023.04.16 |
Reactor3's SwitchIfEmpty with lazy evaluation.. (0) | 2023.03.10 |
Reactive Streams & Reactive Programming (0) | 2023.02.18 |
SQS 중복 수신 이슈 (0) | 2022.12.24 |