콘텐츠로 이동

2024 02 29

2024-02-29

Slick의 filter에서 Some/None을 사용하지 못하는 현상

  • 문제
    • 테이블 칼럼에 다음과 같은 값이 있었음
      • def displayStartAt = column[Option[LocalDateTime]]("display_start_at")
    • 이 상황에서 테이블에 filter를 걸었는데
      • Bulletin.filter(_.displayStartAt === Option(LocalDateTime.now())) 는 OK
      • Bulletin.filter(_.displayStartAt === Some(LocalDateTime.now())) 는 NO
      • Bulletin.filter(_.displayStartAt === None) 도 No
  • 이유
    • OptionSome/None은 스칼라에서 다르게 동작
    • Option

      • Option(LocalDateTime.now)Option.apply를 통해 Some 혹은 None을 반환
        def apply[A](x: A): Option[A] = if (x == null) None else Some(x)
        
      • 이는 Option이 자체적으로 null-check를 해주고 있다는 것
        • Some
      • 반면, Some(LocalDateTime.now)는 직빵으로 Some()으로 감싸는데, 이는 null-check 할 여지를 주지 않음
        final case class Some[+A](value: A) extends Option[A] {
          def get: A = value
        }
        
      • 이때 LocalDateTime.now가 null이라면, 직빵으로 NPE가 터짐
        • None
      • None도 마찬가지
        case object None extends Option[Nothing] {
          def get: Nothing = throw new NoSuchElementException("None.get")
        }
        
  • Slick에서는...
    • Slick에서는 DB 칼럼과 None을 직빵으로 비교하는 것에 대해 제재를 하고 있음 (NPE 터질 수 있으니)
    • 그래서 Option의 isEmpty 사용하도록 권장
    • .filter(_.displayStartAt.isEmpty)

Scala에서 Option/Some/None의 확실한 관계

참고: https://medium.com/@sukumaar/scala-option-some-none-b9f735acfb82 참고: https://hamait.tistory.com/649

  • Option은 컨테이너처럼 동작하여,
    • 참조가 empty(메모리 어딘가를 참조중이면) => Some
    • 아니라면 => None
      def apply[A](x: A): Option[A] = if (x == null) None else Some(x)
      
  • 그러다 보니 case-match와 호환성이 좋네 (컨테이너니까)
    val optionalPerson:Option[Person] = Option(p1)
    
    val stringValue2 = optionalPerson match {
        case Some(s) => s.toString()
        case None => "ohh source really sent null so cant call "
    }
    
  • Option
    • 값이 있거나/없거나 한 상태를 나타냄
      • 값이 담겨져 있다면 => Some[T]
      • 값이 없으면 => None
    • Option은 Try,Future와 함께 대표적인 모나딕 컬렉션
    • 두 가지 생각하자
      1. Null을 안전하게 다루자!
      2. 연속체인에서 안정적으로 사용하자!
  • Option 활용처
    • Map
      val numbers = Map("one" -> 1, "two" -> 2)
      val h2 = numbers.get("two") // Some(2)
      val h3 = numbers.get("three") // None
      

Scala Monadic Collection

참고: https://devus.tistory.com/entry/Learning-Scala-Chapter7-%EA%B7%B8-%EC%99%B8%EC%9D%98-%EC%BB%AC%EB%A0%89%EC%85%98-%EB%AA%A8%EB%82%98%EB%94%95-%EC%BB%AC%EB%A0%89%EC%85%98

  • Iterable 연산과 비슷한 변형 연산을 지원하지만, 하나 이상의 요소를 포함할 수 없는 컬렉션
  • Option
    • 안전한 null 처리
    • Some/None 타입으로 값의 존재 구분
    • isDefined, isEmpty 사용해 값 유무 판단 가능
    • 일반적인 컬렉션에서도 요소 추출시 Option 반환을 통한 안정성 확보할 수 있음
    • .get()은 비권장. 딴거 쓰자
      • .getOrElse()
      • .fold(): None일 경우 해당값 리턴, Some일 경우 고차함수 실행
        val name = Option("Joel")
        val nullName = Option(null)
        
        name.fold("JOELJO")(x => s"$x.jo") // Joel.jo
        nullName.fold("JOELJO")(x => s"$x.jo") // JOELJO
        
  • Try
    • 기존 자바 try/catch를 스칼라의 Try로 바꿔보자!
    • 스칼라에서도 try/catch 블록을 지원하고, catch 블록에서 발생한 에러를 매칭하는 case 문 포함할 수 있다
      • 하지만 util.Try만 사용함으로써 안전/표현력/모나딕 접근법이 가능하다
    • Try를 세련되게 다뤄보자
      val emptyList = List()
      val list = List("JOEL")
      
      Try(emptyList.head) // Failure(java.util.NoSuchElementException: head of empty list)
      Try(list.head) // Success(JOEL)
      
      Try(emptyList.head).toOption // None
      Try(list.head).toOption // Some("JOEL")
      
  • Future

    • 백그라운드 작업을 개시하는 컬렉션
    • 기본적으로 스칼라 코드는 JVM의 메인 쓰레드에서 동작하나, Future의 사용은 별도 쓰레드에서 백그라운드 작업이 가능케 한다
      • 다만 백그라운드로 돌리기 위해서는 실행하기 위한 Context를 지정해야 함
    • 기본 사용법
    • 명시적으로 context 지정
      import scala.concurrent.ExecutionContext.global
      import scala.concurrent.Future
      
      object ScalaFuture extends App {
        Future {
          println("2")
        }(global)
      }
      
    • 묵시적으로 context 지정
      import scala.concurrent.ExecutionContext.Implicits.global
      import scala.concurrent.Future
      
      object ScalaFuture extends App {
        Future {
          println("2")
        }
      }