DataCamp Scala

Scala

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!")
}
  1. 컴파일
scalac Game.scala

scalac이라는 이름의 scala 컴파일러는 작성한 코드를 Java 바이트코드로 변환합니다.

  1. 실행
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