💻 프로그래밍

Scala

Scalable Language

객체지향과 함수형 프로그래밍을 결합한 JVM 언어. Martin Odersky가 개발. Spark, Akka, Play Framework의 핵심 언어.

📖 상세 설명

Scala(Scalable Language)는 JVM(Java Virtual Machine) 위에서 실행되는 프로그래밍 언어로, 객체지향 프로그래밍(OOP)과 함수형 프로그래밍(FP)을 자연스럽게 결합한 하이브리드 언어입니다. Java와 완벽하게 호환되면서도 더 간결하고 표현력 있는 문법을 제공하여, Java 생태계의 방대한 라이브러리를 활용하면서 현대적인 프로그래밍 패러다임을 적용할 수 있습니다.

Scala는 2004년 스위스 EPFL(로잔연방공과대학)의 Martin Odersky 교수가 설계하고 공개했습니다. Odersky는 Java 컴파일러 javac와 Java Generics의 공동 설계자로, 그의 경험을 바탕으로 Java의 장점을 유지하면서 한계를 극복한 언어를 만들고자 했습니다. "Scala"라는 이름은 "Scalable Language"의 약자로, 작은 스크립트부터 대규모 분산 시스템까지 확장 가능한 언어라는 철학을 담고 있습니다.

Scala의 핵심 특징으로는 강력한 타입 추론(Type Inference), 패턴 매칭(Pattern Matching), 불변(Immutable) 데이터 구조, 고차 함수(Higher-Order Functions), 트레이트(Trait) 기반 믹스인 상속 등이 있습니다. 이러한 기능들은 코드의 간결성과 안전성을 동시에 높여줍니다. 특히 for-comprehension, implicit 변환, 케이스 클래스(Case Class) 등은 Scala만의 독창적인 기능으로 평가받습니다.

실무에서 Scala는 빅데이터 처리 프레임워크 Apache Spark의 핵심 언어로 가장 널리 사용됩니다. 또한 고성능 동시성 프레임워크 Akka, 웹 프레임워크 Play Framework, 빌드 도구 SBT 등이 Scala로 작성되었습니다. Twitter, LinkedIn, Netflix, Airbnb 등 글로벌 기업들이 대규모 백엔드 시스템에 Scala를 채택하고 있으며, 특히 분산 처리, 스트리밍 데이터, 마이크로서비스 아키텍처 분야에서 강점을 보입니다.

💻 코드 예제

기본 문법 - 변수, 함수, 컬렉션
// Scala 기본 문법 예제

// 불변 변수 (val) vs 가변 변수 (var)
val name: String = "Scala"    // 불변 (권장)
var count: Int = 0           // 가변

// 타입 추론 - 타입 명시 생략 가능
val message = "Hello, Scala!"  // String으로 추론
val numbers = List(1, 2, 3)    // List[Int]로 추론

// 함수 정의
def greet(name: String): String = {
  s"Hello, $name!"  // 문자열 보간법
}

// 한 줄 함수 (중괄호 생략)
def square(x: Int): Int = x * x

// 익명 함수 (람다)
val double = (x: Int) => x * 2

// 컬렉션 연산 (함수형 스타일)
val nums = List(1, 2, 3, 4, 5)
val evens = nums.filter(_ % 2 == 0)     // List(2, 4)
val doubled = nums.map(_ * 2)           // List(2, 4, 6, 8, 10)
val sum = nums.reduce(_ + _)            // 15

// 메서드 체이닝
val result = nums
  .filter(_ > 2)
  .map(_ * 10)
  .sum  // 120
케이스 클래스와 패턴 매칭
// 케이스 클래스 - 데이터 모델링의 핵심
case class User(
  id: Long,
  name: String,
  email: String,
  age: Option[Int] = None  // Optional 값
)

// 케이스 클래스는 자동으로 equals, hashCode, toString, copy 제공
val user1 = User(1, "Alice", "alice@example.com", Some(25))
val user2 = user1.copy(name = "Bob")  // 불변 업데이트

// Sealed trait로 대수적 데이터 타입 정의 (ADT)
sealed trait PaymentMethod
case class CreditCard(number: String, cvv: String) extends PaymentMethod
case class PayPal(email: String) extends PaymentMethod
case object Cash extends PaymentMethod

// 패턴 매칭 - 타입 안전한 분기 처리
def processPayment(method: PaymentMethod): String = method match {
  case CreditCard(num, _) =>
    s"Processing credit card ending in ${num.takeRight(4)}"
  case PayPal(email) =>
    s"Processing PayPal payment for $email"
  case Cash =>
    "Processing cash payment"
}

// Option 패턴 매칭으로 null 안전 처리
def getUserAge(user: User): String = user.age match {
  case Some(age) => s"User is $age years old"
  case None      => "Age not provided"
}

// 가드 조건과 함께 사용
def categorize(age: Int): String = age match {
  case a if a < 13  => "Child"
  case a if a < 20  => "Teenager"
  case a if a < 65  => "Adult"
  case _            => "Senior"
}
함수형 프로그래밍 스타일
// 고차 함수 (Higher-Order Function)
def applyTwice[A](f: A => A, x: A): A = f(f(x))
val result = applyTwice((x: Int) => x + 1, 5)  // 7

// for-comprehension (flatMap + map의 문법적 설탕)
case class Order(userId: Long, items: List[String])
val orders = List(
  Order(1, List("Apple", "Banana")),
  Order(2, List("Orange"))
)

// 모든 주문의 모든 아이템 추출
val allItems = for {
  order <- orders
  item  <- order.items
} yield item  // List("Apple", "Banana", "Orange")

// Option과 for-comprehension으로 안전한 연산
def findUser(id: Long): Option[User] = ???
def findOrder(userId: Long): Option[Order] = ???

val userItems: Option[List[String]] = for {
  user  <- findUser(1)
  order <- findOrder(user.id)
} yield order.items  // None이 하나라도 있으면 None

// Either로 에러 처리 (함수형 방식)
sealed trait ValidationError
case class InvalidEmail(msg: String) extends ValidationError
case class InvalidAge(msg: String) extends ValidationError

def validateUser(
  email: String,
  age: Int
): Either[ValidationError, User] = {
  if (!email.contains("@"))
    Left(InvalidEmail("Email must contain @"))
  else if (age < 0)
    Left(InvalidAge("Age must be positive"))
  else
    Right(User(1, "Valid", email, Some(age)))
}

// 커링 (Currying)
def add(x: Int)(y: Int): Int = x + y
val addFive = add(5) _  // 부분 적용된 함수
val eight = addFive(3)  // 8

🗣️ 실무 대화 예시

백엔드 기술 스택 회의에서 - Scala vs Kotlin

"새 마이크로서비스 언어로 Scala vs Kotlin을 검토 중인데요. Kotlin이 학습 곡선이 낮고 Android 경험자가 많아서 채용에 유리합니다. 하지만 우리 팀이 Spark 파이프라인을 많이 다루고 Akka 기반 액터 모델을 쓰고 있어서, 기존 코드베이스와의 일관성을 위해 Scala를 유지하는 게 맞을 것 같습니다. 다만 Scala 3로 마이그레이션하면 문법이 많이 간결해져서 학습 부담도 줄어들 거예요."

기술 면접에서 - 함수형 프로그래밍의 장점

"Scala에서 함수형 프로그래밍의 핵심 장점은 불변성과 순수 함수입니다. 불변 데이터 구조를 사용하면 동시성 프로그래밍에서 race condition을 원천 차단할 수 있고, 순수 함수는 같은 입력에 항상 같은 출력을 보장해서 테스트와 디버깅이 쉬워집니다. 실제로 Spark에서 RDD 연산이 모두 불변 변환으로 설계된 것도 이런 이유입니다. 또한 패턴 매칭과 Option/Either 타입을 활용하면 null 관련 런타임 에러를 컴파일 타임에 잡을 수 있어요."

코드 리뷰에서

"이 부분 var 대신 val로 바꾸고 재귀나 fold로 리팩토링하면 좋겠어요. Scala에서는 가변 상태를 최소화하는 게 관례입니다. 그리고 match 표현식에서 모든 케이스를 처리하지 않으면 컴파일러 경고가 뜨는데, sealed trait을 쓰면 패턴 매칭의 완전성을 컴파일러가 체크해줍니다."

⚠️ 주의사항

1
높은 학습 곡선

Scala는 OOP와 FP를 모두 지원하고 implicit, 타입 클래스, 고급 타입 시스템 등 복잡한 개념이 많아 Java나 Python 개발자가 익숙해지기까지 상당한 시간이 필요합니다. 팀에 Scala 경험자가 없다면 3-6개월의 학습 기간을 고려하세요. 처음에는 간단한 FP 패턴부터 시작하고 점진적으로 고급 기능을 도입하는 것을 권장합니다.

2
Scala 2 vs Scala 3 버전 호환성

Scala 3(Dotty)는 2021년 출시되어 문법과 타입 시스템이 크게 변경되었습니다. implicit 대신 given/using, 새로운 enum 문법, 선택적 중괄호 등이 도입되었습니다. 기존 Scala 2 코드베이스와 라이브러리 호환성을 확인해야 하며, 마이그레이션에는 상당한 리팩토링이 필요할 수 있습니다. 신규 프로젝트는 Scala 3을, 기존 프로젝트는 에코시스템 지원 상황을 확인 후 결정하세요.

3
긴 컴파일 시간

Scala의 강력한 타입 추론과 implicit 해석으로 인해 컴파일 시간이 Java보다 2-3배 이상 길어질 수 있습니다. 대규모 프로젝트에서는 빌드 시간이 생산성에 영향을 줍니다. SBT의 증분 컴파일, Bloop 컴파일 서버, Zinc 캐싱 등을 활용하고, 모듈을 잘 분리하여 불필요한 재컴파일을 최소화하세요.

🔗 관련 용어

📚 더 배우기