일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- Spring Boot
- 스프링 배치
- apache storm
- 도메인주도설계
- elasticsearch
- docker
- hadoop
- 엘라스틱서치
- Linux
- Gradle
- hdfs
- elastic search
- Spring Batch
- hibernate
- design pattern
- Storm
- DDD
- SBT
- 인텔리J
- Clean Code
- 제주
- Spring XD
- Angular2
- Java
- nginx
- Spring
- intellij
- scala
- Hbase
- spark
- Today
- Total
욱'S 노트
Option을 사용해야하는 이유/Nested Nullability (Kotlin 함수형 프로그래밍 #4) 본문
Option을 사용해야하는 이유/Nested Nullability (Kotlin 함수형 프로그래밍 #4)
devsun 2025. 1. 15. 09:57예전에 자바를 사용했었다면, NullpointerException을 겪었을 것이다. 널의 가장 큰 문제점은 클라이언트 코드에서 예상할 수 어렵다는데 있었다. 코틀린에서는 ?을 베이스로 null-safety 문제를 해결하고 있다. 코틀린에는 nullable type이 있지만, Arrow에 Option 타입이 있는 이유에 대해서 알아보자.
fun <A> List<A>.firstOrElse(default: () -> A): A = firstOrNull() ?: default()
fun example() {
emptyList<Int?>().firstOrElse { -1 } shouldBe -1
listOf(1, null, 3).firstOrElse { -1 } shouldBe 1
}
다음 코드를 예상해보자.
fun example() {
listOf(null, 2, 3).firstOrElse { -1 } shouldBe null
}
그러나 결과는 다음과 같다.
Exception in thread "main" java.lang.AssertionError: Expected null but actual was -1
Nested Nullabilty Problem
코틀린에서 Nested Nullability 문제는 Nullable Type(?)이 중첩되었을 때 발생하는 혼란스러운 상황이나 이를 처리하는 방법과 관련된 문제를 말한다. 예를 들어, 코틀린에서는 타입이 중첩된 경우 다음과 같은 구조가 가능하다. null을 사용하면 애매모호한 상황에 빠질 수 있다.
- List<String>?: null일 수 있는 리스트
- List<String?>: null일 수 있는 요소들을 가진 리스트
- List<String?>?: 리스트 자체도 null일 수 있고, 리스트 내부의 요소들도 각각 null일 수 있음
val map = hashMapOf<String, Int?>("key1" to 1, "key2" to null)
val val1: Int? = map.get("key1")
val val2: Int? = map.get("key2")
val val3: Int? = map.get("key3")
println(val1 == null) // false
println(val2 == null) // true
println(val3 == null) // true
Option
Option<A>는 A라는 타입의 옵셔널 컨테이너이다. A 타입의 값이 존재한다면 Option<A>는 A타입의 값을 포함한 Some<A>의 인스턴스이다. 값이 존재하지 않는다면 Option<A>는 None 오브젝트이다. 생성자나 확장 함수를 통해서 생성할 수 있다.
val some: Some<String> = Some("I am wrapped in something")
val none: None = None
val optionA: Option<String> = "I am wrapped in something".some()
val optionB: Option<String> = none<String>()
fun example() {
some shouldBe optionA
none shouldBe optionB
}
하지만 아직도 많은 라이브러리들이 nullable 타입으로 결과를 리턴하기 때문에 nullable type으로 부터 생성하는 방법을 제공한다. fromNullable 함수를 사용해서 A? 타입을 Option으로 리프트 할 수 있다.
val some: Some<String> = Some("I am wrapped in something")
val none: None = None
val optionA: Option<String> = "I am wrapped in something".some()
val optionB: Option<String> = none<String>()
fun example() {
some shouldBe optionA
none shouldBe optionB
}
리프트와 명시적으로 생성하는 것에는 차이점이 있으니 주의하자.
fun example() {
val some: Option<String?> = Some(null)
val none: Option<String?> = Option.fromNullable(null)
some shouldBe null.some()
none shouldBe None
}
Option 사용하기
가장 쉬운 접근 방식은 getOrNull을 사용하는 것이다.
fun example() {
Some("Found value").getOrNull() shouldBe "Found value"
None.getOrNull() shouldBe null
}
다른 방법은 getOrElse를 사용하는 방법이다.
fun example() {
Some( "Found value").getOrElse { "No value" } shouldBe "Found value"
None.getOrElse { "No value" } shouldBe "No value"
}
Option도 대수적 타입이기 때문에 when절에서 패턴매치시 exhaustive 검사가 자동적으로 수행된다.
fun example() {
when(val value = 20.some()) {
is Some -> value.value shouldBe 20
None -> fail("$value should not be None")
}
when(val value = none<Int>()) {
is Some -> fail("$value should not be Some")
None -> value shouldBe None
}
}
isSome과 isNone으로 Option 값이 값을 가지고 있는지 확인할 수 있다.
fun example() {
Some(1).isSome() shouldBe true
none<Int>().isNone() shouldBe true
}
?.let { } ?: false 값은 복잡한 널처리 대신에 특정 서술부를 기술할 수도 있다.
fun example() {
Some(2).isSome { it % 2 == 0 } shouldBe true
Some(1).isSome { it % 2 == 0 } shouldBe false
none<Int>().isSome { it % 2 == 0 } shouldBe false
}
?.also { } or ?.also { if(it != null) { } }. 대신에 값이 존재할때의 필요한 사이드 이펙트를 실행할 수도 있다.
fun example() {
Some(1).onSome { println("I am here: $it") }
none<Int>().onNone { println("I am here") }
none<Int>().onSome { println("I am not here: $it") }
Some(1).onNone { println("I am not here") }
}
결론
- 일반적으로 코틀린에서 nullable types을 사용하는 것을 선호할 것이다. 하지만, option이 더 이상적이다.
- nested nullable issues나 null value를 지원하지 않는 Reactor나 RxJava와 함께 사용할 때도 그러하다.
참고자료 : https://arrow-kt.io/
'Methdology > Functional Programming' 카테고리의 다른 글
외부효과와 입출력/Effects and IO (Kotlin 함수형프로그래밍 #6) (0) | 2025.01.17 |
---|---|
불변 컬렉션/Immutable Collections (Kotlin 함수형 프로그래밍 #5) (4) | 2025.01.16 |
타입 에러/Typed Errors (Kotlin 함수형 프로그래밍 #3) (0) | 2025.01.14 |
함수형 데이터구조/대수적 타입/Algebraic Data Type (Kotlin 함수형프로그래밍 #3) (0) | 2025.01.13 |
불변 데이터/Immutable Data/Optics(Kotlin 함수형 프로그래밍 #2) (0) | 2025.01.09 |