1. Scala 소개
- 2003년 스위스 로잔공대의 Martin Odersky가 개발한 강력한 타입(Storngly typed) 방식의 확장 가능한(Scalable) 프로그래밍 언어(Language)
- 객체 지향형과 함수형 프로그래밍을 모두 지원
- 스칼라 코드는 자바 바이트코드로 변환될 수 있음(JVM이 읽을 수 있는 자바가 컴파일된 결과인 .class 파일이 있으면 scala도 읽을 수 있음, 100% 호환은 아님)
- Scala는 대규모 데이터 분산 컴퓨팅 분야에 많이 사용됨 (데이터 엔지니어에게는 유요한 기술)
- 대규모 데이터에 주로 쓰이는 만큼 대중적이고 인기가 많은 언어는 아님
2. 변수, 상수
- var
- 변수 선언 (같은 타입에 한해 변경 가능)
- val
- 상수 선언 (초기값 변경 불가)
- 변수와 상수의 이름 규칙 (Naming Convention)
- 변수나 상수는 lowerCamelCase
- 클래스나 오브젝트는 UpperCamelCase
val UpperCamelCase: String = "UpperCamelCase"
val lowerCamelCase: String = "lowerCamelCase"
2. 연산자
- 산수 연산
val a: Int = 3
val b: Int = 5
println(a + b); // 더하기 연산
println(a - b); // 빼기 연산
println(a * b); // 곱하기 연산
println(a / b); // 나누기 연산 (경우에 따라 결과가 float일 수 있음)
println(a % b); // 나머지 연산 (a 를 b 로 나눈 나머지. 예) 1 % 2 => 1, 3 % 2 => 1)
- 대입 연산
var a: Int = 5
var b: Int = 10
a += b // a = a + b
println(a) // 15 = 5 + 10
a -= b // a = a - b
println(a) // 5 = 15 - 10
a *= b // a = a * b
println(a) // 50 = 5 * 10
a /= b // a = a / b
println(a) // 5 = 50 / 10
a %= b // a = a % b
println(b) // 10 = 5 % 10 (% 는 나머지 연산입니다.)
- 관계 연산
val num1: Int = 10
val num2: Int = 10
val num3: Int = 20
println(num1 == num2) // 10 == 10 => true
println(num1 != num2) // 10 != 10 => false
println(num1 > num2) // 10 > 10 => false
println(num1 >= num2) // 10 >= 10 => true
println(num1 < num3) // 10 < 20 => true
println(num1 <= num3) // 10 <= 20 => true
- 논리 연산
val t: Boolean = true
val f: Boolean = false
println(t && t) // true (둘 다 모두 true 면 true)
println(t && f) // false
println(f && f) // false
println(t || t) // true (둘 중 하나라도 true 면 true)
println(t || f) // true
println(f || f) // false
println(!t) // false (true 의 반대값)
println(!f) // true (false 의 반대값)
println(!(t || f)) // false (t || f 의 반대값)
- 비트 연산
var a: Int = 2 // 2진수(0010)
var b: Int = 6 // 2진수(0110)
println(a & b) // 2. bit 단위로 and 연산 (0010 and 0110 => 0010 => 10진수(2))
println(a | b) // 6. bit 단위로 or 연산 (0010 or 0110 => 0110 => 10진수(6))
println(a ^ b) // 4. bit 단위로 xor 연산 (0010 xor 0110 => 0100 => 10진수(4))
println(~b) // -7. bit 단위로 not 연산 (not 0110 => 1001 => 10진수(-7))
// 정확히는 "ones complement"로 signed bit 이 바뀌어서 계산이
// 헷갈릴 수 있습니다만 현재 수준에서 자세히 아실 필요는 없습니다.
println(a << 1) // 4. bit 단위로 왼쪽으로 밀기 연산 (0010 << 1 => 0100 => 10진수(4))
println(a >> 1) // 1. bit 단위로 오른쪽으로 밀기 연산 (0010 >> 1 => 0001 => 10진수(1))
- 연산자 처리 순서
// 1. () []
// 2. ! ~
// 3. * / %
// 4. + -
// 5. >> <<
// 6. < <= > >=
// 7. == !=
// 8. &
// 9. ^
// 10. |
// 11. &&
// 12. ||
// 13. 대입 연산자 (= += -= ...)
println(1.0 + 2.0 * 3.0 / 4.0) // 2.5
println((1.0 + 2.0) * 3.0 / 4.0) // 2.25
var a: Int = 1
var b: Int = 2
var c: Int = 3
a += b - c
println(a) // 0 (- 가 += 보다 먼저 선행되기에 a += (b - c) 라고 볼 수 있습니다.
println(b) // 2
println(c) // 3
3. Pattern Matching
- Pattern Matching이란?
Pattern Matching 은 스칼라에서 가장 많이 쓰이는 기능 중 하나입니다. 자바의 switch 기능보다 더 강력한 기능이라고 생각하시면 됩니다. 여러 번의 if/else 보다 pattern matching 을 사용하시면 더 편리하고 간결한 코딩이 가능해집니다. switch 대신 match 키워드를 사용하고 각각의 경우는 case 키워드에 매칭됩니다.
val anything: Int = 42
anything match {
// anything 이 0 이면 호출됩니다.
case 0 => println("Matched 0!")
// anything 이 1 이면 호출됩니다.
case 1 => println("Matched 1!")
// anything 이 0도 1도 아닐 때 디폴트로 호출됩니다.
case _ => println("Oops! No match!")
}
// 위의 경우에서는 마지막 println("Oops! No match!") 가 호출됩니다.
- Match Expression
Match 블락은 문장(Statement)이 아닌 식(Expression)입니다. 따라서 값을 반환하고 타입을 지정해줄 수 있습니다. 이는 함수형 프로그래밍에서 아주 중요한 기능입니다.
def example(anything: Int): String = anything match {
// anything 이 0 이면 호출됩니다.
case 0 => "Matched 0!"
// anything 이 1 이면 호출됩니다.
case 1 => "Matched 1!"
// anything 이 0도 1도 아닐 때 디폴트로 호출됩니다.
case _ => "Oops! No match!"
}
val case0: String = example(0)
println(case0) // "Matched 0!"
val case1: String = example(1)
println(case1) // "Matched 1!"
val case2: String = example(2)
println(case2) // "Oops! No match!"
val casestring0: Int = example(0) // Not allowed!
val casestring0: String = example("0") // Not allowed!
4. 조건식
- if
- condition 에는 true 또는 false 만 들어갈 수 있습니다.
- condition 이 true 일 때만 코드가 실행됩니다.
- if 식은 항상 결과를 돌려줍니다
val condition: Boolean = true
if(condition) {
"condition is True!"
}
- 값을 반환 하는 if
- 이전 match 파트에서 match 는 값을 반환하는 “식 (expression)” 이라고 했습니다.
- if 식은 값을 리턴합니다. 따라서 match 식과 비슷하게 if 도 “식(expression)”으로 사용할 수 있습니다.
- 역으로, 값을 리턴하지 않으면 “문장 (statement)” 입니다. if 는 문장으로도 사용할 수 있습니다.
- 식과 문장의 차이는 스칼라를 사용할수록 더 명확해질 것입니다.
// if가 수식(expression) 으로 사용
val a: Int = 1
val b: Int = 2
val smallerNum = if(a < b) a else b // if 는 a의 값을 반환
println(smallerNum) // 1
// if가 문장(statement) 로 사용
val c: Int = 3
val d: Int = 4
if (c < d) println("d is larger!")
- if-else
- if 의 조건이 거짓이면 else 에 속하는 코드가 실행됩니다.
val a: Int = 1
val b: Int = 2
if (a == b) {
println("a and b is equal!") // 실행 X
} else {
println("a and b is NOT equal!") // 실행 O
}
- nested if-else
- if 와 else 는 중첩해서 사용할 수 있습니다.
if (condition1) {
if (condition2) {
// condition_1 = true & condition_2 = true
}
else {
// condition_1 = true & condition_2 = false
}
} else {
if (condition2) {
// condition_1 = false & condition_2 = true
} else {
// condition_1 = false & condition_2 = false
}
}
- if-else if ladder
- else if 를 사용해 조금 더 복잡한 구성을 만들 수 있습니다.
val condition1: Boolean = true
val condition2: Boolean = false
if(condition1) {
// 컨디션1이 참
"condition 1 is True!"
} else if (condition2) {
// 컨디션1은 거짓 컨디션2는 참
// 컨디션2가 참이어도 컨디션1이 참이면 실행되지 않습니다.
"condition 1 is false and condition 2 is true!"
} else {
// 컨디션1 과 컨디션2 모두 거짓
"Both condition 1 and condition 2 are false!"
}
5. 반복문
- for
- For loop 은 화살표 <- 를 사용해서 컬렉션 내의 요소들을 각각 처리할 수 있습니다.
// "nums" list 의 요소들을 각자 처리해줍니다
for (num <- nums) println(num)
// 출력:
// 1
// 2
// 3
// 4
// 5
- for each
- for each 는 for 과 같은 기능을 하지만 조금 더 사람에 익숙한 언어로 표현한 방법입니다.
- Map 타입과도 함께 사용할 수 있습니다.
nums.foreach(println)
// 출력:
// 1
// 2
// 3
// 4
// 5
val numbers = Map(
"one" -> 1,
"two" -> 2,
"three" -> 3
)
for ((key, value) <- numbers) println(s"Number $key : $value")
/**
* 출력
* Number one (1)
* Number two (2)
* Number three (3)
*/
6. 반복식
이전에 언급했듯이 for 은 문장이 아닌 식으로 사용할 수 있습니다. 이전까지의 for-loop는 println 과 같은 side effect*를 위해서 사용되었다면 for-expression 은 기존 컬렉션(List/Array 같은)으로부터 새로운 컬렉션을 만들 때 유용하게 쓰입니다.
- yield
- for 을 식으로 사용하기 위해서는 yield (뜻: 생성)이란 키워드를 알아야 합니다. for loop가 돌면서 각 요소를 처리한 후 값을 yield (또는 return 이라고 해석해도 무방합니다) 한다고 이해하시면 됩니다.
val doubleNumbers = for (num <- nums) num * 2 // yield 가 없는 것을 주목해 주세요
println(doubleNumbers)
// 출력: ()
// () 는 Scala 에서 Unit 타입 값입니다. 리턴값이 없을 때 출력됩니다.
val doubleNumbersWithYield = for (num <- nums) yield num * 2
println(doubleNumbersWithYield)
// 출력: List(2, 4, 6, 8, 10)
- block yield
yield를 사용하실 때 복잡한 코드를 for loop 안에 넣고 싶을 때는 중괄호를 씌워주면 됩니다.
val someNumbers = for (num <- nums) yield {
val numPlusOne = num + 1
val numMinusOne = num -1
numPlusOne * numMinusOne
}
println(someNumbers)
// 출력: List(0, 3, 8, 15, 24)
여기서 다른 언어들을 다뤄봤다면 (예를 들어 Python), yield 를 중괄호 안에 넣어도 되지 않을까? 라는 생각을 하실 수 있습니다.
val someNumbers = for (num <- nums) {
val numPlusOne = num + 1
val numMinusOne = num -1
yield numPlusOne * numMinusOne // Not allowed!
}
이렇게 하면 yield 후에도 다른 side effect ( println 같은 )를 처리할 수 있어서 코드가 더 유연해질 수 있겠죠? 하지만 Scala 에서는 이렇게 사용하실 수 없습니다 (컴파일 에러). for직후에 yield 가 와야만 Scala 컴파일러에서 해당 블락이 for-expression 이라는 것을 알고 그에 맞춰 코드를 변환해줍니다.
다음 포스팅에서는 scala 프로그래밍 심화 버전으로 객체 지향형 프로그래밍과 함수형 프로그래밍에 대해 알아보겠습니다.
'ETC > Scala' 카테고리의 다른 글
[Scala] 스칼라 프로그래밍 심화 (0) | 2025.05.27 |
---|