A Scalable Language
스칼라는 정교하고 간결한 프로그래밍 언어입니다.
세계 최대 규모의 웹사이트, 애플리케이션 및 데이터 엔지니어링 인프라를 지원합니다.
Scala는 무엇인가?
- Scala는 범용 프로그래밍 언어입니다.
- Scala는 새롭게 개선된 Java와 같습니다.
- Scala는 JVM에서 실행됩니다. - 이는 Java가 널리 사용되기 때뭉네 많은 기존 인프라에서 실행된다는 것을 의미합니다.
Scala를 왜 사용햐는가?
- Scala는 확장 가능한 언어라는 뜻입니다. ( Sca(lable) + La(nguage))
- 작은 스크립트 작성부터 사용자의 요구에 따라 성장하도록 설계되었습니다. ( 데이터를 위한 대규모 시스템 구축, 처리, 분산 컴퓨팅 등 )
- Scala는 완전한 프로그래밍 언어는 아니지만, 모든 것이 프로그램의 요구 사항에 맞게 사용자 지정될 수 있습니다.
- Scala는 유연하고, 편리합니다. ( 커스텀마이징 가능 )
누가 Scala를 사용하는가? - 직무
- 소프트웨어 엔지니어
- 데이터 엔지니어
- 데이터 사이언티스트
- 머신러닝 엔지니어
누가 Scala를 사용하는가? - 산업
- 금융
- 테크
- 헬스케어
- …등
이러한 산업들은, Scala로 작성된 Apache Spark와 같은 데이터 처리 도구와 함께 Scala를 채택하고 있습니다.
Scala = OOP + FP
Scala는 어떤 언어보다 객체 지향 및 함수형 프로그래밍 개념을 융합합니다.
- Scala는 객체 지향적입니다.
- 모든 값이 객체
- 모든 작업이 메서드 호출
- Scala는 함수형 프로그래밍 언어입니다.
- Scala에서 함수는 정수나 문자열처럼 first-class value ( 다른 함수에 인수로 전달하고, 함수에서 반환하고, 변수에 저장하는 등의 작업을 할 수 있습니다. )
- 프로그램의 작동은 데이터를 제자리에서 변경하는 것이 아니라 입력 값을 출력 값에 매핑해야 합니다.
(추가) 왜 Scala를 사용하나요?
- Scala는 간결합니다.
- 스칼라 프로그래밍은 자바 프로그래밍에 비해 1/10까지 짧아지는 경향이 있습니다.
- Scala는 high-level 언어이기 때문에, Scala의 코드에서 컴퓨터의 세부 사항을 다루지 않습니다.
- Scala에는 코드의 장황함을 줄이고 언어 유연성을 추가하는 Advanced 정적 유형 시스템이 있습니다.
- Scala는 JVM기반이므로, 이전에 존재하는 Java코드를 기반으로 빌드할 수 있습니다.
The Scala interpreter
scala> 2 + 3
val res0: Int = 5
scala> res0 * 2
val res1: Int = 10
scala> println("Let's play Twenty-One!")
Let's play Twenty-One!
res0은 result 0의 약자로 결과를 참조하는 자동 생성 이름입니다.
연산과 즉시 변수를 저장하고 이를 연산할 수도 있습니다.
vals, vars
Scala는 vals 와 vars라는 두 종류의 변수를 가지고 있습니다.
val
- val은 변경할 수 없습니다. 즉 초기화 되면 val을 재 할당할 수 없습니다.
scala> val a: Int =4
val a: Int = 4
scala> a
val res3: Int = 4
scala> a = 2
^
error: reassignment to val
- Double, Float, Long, Int, Short, Byte, Char, Boolean, Unit 8개의 변수 타입이 있습니다.
- 각각의 타입 명칭은, scala.Double처럼 앞에 scala. 가 붙지만, 자동으로 소스파일을 가져오기 때문에 생략 가능
vars
- vars는 변경 가능하므로 재할당 할 수 있습니다.
scala> var a: Int = 1
var a: Int = 1
scala> a
val res0: Int = 1
scala> a = 2
// mutated a
scala> a
val res1: Int = 2
Immutable의 Pros
- 프로그램의 논리의 오류로 인해 데이터가 변경되지 않는다.
- 코드를 추론하지 않기 때문에 추론하기가 더 쉽다. ( 변경되는 내용이 없으니까 초기에 값만 참조하면 됨)
- 적은 수의 유닛 테스트를 작성한다.
Immutable의 Cons
- 객체 복사로 인해 메모리가 추가 생성된다. ( 재할당 할 수 없으므로, 변경하려면 새 개체를 만들어야 합니다.)
유형추론
- 타입을 설정하지 않고 Scala가 추론하게 하는 방식
scala> val a = 3
val a: Int = 3
scala> val b = "hello world"
val b: String = hello world
기존에 사용했던 :type 삭제하면 사용가능
Semicolons(;)
세미클론의 경우도 생략 가능합니다.
Scripts
- 스크립트는 순차적으로 실행되는 파일의 명령 시퀀스입니다.
- 스크립트는 작은 작업에 적합합니다. ( 단일 파일에 들어갈 정도 )
- Scala 인터프리터 처럼, 컴파일 단계를 숨겨 코드가 즉시 실행되는 것처럼 보입니다.
Applications
- Scala application은 명시적으로 컴파일 한 다음 실행해야 합니다.
- 응용 프로그램은 개별적으로 컴파일된 많은 소스 파일로 구성됩니다.
- 더 복잡한 프로젝트에 적합합니다.
- 스크립트에 비해 컴파일 이후 지연시간이 없다는 장점이 있습니다. ( 응용 프로그램은 미리 컴파일 될 수 있지만, 스크립트는 매번 컴파일되고 실행 )
object Game extents App {
println("Let's play Twenty-One!")
}
- 컴파일
scalac Game.scala
scalac이라는 이름의 scala 컴파일러는 작성한 코드를 Java 바이트코드로 변환합니다.
- 실행
scala Game
미리 컴파일된 코드가 Java 가상 머신에서 실행됩니다.
컴파일된 언어 장점
- 코드를 즉석에서 해석할 필요가 없기 때문에 빠르다
컴파일된 언어 단점
- 코드를 컴파일하는데 시간이 걸리며, 대규모 스칼라 프로그램의 경우 몇 분이 걸립니다.
이러한 로드 시간의 밀리초는 때로는 수백만 달러의 추가 수익을 의미하기 때문에 기업들이 Scala를 사용하는 이유입니다.
Scala workflows
- Command line 사용
- IDE 사용 ( 통합 개발 환경 )
IDE
- IDE는 대규모 시스템을 구축하거나 작업해야 할 때 적절합니다.
- IntelliJ IDEA 는 Scala 전문가를 위해 가장 일반적입니다.
- sbt 파일은 Scala앱을 빌드하는데 가장 널리 쓰이는 도구입니다. ( Simple build tool )
- sbt를 사용하면, Scala 애플리케이션을 컴파일, 실행 및 테스트 할 수 있습니다.
Functions
Scala는 함수형 프로그래밍 언어라는 것이 다른 언어들과 핵심 차별화 특성입니다.
함수는 무엇을 하는가?
- 함수는 결과를 생성하기 위해 호출되거나 인수 목록과 함께 호출됩니다.
- 함수 구조
- Parameter list
- Body
- Result type
scala> def bust(hand: Int) = {
hand > 21
}
def bust(hand: Int): Boolean
scala> bust(10)
val res1: Boolean = false
scala> bust(22)
val res2: Boolean = true
- Scala의 Function은 first-class values 입니다.
- 모든 함수는 결과를 생성하고 모든 결과에는 값이 있으며, 모든 값에는 유형이 있습니다.
Collections
변수와 마찬가로 변경 가능한 것과 변경 불가능한 것 두가지 종류가 있습니다.
- Mutable collections
- 제자리에서 업데이트하거나 확장할 수 있습니다. ( 생성된 컬렉션의 요소를 변경, 추가 또는 제거 가능 )
- Immutable collections
- 변경되지 않음 ( 제거 또는 업데이트하는 시뮬레이션 과정이 있지만, 이러한 작업은 새 컬렉션을 반환하고 이전 컬렉셩늘 변경하지 않은 상태로 둡니다. )
Array
- 배열은 모두 같은 유형을 공유하는 변경 가능한 개체 시퀀스입니다.
scala> val players = Array("Alex", "Chen", "Marta")
val players: Array[String] = Array(Alex, Chen, Marta)
- Parameterize an array
scala> val players = new Array[String](3)
val players: Array[String] = Array(null, null, null)
Type parameter 와 Value parameter를 지정해서 초기화 진행
- Initialize elements of an array
scala> players(0) = "Alex"
scala> players(1) = "Chen"
scala> players(2) = "Marta"
scala> players
val res7: Array[String] = Array(Alex, Chen, Marta)
Arrays are mutable
scala> val players = Array("Alex", "Chen", "Marta")
val players: Array[String] = Array(Alex, Chen, Marta)
scala> players(1) = "KB"
scala> players
val res9: Array[String] = Array(Alex, KB, Marta)
다음과 같이 val이여도 매개변수안에 값을 변경하는 것은 가능합니다.
scala> players = 1
^
error: reassignment to val
scala> players = Array("1","2","3")
^
error: reassignment to val
하지만 위와 같이 array자체를 바꾸는 것은 불가능 또한, 지정한 타입을 벗어나는 타입은 변경 불가능
권고사항 : Use val with Array
- Scala에서는 val을 통해 변경 가능한 컬렉션을 만드는 것이 좋습니다.
The Any supertype
scala> val mixedTypes = new Array[Any](3)
val mixedTypes: Array[Any] = Array(null, null, null)
scala> mixedTypes(0) = "I like turtles"
scala> mixedTypes(1) = 5000
scala> mixedTypes(2) = true
scala> mixedTypes
val res16: Array[Any] = Array(I like turtles, 5000, true)
Any타입을 이용하면 여러개의 타입이 하나의 Array에 포함될 수 있습니다.
Collections type
- 컬렉션에는 2가지의 타입이있다.
- Mutable - Array
- Immutable - List
List
scala> val players = Array("Alex", "Chen", "Marta")
val players: Array[String] = Array(Alex, Chen, Marta)
scala> val players = List("Alex", "Chen", "Marta")
val players: List[String] = List(Alex, Chen, Marta)
선언 방법은 Array와 동일하게 선언
How Lists are useful while immutable
- List는 Scala의 거의 모든것과 같은 객체입니다. ( 객체에 속하는 함수로 생각할 수 있는 메서드 )
- List 개체에서 메서드를 호출하면 새 값이 있는 목록이 반환됩니다.
- 여러개의 List 메서드가 있습니다.
- List.drop()
- List.mkString(“,”)
- List.length
- List.reverse
scala> val players = List("Alex", "Chen", "Marta")
val players: List[String] = List(Alex, Chen, Marta)
scala> val newPlayers = "Sindhu" :: players
val newPlayers: List[String] = List(Sindhu, Alex, Chen, Marta)
다음과 같이 새로운 값을 추가할 수 있습니다. 하지만 List는 변경할 수 없으므로, 새로운 val을 정의해야 합니다.
scala> var players = List("Alex", "Chen", "Marta")
var players: List[String] = List(Alex, Chen, Marta)
scala> players = "Sindhu" :: players
// mutated players
scala> players
val res0: List[String] = List(Sindhu, Alex, Chen, Marta)
var로 선언시, 새롭게 추가할 수 있습니다.
cons(::)
- cons 는 Scala의 List에 고유한 연산자입니다.
- 목록을 구성하는 cons 같은 것은 일반적으로 함수형 프로그래밍 언어에서 널리 사용됩니다.
- Append 연산은 Scala에 존재하기는 하지만, 효율적이지 않기 때문에 거의 사용되지 않습니다.
Nil
- Nil은 빈 목록을 지정하는 약식 방법입니다.
scala> Nil
val res1: collection.immutable.Nil.type = List()
출력에서 괄호 안에 요소가 없는 비어 있음을 나타냅니다.
- 새 목록을 초기화하는 일반적인 방법은 요소를 지정하는 것입니다.
scala> val players = "Alex" :: "Chen" :: "Marta" :: Nil
val players: List[String] = List(Alex, Chen, Marta)
cons연산자는 실제로 List 객체에 속하는 메서드이기 때문에 Nil이 필요합니다.
Concatenating Lists
:::
연산자를 통해 연결할 수 있습니다.
scala> val playersA = List("Sindhu", "Alex")
val playersA: List[String] = List(Sindhu, Alex)
scala> val playersB = List("Chen", "Marta")
val playersB: List[String] = List(Chen, Marta)
scala> val allplayers = playersA ::: playersB
val allplayers: List[String] = List(Sindhu, Alex, Chen, Marta)
Scala nudges us towards immutability
- Scala는 불변성을 선호하는 프로그래밍을 좋아하기 때문에 배열을 사용하는 것보다 리스트를 사용하는 것을 더 선호합니다.
Type Systems, Control Structures, Style
- Scala의 정적 유형은 복잡한 어플리케이션의 버그를 방지하는데 도움이 됩니다.
Type Systems
- 언어는 컴파일 타임에 변수의 유형을 알고 있는 경우 정적으로 유형이 지정됩니다. ( C/C++, Fortran, Java, Scala 등 )
- 유형이 즉석에서 검사되는 언어는 동적 유형이 지정됩니다. ( JavaScript, Python, Ruby, R )
정적 타입의 Pros
- 정적으로 유형이 지정된 언어는 확인할 필요가 없기 때문에 더 빠르게 실행됩니다.
- 프로그램의 검증 가능한 속성입니다.
- 정적 유형 시스템을 사용하면 높은 수준의 확신을 갖고 코드베이스를 변경할 수 있습니다. ( 큰 코드베이스에서 작업하는 엔지니어는 이 안전망을 좋아합니다. )
- 유형 주석은 변수, 컬렉션, 함수가 수행할 것으로 예상되는 작업을 알려줍니다.
정적 타입의 Cons
- 유형을 확인하는데 시간이 걸리고 코드가 더 장황해진다.
- 쓰기가 짜증나고, 언어가 유연하지 않아 코더가 원하는대로 표현하지 못한다.
다행히도, Scala에는 장황함과 융통성 없는 단점을 해결하는 Advanced 정적 유형 시스템이 있습니다.
Promoting flexibility
- 패턴 매칭
- 다양한 새로운 글쓰기 및 작성 방식
Compiled, statically-typed languages
- Scala는 컴파일 되고 정적으로 유형화되어 성능 향상 이점이 두 배가 됩니다.
- 사용되는 정확한 데이터 유형을 알고 있기 때문에 코드를 더 빠르고 효율적으로 만들고 그에 따라 메모리를 할당합니다.
if/else
scala> val hand = 24
val hand: Int = 24
scala> if (hand > 21) {
println("This hand busts!")
}
This hand busts!
소괄호에 조건을 설정하고, { }에 조건이 충족될 시 실행될 코드를 입력 ( 내용이 단 한줄인 경우에도 중괄호를 사용하는 것을 Scala에서 권장 )
if-else control flow
scala> def maxHand ( handA: Int, handB: Int ): Int = {
| if (handA > handB) handA
| else handB
| }
def maxHand(handA: Int, handB: Int): Int
maxHand 함수의 간단한 버전은 최대값을 결정합니다.
scala> val handA = 17
val handA: Int = 17
scala> val handB = 19
val handB: Int = 19
scala> if (handA > handB) {
| println(handA)
| }
| else {
| println(handB)
| }
19
if/else 문 뒤에 한줄의 간단한 코드인 경우 중괄호를 생략할 수 있습니다. ( 하지만 괄호 사용을 권장 )
scala> val handA = 17
val handA: Int = 17
scala> val handB = 19
val handB: Int = 19
scala> if (handA > handB) println(handA) else println(handB)
19
위와 같이 이어서 사용할 수 도 있습니다.
val handA = 26
val handB = 20
if (handA > handB) println(0)
else if (handA == handB) println(1)
else println(2)
위와 같이 else if 또한 사용 가능합니다.
val handA = 26
val handB = 20
val maxHand = if (handA > handB) handA else handB
다음과 같이 if else 문을 활용하여 해당 값을 변수에 저장할 수도 있습니다.
다른 언어들과 유사하게 연산자 사용이 가능합니다.
Loop with while
scala> var i = 0
var i: Int = 0
scala> val numRepetitions = 3
val numRepetitions: Int = 3
scala> while (i < numRepetitions)
| { println(i)
| i += 1
| }
0
1
2
위와 같이 반복문을 할 수 있습니다.
i += 1 처럼 파이썬 형식과 유사함을 알 수 있습니다. ( i++ 같은 형식 미지원 )
scala> var hands = Array(17, 24, 21)
var hands: Array[Int] = Array(17, 24, 21)
scala> hands.length
val res0: Int = 3
scala> while(i<hands.length){
| println(hands(i))
| i = i + 1
| }
17
24
21
위와 같이 배열을 이용한 반복문을 주로 사용합니다.
Scala 정리
- Scala 는 Functional 하다
- Scala는 imperative / functional hybrid 하다 ( 명령형 / 기능적 언어 )
- Scala는 강요하지 않지만, functional style로 유도한다
The imperative style
- 한번에 하나의 명령을 의미한다
- 루프로 반복한다.
- 공유 상태를 변경한다.
The functional style
- Function 들은, first-class values이다.
Scala fuses OOP and FP -> Scala is scalable
var hands = Array(17,24,21)
scala> def bust(hand: Int) = {
| println(hand > 21)
| }
scala> hands.foreach(bust)
false
true
false
위와 같이 foreach를 사용하면, while 루프와 동일한 출력을 얻습니다.
스칼라의 기능적 수단
- 로컬 범위 외부에서 일반 변수를 수정하는 코드를 의미하는 side effect를 피해야 합니다.
scala> val myHand = bust(22)
true
val myHand: Unit = ()
출력 스트림으로 인쇄하는 것은 side effect를 가져옵니다.
Imperative
- Side effects
- Type Unit
- var
Functional
- No side effects
- Non - Unit value types
- val
Functional style
- 입력 값을 출력 값에 매핑하는 프로그램 작업을 선호
- 누출이 없는 파이프
- 함수에 대한 입력으로 전달된 변수를 변경하지 않기 때문에 변수를 변경하지 않습니다.
- 함수가 덜 얽혀 있기 때뭉네 코드를 추론하기 쉽습니다.
- Side effect를 테스트할 필요가 없습니다.
- 생성한 함수는 더 안정적이고 재사용이 가능합니다.
Prefer
- val
- Immutable objects
- Functions without side effects
Scala 설계자들은 위와 같은 내용을 선호합니다.
하지만 필요에 따라 Imperative를 사용하는 것도 좋습니다.
- var
- Mutable objects
- Functions with side effects