Python 개발자를 위한 Scala

Python 개발자를 위한 Scala

이 섹션은 Python과 Scala 프로그래밍 언어를 비교해 줘요. Python은 알지만 Scala를 배우고 싶은 프로그래머를 위한 것으로, Python의 언어 기능이 Scala에서는 어떻게 대응하는지 예제를 통해 보여주는 데 초점을 맞춥니다.

출처: Scala 3 Book

본문

소개

예제로 들어가기 전에, 이 첫 섹션에서 뒤따를 섹션들을 비교적 간결하게 소개하고 요약할게요. 두 언어를 먼저 높은 수준에서 비교하고, 그다음 일상적인 프로그래밍 수준에서 비교해요.

높은 수준의 유사점

높은 수준에서 Scala는 Python과 이런 유사점을 공유해요:

  • 둘 다 고수준 프로그래밍 언어라서 포인터나 수동 메모리 관리 같은 저수준 개념에 신경 쓸 필요가 없어요
  • 둘 다 비교적 간단하고 간결한 문법을 가져요
  • 둘 다 함수형 스타일의 프로그래밍을 지원해요
  • 둘 다 객체지향 프로그래밍(OOP) 언어이에요
  • 둘 다 컴프리헨션을 가져요: Python은 리스트 컴프리헨션, 딕셔너리 컴프리헨션, 제너레이터 표현식을, Scala는 for 컴프리헨션을 갖고 있죠
  • 두 언어 모두 람다와 고차 함수를 지원해요
  • 둘 다 빅데이터 처리를 위해 Apache Spark와 함께 쓸 수 있어요
  • 둘 다 훌륭한 라이브러리가 풍부해요

높은 수준의 차이점

높은 수준에서 Python과 Scala의 차이는 이래요:

  • Python은 동적 타입이고 Scala는 정적 타입이에요
  • 동적 타입이긴 하지만 Python은 타입 힌트로 "점진적 타이핑(gradual typing)"을 지원하며, 이는 mypy 같은 정적 타입 검사기가 검사해요
  • 정적 타입이긴 하지만 Scala의 타입 추론 같은 기능 덕분에 동적 언어처럼 느껴져요
  • Python은 인터프리터 방식이고, Scala 코드는 .class 파일로 컴파일되어 JVM(Java Virtual Machine) 위에서 실행돼요
  • JVM에서 실행되는 것 외에도 Scala.js 프로젝트 덕분에 Scala를 JavaScript 대체재로 쓸 수 있어요
  • Scala Native 프로젝트는 "시스템" 수준 코드를 작성하고 네이티브 실행 파일로 컴파일할 수 있게 해 줘요
  • Scala에서는 모든 것이 표현식이에요: if 문, for 루프, match 표현식, 심지어 try/catch 표현식까지 모두 반환 값을 가져요
  • Scala 관용구는 기본적으로 불변(immutability)을 선호해요: 불변 변수와 불변 컬렉션을 쓰도록 권장돼요
  • Scala는 동시성과 병렬 프로그래밍 지원이 뛰어나요

프로그래밍 수준의 유사점

일상적으로 코드를 쓸 때 Python과 Scala 사이에서 보게 될 유사점들을 볼게요:

  • Scala의 타입 추론 덕분에 종종 동적 타입 언어처럼 느껴져요
  • 두 언어 모두 문장 끝에 세미콜론을 쓰지 않아요
  • 두 언어 모두 중괄호와 괄호 대신 유의미한 들여쓰기(indentation)를 지원해요
  • 메서드를 정의하는 문법이 비슷해요
  • 둘 다 리스트, 딕셔너리(맵), 셋, 튜플을 가져요
  • 둘 다 매핑과 필터링을 위한 컴프리헨션을 가져요
  • 둘 다 훌륭한 IDE 지원을 가져요
  • Scala 3의 toplevel 정의를 사용하면 메서드, 필드, 그 외 정의를 어디에나 둘 수 있어요
  • 한 가지 차이는 Python은 메서드를 하나도 선언하지 않고도 작동할 수 있는 반면, Scala 3는 toplevel에서 모든 것을 할 수 없어요. 예를 들어 Scala 애플리케이션을 시작하려면 main 메서드(@main def)가 필요해요

프로그래밍 수준의 차이점

프로그래밍 수준에서도 매일 코드를 쓰면서 보게 될 차이점들이 몇 가지 있어요:

  • Scala 프로그래밍은 매우 일관성 있게 느껴져요:
    • valvar 필드가 필드와 파라미터를 정의하는 데 일관되게 사용돼요
    • 리스트, 맵, 셋, 튜플이 모두 비슷하게 생성되고 접근돼요. 예를 들어 모든 타입을 만들 때 괄호를 써요—List(1,2,3), Set(1,2,3), Map(1->"one")—마치 다른 Scala 클래스를 만드는 것처럼요
    • 컬렉션 클래스는 대체로 대부분의 고차 함수를 공유해요
    • 패턴 매칭이 언어 전반에 걸쳐 일관되게 사용돼요
    • 메서드에 전달되는 함수를 정의하는 문법이 익명 함수를 정의하는 문법과 같아요
  • Scala 변수와 파라미터는 val(불변) 또는 var(가변) 키워드로 정의돼요
  • Scala 관용구는 불변 데이터 구조를 선호해요
  • 주석: Python은 #을 주석에 쓰고, Scala는 C, C++, Java 스타일인 //, /*...*/, /**...*/을 써요
  • 명명 규칙: Python 표준은 my_list처럼 밑줄을 쓰고, Scala는 myList를 써요
  • Scala는 정적 타입이라 메서드 파라미터, 메서드 반환 값, 그 외 여러 곳에서 타입을 선언해요
  • 패턴 매칭과 match 표현식이 Scala에서 광범위하게 사용돼요(그리고 여러분의 코드 작성 방식을 바꿀 거예요)
  • 트레이트가 Scala에서 많이 쓰여요. Python에서는 인터페이스와 추상 클래스를 덜 자주 써요
  • Scala의 문맥적 추상화와 용어 추론은 다양한 기능들을 제공해요:
    • 확장 메서드를 쓰면 명확한 문법으로 클래스에 새 기능을 쉽게 추가할 수 있어요
    • 다중 우주 평등(Multiversal equality)은 컴파일 시간에 말이 되는 비교로만 평등 비교를 제한할 수 있게 해 줘요
  • Scala에는 최첨단 오픈소스 함수형 프로그래밍 라이브러리들이 있어요("Awesome Scala" 목록 참고)
  • 객체, 이름 붙은 파라미터, 중위 표기법, 선택적 괄호, 확장 메서드, 고차 함수 같은 기능 덕분에 여러분만의 "제어 구조"와 DSL을 만들 수 있어요
  • Scala 코드는 JVM에서 실행될 수 있고(Scala Native와 GraalVM을 사용해) 네이티브 이미지로 컴파일되어 높은 성능을 낼 수도 있어요
  • 그 외 많은 것들이 있어요: 컴패니언 클래스와 객체, 매크로, 숫자 리터럴, 여러 파라미터 목록, 교집합 타입, 타입 수준 프로그래밍 등

예제로 비교한 기능

이제 소개는 됐으니, 다음 섹션들에서 Python과 Scala 프로그래밍 언어 기능을 나란히 비교해 볼게요.

주석

Python은 #을 주석에 쓰고, Scala의 주석 문법은 C, C++, Java 같은 언어와 같아요:

# a comment
// a comment
/* ... */
/** ... */

변수 할당

이 예제들은 Python과 Scala에서 변수를 만드는 방법을 보여줘요.

정수와 문자열 변수 만들기:

x = 1
x = "Hi"
y = """foo
       bar
       baz"""
val x = 1
val x = "Hi"
val y = """foo
           bar
           baz"""

리스트:

x = [1,2,3]
val x = List(1,2,3)

딕셔너리/맵:

x = {
  "Toy Story": 8.3,
  "Forrest Gump": 8.8,
  "Cloud Atlas": 7.4
}
val x = Map(
  "Toy Story" -> 8.3,
  "Forrest Gump" -> 8.8,
  "Cloud Atlas" -> 7.4
)

셋:

x = {1,2,3}
val x = Set(1,2,3)

튜플:

x = (11, "Eleven")
val x = (11, "Eleven")

Scala 필드를 가변으로 만들려면 변수 정의에 val 대신 var를 쓰면 돼요.

var x = 1
x += 1

다만 Scala에서의 경험칙은, 변수가 특별히 변경될 필요가 없으면 항상 val을 쓰는 거예요.

FP 스타일 레코드

Scala의 케이스 클래스는 Python의 frozen dataclass와 비슷해요.

생성자 정의:

from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Person:
    name: str
    age: int
case class Person(name: String, age: Int)

인스턴스 생성 및 사용:

p = Person("Alice", 42)
p.name   # Alice
p2 = replace(p, age=43)
val p = Person("Alice", 42)
p.name   // Alice
val p2 = p.copy(age = 43)

OOP 스타일 클래스와 메서드

이 섹션은 OOP 스타일 클래스와 메서드와 관련된 기능을 비교해 줘요.

OOP 스타일 클래스, 기본 생성자:

class Person(object):
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f'Hello, my name is {self.name}')
class Person (var name: String):
    def speak() = println(s"Hello, my name is $name")

인스턴스 생성 및 사용:

p = Person("John")
p.name   # John
p.name = 'Fred'
p.name   # Fred
p.speak()
val p = Person("John")
p.name   // John
p.name = "Fred"
p.name   // Fred
p.speak()

한 줄 메서드:

def add(a, b): return a + b
def add(a: Int, b: Int): Int = a + b

여러 줄 메서드:

def walkThenRun():
    print('walk')
    print('run')
def walkThenRun() =
    println("walk")
    println("run")

인터페이스, 트레이트, 상속

Java 8 이상에 익숙하다면, Scala 트레이트가 그 Java 인터페이스와 비슷해요. 트레이트는 Scala에서 항상 쓰이고, Python의 인터페이스(Protocols)와 추상 클래스는 훨씬 덜 자주 쓰여요. 그래서 둘을 비교하기보다, 이 예제는 Scala 트레이트로 시뮬레이션된 수학 문제에 대한 작은 해법을 만드는 방법을 보여줘요:

trait Adder:
  def add(a: Int, b: Int) = a + b

trait Multiplier:
  def multiply(a: Int, b: Int) = a * b

// create a class from the traits
class SimpleMath extends Adder, Multiplier
val sm = new SimpleMath
sm.add(1,1)        // 2
sm.multiply(2,2)   // 4

트레이트를 클래스와 객체에 사용하는 방법은 훨씬 많지만, 이 예제만으로도 트레이트가 개념을 논리적 동작 그룹으로 조직하고, 필요할 때 그것들을 합쳐 완전한 해법을 만드는 데 어떻게 쓰이는지 감을 잡을 수 있을 거예요.

제어 구조

이 섹션은 Python과 Scala의 제어 구조를 비교해요. 두 언어 모두 if/else, while, for 루프, try 같은 구조를 가져요. Scala에는 또한 match 표현식이 있습니다.

if 문, 한 줄:

if x == 1: print(x)
if x == 1 then println(x)

if 문, 여러 줄:

if x == 1:
    print("x is 1, as you can see:")
    print(x)
if x == 1 then
    println("x is 1, as you can see:")
    println(x)

if, else if, else:

if x < 0:
    print("negative")
elif x == 0:
    print("zero")
else:
    print("positive")
if x < 0 then
    println("negative")
else if x == 0 then
    println("zero")
else
    println("positive")

if에서 값 반환하기:

min_val = a if a < b else b
val minValue = if a < b then a else b

메서드 본문으로서의 if:

def min(a, b):
    return a if a < b else b
def min(a: Int, b: Int): Int =
    if a < b then a else b

while 루프:

i = 1
while i < 3:
    print(i)
    i += 1
var i = 1
while i < 3 do
    println(i)
    i += 1

range를 쓰는 for 루프:

for i in range(0,3):
    print(i)
// preferred
for i <- 0 until 3 do println(i)

// also available
for (i <- 0 until 3) println(i)

// multiline syntax
for
    i <- 0 until 3
do
    println(i)

리스트를 쓰는 for 루프:

for i in ints: print(i)

for i in ints:
    print(i)
for i <- ints do println(i)

for 루프, 여러 줄:

for i in ints:
    x = i * 2
    print(f"i = {i}, x = {x}")
for
    i <- ints
do
    val x = i * 2
    println(s"i = $i, x = $x")

여러 "range" 제너레이터:

for i in range(1,3):
    for j in range(4,6):
        for k in range(1,10,3):
            print(f"i = {i}, j = {j}, k = {k}")
for
    i <- 1 to 2
    j <- 4 to 5
    k <- 1 until 10 by 3
do
    println(s"i = $i, j = $j, k = $k")

가드를 가진 제너레이터(if 표현식):

for i in range(1,11):
    if i % 2 == 0:
        if i < 5:
            print(i)
for
    i <- 1 to 10
    if i % 2 == 0
    if i < 5
do
    println(i)

한 줄에 여러 if 조건:

for i in range(1,11):
    if i % 2 == 0 and i < 5:
        print(i)
for
    i <- 1 to 10
    if i % 2 == 0 && i < 5
do
    println(i)

컴프리헨션:

xs = [i * 10 for i in range(1, 4)]
# xs: [10,20,30]
val xs = for i <- 1 to 3 yield i * 10
// xs: Vector(10, 20, 30)

지연 평가 컴프리헨션:

from itertools import count
all_squares = (n**2 for n in count())  # generator expression
# all_squares: <generator object <genexpr> at ...>
val allSquares = for n <- LazyList.from(0) yield n * n
// allSquares: LazyList(<not computed>)

match 표현식:

# From 3.10, Python supports structural pattern matching
# You can also use dictionaries for basic "switch" functionality
match month:
    case 1:
        monthAsString = "January"
    case 2:
        monthAsString = "February"
    case _:
        monthAsString = "Other"
val monthAsString = month match
    case 1 => "January"
    case 2 => "February"
    _ => "Other"

switch/match:

# Only from Python 3.10
match i:
    case 1 | 3 | 5 | 7 | 9:
        numAsString = "odd"
    case 2 | 4 | 6 | 8 | 10:
        numAsString = "even"
    case _:
        numAsString = "too big"
val numAsString = i match
    case 1 | 3 | 5 | 7 | 9 => "odd"
    case 2 | 4 | 6 | 8 | 10 => "even"
    case _ => "too big"

try, catch, finally:

try:
    print(a)
except NameError:
    print("NameError")
except:
    print("Other")
finally:
    print("Finally")
try
    writeTextToFile(text)
catch
    case ioe: IOException =>
        println(ioe.getMessage)
    case fnf: FileNotFoundException =>
        println(fnf.getMessage)
finally
    println("Finally")

match 표현식과 패턴 매칭은 Scala 프로그래밍 경험의 큰 부분이지만, 여기서는 그 일부만 보여줬어요. 더 많은 예제는 Control Structures 페이지에서 확인할 수 있어요.

컬렉션 클래스

이 섹션은 Python과 Scala에서 사용 가능한 컬렉션 클래스(리스트, 딕셔너리/맵, 셋, 튜플)를 비교해요.

리스트

Python에 리스트가 있다면, Scala에는 필요에 따라 사용할 수 있는 여러 특화된 가변·불변 시퀀스 클래스가 있어요. Python 리스트는 가변이라서 가장 직접적으로 Scala의 ArrayBuffer와 비교돼요.

Python 리스트 & Scala 시퀀스:

a = [1,2,3]
// use different sequence classes
// as needed
val a = List(1,2,3)
val a = Vector(1,2,3)
val a = ArrayBuffer(1,2,3)

리스트 요소 접근:

a[0]
a[1]
a(0)
a(1)   // just like all other method calls

리스트 요소 갱신:

a[0] = 10
a[1] = 20
// ArrayBuffer is mutable
a(0) = 10
a(1) = 20

두 리스트 결합:

c = a + b
val c = a ++ b

리스트 순회:

for i in ints: print(i)

for i in ints:
    print(i)
// preferred
for i <- ints do println(i)

// also available
for (i <- ints) println(i)

Scala의 주요 시퀀스 클래스는 List, Vector, ArrayBuffer예요. ListVector는 불변 시퀀스를 원할 때 쓰는 주요 클래스이고, ArrayBuffer는 가변 시퀀스를 원할 때 쓰는 주요 클래스예요. (Scala에서 "buffer"는 늘어나고 줄어들 수 있는 시퀀스를 말해요.)

딕셔너리/맵

Python 딕셔너리는 가변 Scala Map 클래스와 비슷해요. 다만 기본 Scala 맵은 불변이고, 새 맵을 쉽게 만들 수 있는 여러 변환 메서드를 가지고 있어요.

딕셔너리/맵 생성:

my_dict = {
    'a': 1,
    'b': 2,
    'c': 3
}
val myMap = Map(
    "a" -> 1,
    "b" -> 2,
    "c" -> 3
)

딕셔너리/맵 요소 접근:

my_dict['a']   # 1
myMap("a")   // 1

for 루프를 쓰는 딕셔너리/맵:

for key, value in my_dict.items():
    print(key)
    print(value)
for (key,value) <- myMap do
    println(key)
    println(value)

Scala에는 필요에 따라 쓸 수 있는 특화된 Map 클래스들이 더 있어요.

Python 셋은 가변 Scala Set 클래스와 비슷해요.

셋 생성:

set = {"a", "b", "c"}
val set = Set(1,2,3)

중복 요소:

set = {1,2,1}
# set: {1,2}
val set = Set(1,2,1)
// set: Set(1,2)

Scala에는 필요에 따라 쓸 수 있는 특화된 Set 클래스들이 더 있어요.

튜플

Python과 Scala의 튜플도 비슷해요.

튜플 생성:

t = (11, 11.0, "Eleven")
val t = (11, 11.0, "Eleven")

튜플 요소 접근:

t[0]   # 11
t[1]   # 11.0
t(0)   // 11
t(1)   // 11.0

컬렉션 클래스의 메서드

Python과 Scala는 흔한 함수형 메서드 몇 가지를 동일하게 사용할 수 있어요:

  • map
  • filter
  • reduce

Python에서 이것들을 람다 표현식과 함께 쓰는 데 익숙하다면, Scala에도 컬렉션 클래스의 메서드로 비슷한 방식이 있다는 걸 알게 될 거예요. 이 기능을 보여주기 위해 두 개의 샘플 리스트를 준비했어요:

numbers = [1,2,3]           // python
val numbers = List(1,2,3)   // scala

그 리스트들은 매핑과 필터링 알고리즘을 적용하는 방법을 보여주는 다음 표에서 사용돼요.

컴프리헨션으로 매핑:

x = [i * 10 for i in numbers]
val x = for i <- numbers yield i * 10

컴프리헨션으로 필터링:

evens = [i for i in numbers if i % 2 == 0]
val evens = numbers.filter(_ % 2 == 0)
// or
val evens = for i <- numbers if i % 2 == 0 yield i

컴프리헨션으로 매핑 & 필터링:

x = [i * 10 for i in numbers if i % 2 == 0]
val x = numbers.filter(_ % 2 == 0).map(_ * 10)
// or
val x = for i <- numbers if i % 2 == 0 yield i * 10

매핑:

x = map(lambda x: x * 10, numbers)
val x = numbers.map(_ * 10)

필터링:

f = lambda x: x > 1
x = filter(f, numbers)
val x = numbers.filter(_ > 1)

Scala 컬렉션 메서드

Scala 컬렉션 클래스에는 코드를 단순화하는 100개가 넘는 함수형 메서드가 있어요. Python에서는 이런 함수 중 일부가 itertools 모듈에 있어요. map, filter, reduce 외에도 Scala에서 흔히 쓰는 메서드들은 아래에 나열돼 있어요. 그 메서드 예제들에서:

  • c는 컬렉션을 가리켜요
  • p는 술어(predicate)예요
  • f는 함수, 익명 함수, 또는 메서드예요
  • n은 정수 값을 가리켜요

이용 가능한 필터링 메서드 몇 가지:

Method Description
c1.diff(c2) c1과 c2의 요소 차이(difference)를 반환한다.
c.distinct c에서 고유한 요소들을 반환한다.
c.drop(n) 컬렉션의 첫 n개 요소를 제외한 모든 요소를 반환한다.
c.filter(p) 술어가 참인 컬렉션의 모든 요소를 반환한다.
c.head 컬렉션의 첫 번째 요소를 반환한다. (컬렉션이 비어 있으면 NoSuchElementException을 던진다.)
c.tail 컬렉션의 첫 번째 요소를 제외한 모든 요소를 반환한다. (비어 있으면 UnsupportedOperationException을 던진다.)
c.take(n) 컬렉션 c의 첫 n개 요소를 반환한다.

변환(transformer) 메서드 몇 가지:

Method Description
c.flatten 컬렉션의 컬렉션(예: 리스트의 리스트)을 단일 컬렉션(단일 리스트)으로 변환한다.
c.flatMap(f) 컬렉션 c의 모든 요소에 f를 적용한(map) 뒤, 결과 컬렉션들의 요소를 평탄화(flatten)하여 새 컬렉션을 반환한다.
c.map(f) 컬렉션 c의 모든 요소에 f를 적용하여 새 컬렉션을 만든다.
c.reduce(f) "환원" 함수 f를 c의 연속 요소들에 적용하여 단일 값으로 만든다.
c.sortWith(f) 비교 함수 f로 정렬된 c의 버전을 반환한다.

흔한 그룹화 메서드 몇 가지:

Method Description
c.groupBy(f) f에 따라 컬렉션을 컬렉션들의 Map으로 분할한다.
c.partition(p) 술어 p에 따라 두 컬렉션을 반환한다.
c.span(p) 두 컬렉션으로 이루어진 컬렉션을 반환한다. 첫 번째는 c.takeWhile(p)로, 두 번째는 c.dropWhile(p)로 만들어진다.
c.splitAt(n) 컬렉션 c를 요소 n에서 분할하여 두 컬렉션으로 이루어진 컬렉션을 반환한다.

정보 및 수학 메서드 몇 가지:

Method Description
c1.containsSlice(c2) c1이 시퀀스 c2를 포함하면 true를 반환한다.
c.count(p) p가 참인 c의 요소 수를 센다.
c.distinct c에서 고유한 요소들을 반환한다.
c.exists(p) 컬렉션의 어떤 요소에 대해 p가 참이면 true를 반환한다.
c.find(p) p와 일치하는 첫 번째 요소를 반환한다. 요소는 Option[A]로 반환된다.
c.min 컬렉션에서 가장 작은 요소를 반환한다. (java.lang.UnsupportedOperationException을 던질 수 있다.)
c.max 컬렉션에서 가장 큰 요소를 반환한다. (java.lang.UnsupportedOperationException을 던질 수 있다.)
c.slice(from, to) 요소 from에서 시작해 요소 to에서 끝나는 요소 구간을 반환한다.
c.sum 컬렉션의 모든 요소의 합을 반환한다. (컬렉션 요소에 Ordering이 정의되어 있어야 한다.)

이 메서드들이 리스트에서 어떻게 동작하는지 보여주는 예제 몇 가지를 볼게요:

val a = List(10, 20, 30, 40, 10)      // List(10, 20, 30, 40, 10)
a.distinct                            // List(10, 20, 30, 40)
a.drop(2)                             // List(30, 40, 10)
a.dropRight(2)                        // List(10, 20, 30)
a.dropWhile(_ < 25)                   // List(30, 40, 10)
a.filter(_ < 25)                      // List(10, 20, 10)
a.filter(_ > 100)                     // List()
a.find(_ > 20)                        // Some(30)
a.head                                // 10
a.headOption                          // Some(10)
a.init                                // List(10, 20, 30, 40)
a.intersect(List(19,20,21))           // List(20)
a.last                                // 10
a.lastOption                          // Some(10)
a.slice(2,4)                          // List(30, 40)
a.tail                                // List(20, 30, 40, 10)
a.take(3)                             // List(10, 20, 30)
a.takeRight(2)                        // List(40, 10)
a.takeWhile(_ < 30)                   // List(10, 20)

이 메서드들은 Scala의 흔한 패턴을 보여줘요: 객체에 사용할 수 있는 함수형 메서드. 이 메서드들 중 어느 것도 초기 리스트 a를 변경하지 않아요. 대신 주석 뒤에 보이는 데이터를 모두 반환합니다.

사용할 수 있는 메서드는 훨씬 더 많지만, 이 설명과 예제만으로도 미리 만들어진 컬렉션 메서드가 얼마나 강력한지 맛보는 데 충분할 거예요.

열거형 (Enums)

이 섹션은 Python과 Scala 3의 열거형(enumerations)을 비교해요.

열거형 생성:

from enum import Enum, auto
class Color(Enum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()
enum Color:
    case Red, Green, Blue

값과 비교:

Color.RED == Color.BLUE  # False
Color.Red == Color.Blue  // false

파라미터화된 열거형:

N/A
enum Color(val rgb: Int):
    case Red   extends Color(0xFF0000)
    case Green extends Color(0x00FF00)
    case Blue  extends Color(0x0000FF)

사용자 정의 열거형 멤버:

N/A
enum Planet(
    mass: Double,
    radius: Double
  ):
    case Mercury extends
        Planet(3.303e+23, 2.4397e6)
    case Venus extends
        Planet(4.869e+24, 6.0518e6)
    case Earth extends
        Planet(5.976e+24, 6.37814e6)
    // more planets ...

    // fields and methods
    private final val G = 6.67300E-11
    def surfaceGravity = G * mass /
        (radius * radius)
    def surfaceWeight(otherMass: Double)
        = otherMass * surfaceGravity

Scala에만 있는 개념

현재 Python에는 대응하는 기능이 없는 다른 Scala 개념들이 있어요. 자세한 내용은 아래 링크를 따라가 보세요:

  • 문맥적 추상화와 관련된 대부분의 개념, 예: 확장 메서드, 타입 클래스, 암시적 값
  • Scala는 여러 파라미터 목록을 허용해서, 부분 적용 함수(partially-applied functions) 같은 기능과 여러분만의 DSL을 만드는 능력을 제공해요
  • 여러분만의 제어 구조와 DSL을 만드는 능력
  • 다중 우주 평등(Multiversal equality): 컴파일 시간에 어떤 평등 비교가 말이 되는지 제어하는 능력
  • 중위(infix) 메서드
  • 매크로(Macros)

Scala와 가상 환경

Scala에서는 Python 가상 환경과 같은 것을 명시적으로 설정할 필요가 없어요. 기본적으로 Scala 빌드 도구가 프로젝트 의존성을 관리해서, 사용자가 수동 패키지 설치에 대해 생각할 필요가 없어요. 예를 들어 sbt 빌드 도구를 사용한다면, build.sbt 파일의 libraryDependencies 설정 아래 의존성을 지정하고, 그런 다음

cd myapp
sbt compile

을 실행하면 그 특정 프로젝트의 모든 의존성이 자동으로 해결돼요. 다운로드된 의존성의 위치는 대체로 빌드 도구의 구현 세부사항이고, 사용자는 직접 그 다운로드된 의존성과 상호작용할 필요가 없어요. 예를 들어 sbt 의존성 캐시 전체를 삭제해도, 다음 컴파일에서 sbt가 필요한 모든 의존성을 다시 자동으로 해결하고 다운로드해요.

이것은 Python과 다르죠. Python은 기본적으로 의존성이 시스템 전체 또는 사용자 전체 디렉토리에 설치되고, 프로젝트별로 격리된 환경을 얻으려면 해당 가상 환경을 만들어야 해요. 예를 들어 venv 모듈을 사용해 특정 프로젝트용 환경을 이렇게 만들 수 있어요:

cd myapp
python3 -m venv myapp-env
source myapp-env/bin/activate
pip install -r requirements.txt

이것은 모든 의존성을 프로젝트의 myapp/myapp-env 디렉토리 아래에 설치하고, 셸 환경 변수 PATH를 바꿔서 myapp-env에서 의존성을 찾게 해요. 이 모든 수동 과정은 Scala에서는 필요 없습니다.

더 알아보기

  • 컬렉션, 메서드, 제어 구조 등 이 페이지에서 다룬 내용의 자세한 버전을 Scala 3 Book의 해당 섹션에서 확인할 수 있어요.
  • 패턴 매칭 예제를 더 보려면 Control Structures 페이지를 참고하세요.