Language/Scala 🛢️

[Scala] 1. 스칼라 소개

SLYK1D 2024. 12. 1. 16:37
728x90
반응형

0. 시작하면서 

개인적으로 업무를 하면서, Apache Spark를 많이 사용하는 입장에서 Scala 언어에 대해서는 꼭 필요한 내용이 될 것이라 생각됩니다. 물론 Java 나 Python 으로도 Spark를 실행할 수 있지만, 기본적으로 Scala 를 기반으로 하였고, Java 와 같이 JVM을 활용하기 때문에 기존에 자바를 공부하시던 분들에게도 조금 더 익숙하게 다가오지 않을까 합니다. 

이번 주제인 Scala 와 관련해서는 아래 책 2권을 참고하여 포스트를 작성할 예정이며, 좀 더 궁금하신 분들은 책에 대한 링크를 남겨 둘테니 참고하시면 좋겠습니다. 

[프로그래밍 인 스칼라 4판]
https://product.kyobobook.co.kr/detail/S000001804990

 

Programming in Scala | 마틴 오더스키 - 교보문고

Programming in Scala | 스칼라는 여러 함수 언어적 기법과 객체지향 기법이 어우러져 루비나 파이썬 같은 동적 언어 못지않게 간결하면서 풍부한 표현력을 가진 언어다. 스칼라는 빅데이터나 머신러

product.kyobobook.co.kr

[프로그래밍 스칼라]
https://product.kyobobook.co.kr/detail/S000001057693

 

프로그래밍 스칼라 | 딘 왐플러 - 교보문고

프로그래밍 스칼라 | 『프로그래밍 스칼라』는 다양한 코드 예제가 포함된 실전 바이블이다. 초보자와 고급 사용자를 한데 아우를 뿐 아니라, 실제 개발자들의 실용적 관심사에 초점을 맞추어

product.kyobobook.co.kr

그러면 본격적으로 스칼라에 대한 이야기를 시작해보겠습니다.

 

1. 스칼라를 사용하는 이유

1.1 우리가 현재까지 사용하는 프로그래밍 언어는?

IT 분야에 종사한 분들을 포함해 최소한 프로그래밍을 접해본 분들이라면, 프로그램이나 웹페이지 등을 만드는 작업에 있어, 다양한 언어를 사용한다는 것을 알 수 있을 겁니다. 이 글을 작성하는 시점 기준으로 현재까지 등장한 프로그래밍 언어의 종류에 대해서는 아래 그림을 통해서만 봐도 꽤 다양하다는 것을 알 수 있습니다.

또한 현 시점에서 가장 인기있는 언어라고 하면, 아래와 같은 순위표도 볼 수 있습니다.

출처: https://www.tiobe.com/tiobe-index/

아마 이 글을 처음 접하시는 분들도 대게는 파이썬, 자바, 자바스크립트 등 위에서 많이 사용하는 언어의 유저분들이 많으실 거라 예상됩니다. 

1.2 왜 스칼라를 주제로 글을 쓰나요?

이 쯤 되면, 위에서 언급되지도 않는 언어인 Scala 를 굳이 왜 배우는가 에 대한 의문이 드실 겁니다. 물론 저 역시도, 일을 할 때는 Java 나 Python, R 등의 프로그래밍 언어를 사용하고, 데이터 처리는 SQL 을 위주로 사용합니다. 그럼에도 불구하고, 제가 Scala 에 대한 글을 쓰는 이유는 Apache Spark 를 사용하기 때문에 라는 이유가 가장 클 것 같습니다. 

빅데이터를 처리해본 경험이 있다면, Apache Spark 를 사용해 본 경험이 있을 겁니다. 물론 Spark 도 JVM을 사용하기 때문에 Java 나 파이썬, R 등의 언어를 지원하기도 하지만, 기본적으로 스파크의 엔진이 스칼라 언어로 구현되어있어, 필요에 따라서는 로우 레벨(Low-Level) 의 API를 사용해야할 때, 스칼라로 구현을 해야되는 필요가 있기 때문입니다. 

또한 뒤에서 설명할 특징에서 이야기할 예정이지만, 간단하게 설명하자면, Java 에서도 함수형 프로그래밍이 있고, 이를 발전시킨 언어가 Scala 이지 않을까 라는 생각이 들만큼, 프로그래밍 방식에 있어 유사한 측면이 많다고 생각합니다. 비주류의 언어이긴해도 함수형 프로그래밍을 포함해 여러가지 사용법을 알아두면 자바를 사용하는 것보다 강점이 있다는 것도 이유가 될 수 있을 것 같습니다. 

 

2. 그래서 스칼라가 어떤 언어인데요?

여기까지 읽으셨다면 본격적으로 스칼라에 대해 궁금증과 흥미가 있으실 것 같습니다. 그렇다면 스칼라 언어가 어떤 특징을 가지고 있는지 한 번 알아보도록 하죠.

스칼라(Scala) 언어는 2004년, 마틴 오더스키 박사님에 의해 처음 개발되었습니다. 공식 문서를 통해서도 확인해볼 수 있지만, 스칼라 언어에 대한 소개로는 "일반적인 프로그래밍 패턴을 간결하고 우아하며, 유연한 문법을 사용하는 정적 타입의 다중 패러다임 JVM 언어"라고 합니다. 작게는 인터프리터 방식의 스크립트부터 대규모의 복잡한 애플리케이션에 이르기까지 폭 넓은 규모 확장성을 제공하는 언어라고 할 만큼 확장성의 측면에서는 강점을 가집니다. 그 외에도 몇 가지 특징을 갖고 있는데, 아래에서 좀 더 설명하도록 하겠습니다. 

2.1 스칼라의 특징

2.1.1 JVM과 자바스크립트 언어

스칼라는 JVM의 성능 및 최적화를 활용하며, 자바를 기반으로 구축된 각종 도구와 라이브러리 생태계를 사용할 수 있습니다. 더 나아가 Scala.js 의 경우 자바스크립트도 포팅 가능한 프로젝트가 있을 만큼 JVM에 한정된 것이 아닌, 다양한 생태계가 있다는 특징이 있습니다.

2.1.2 정적 타입

스칼라의 타입 시스템은 기본적으로 정적 타입 지정(Static Typing)을 채택했습니다. 기존의 자바에서 사용하는 타입 시스템의 단점을 수정하고, 타입 추론을 사용해 불필요한 타입 표기를 생략할 수 있도록 지원하는 특징이 있습니다. 

2.1.3 객체지향 프로그래밍 + 함수형 프로그래밍

자바를 기반으로 생성된 언어인 만큼 기존의 자바가 갖는 특징인 객체지향 프로그래밍(OOP, Object-Oriented Programming) 을 완벽하게 지원하며, 데이터 타입을 포함해 모든 것을 객체로 표현합니다. 더 나아가 혼합 합성을 사용해 타입을 깔금하게 구현하는 트레이트(traits)를 통해 자바 객체 모델을 보완하는 특징이 있습니다. Traits 에 대해서는 추후에 다시 설명하도록 하겠습니다.

뿐만 아니라, 함수형 프로그래밍도 지원하는데, 특히 빅데이터, 동시성, 코드 정확성을 로직화 하는 데 있어 최선의 도구라고 할 만큼 간결하고 강력하면서 정확한 코드를 작성하는 데 강점이 있습니다. 또한 이에 대한 결과로 가독성이 좋은 코드를 작성할 수 있다는 강점도 있습니다.

2.1.4 규모 확장성

끝으로, 이 글을 시작하면서 계속 언급한 특징으로, Scalable Language 라는 이름에서 비롯된 만큼, 작은 규모에서 대규모의 복잡한 애플리케이션을 프로그래밍 할 수 있는, 확장하기에 용이한 아키텍처를 가지고 있습니다. 

위의 큰 특징 4가지가 장점인 언어이지만, 반대로 함수형 프로그래밍 및 트레이트와 같은 새로운 개념들도 등장하기 때문에 진입장벽이 높고, 프로그래밍을 어떻게 하느냐에 따라 기존 자바를 사용했을 때보다 컴파일 시간이 오래 걸릴 수 있으며, 비주류 언어인 만큼 커뮤니티가 작고, 활용 자료도 적은 언어라는 점이 단점일 수 있습니다.

 

2.2 스칼라 찍먹하기

위와 같은 장,단점이 있음에도 왜 배우는 지, 얼마나 좋을 지를 알아보려면 직접 사용해보는 것만이 방법일 겁니다. 이번에는 몇 가지 예시를 통해 스칼라의 사용법과 특징 중 하나인 동시성을 활용하면 얼마나 유용한지를 알아보도록 하겠습니다. 

아래 예시를 실행하기에 앞서 스칼라 언어에 대한 설치를 하셔야하며, 아래 공식문서의 링크를 남겨두었으니, 설치방법을 참고해서 진행해주시면 됩니다. 
https://docs.scala-lang.org/getting-started/install-scala.html

 

Getting Started

To start experimenting with Scala right away, use "Scastie" in your browser.

docs.scala-lang.org

 

2.2.1 기본 사용법 살펴보기

스칼라를 설치하고 개발환경을 세팅했다면, SBT콘솔을 통해 간단하게 프롬프트 상에서 실행해보도록 하겠습니다. 

먼저, 프롬프트 상에서 "console" 을 입력하면, 인터프리터 방식의 스칼라 프롬프트로 전환이 됩니다. 

다음으로 간단하게 "Hello, Scala" 라는 문자열을 입력으로 하는 변수를 선언하고, 변수의 내용을 출력하도록 해보겠습니다. 

val message = "Hello, Scala!"

println(message)


[실행결과]
>....val message: String = Hello, Scala!
>....Hello, Scala!

스칼라에서는 변경 가능한 데이터를 사용하는 것이 일반적인 버그의 원인으로 보는 만큼, val 키워드를 사용한 불멸 변수를 사용하기를 권장합니다. 또한 위의 message 라는 변수를 생성하고 나면, String 이라는 변수의 타입이 출력되는 것을 볼 수 있는데, 이는 입력한 "Hello, Scala!" 라는 리터럴 값으로부터 message 변수의 타입이 java.lang.String 임을 추론한 것입니다. 

또한 타입의 정보를 보여주거나 선언에 명시적으로 타입 정보를 추가하려는 경우에는 대상 이름 뒤에 콜론을 붙이고 타입을 표기하면 됩니다. 이 처럼 데이터 타입이 꼭 필요한 순간이 아니라면, 컴파일러의 구문분석 시 모호함을 제거하기 위해 별도의 타입 선언을 하지 않습니다.

이번에는 클래스를 선언하고 객체를 생성해,  해당 객체의 메소드를 실행하는 간단한 객체지향 프로그래밍을 살펴보도록 하겠습니다. 

[Scala Code - Upper.sc]

package Basic.AboutScala

class Upper {
  def upper(strings: String*): Seq[String] = {
    strings.map((s: String) => s.toUpperCase())
  }
}

val up = new Upper
println(up.upper("Hello", "World!"))

위의 스크립트를 실행할 때는 아래와 같이 :load 명령을 사용해 파일을 로드하면 됩니다. 

scala> :load [스크립트 경로]/Upper.sc


[실행결과]
Loading src/main/scala/Basic/AboutScala/Upper.sc...
class Upper
val up: Upper = Upper@2885bb8e
ArraySeq(HELLO, WORLD!)
>....

그럼 코드의 내용을 한 번 살펴보록 하죠. 위에서 실행한 Upper 클래스의 upper() 메소드는 하나 이상의 입력 문자열을 대문자로 변환 후 Seq() 라는 객체로 변환합니다. 자바에서와 마찬가지로 클래스는 .class 키워드로 시작하고, 전체 클래스 본문은 중괄호 사이에 위치해야합니다. 

다음으로 메소드 선언 방법을 살펴봅시다. 자바에서와 달리 스칼라에서의 메소드 선언은 "def upper(strings: String*): Seq[String] = {...}" 처럼 메소드를 선언할 때 "def [메소드명]" 의 형식으로 되어야하며, 매개변수 목록을 작성하는 것은 선택적으로 사용됩니다. 그 다음 반환 타입에 대해 명시할 때는 ": 반환타입" 을 붙여주고, 본문을 표기하기 전에 등호(=)를 사용해 중괄호 이하의 내용을 실행합니다 라는 의미로 붙여줍니다. 만약 매개변수 작성 시, String 과 같은 가변 길이 매개변수가 오는 경우에는 "String*" 처럼 가변 길이 인자 목록이라는 것을 표시하면 됩니다.

끝으로 본문에 대한 내용인데, 본문 내용을 작성하기 전에 등호가 사용된 이유로는 첫 번째 모호성을 줄이기 위해서이며, 대부분의 경우 스칼라는 메소드의 반환 타입을 추론할 수 있습니다. 다른 이유로는 등호를 사용하는 것이 함수형 프로그래밍의 원칙을 강조하기 위함이기도 합니다. 추후에 보겠지만, 일반적인 객체와 마찬가지로 함수를 다른 함수의 인자로 넘길 수도 있고, 함수가 함수를 반환하거나 변수에 대입하는 등의 작업이 가능하기 때문입니다.

2.2.2 동시성 살펴보기

다음으로는 스칼라가 많이 사용되는 사례 중 하나였던 동시성에 대한 프로그래밍 시 어떤 강점을 갖는지 살펴보겠습니다. 스칼라에서는 동시성 애플리케이션을 액터(actor) 라는 직관적 모델을 사용해 표현할 수 있습니다. 물론 아래의 예제와 관련해 동시성에 대해서는 추후 다른 장에서 구체적으로 다룰 예정이며, 이번 절이 어디까지나 "찍먹" 하는 것이 목적이기 때문에 간단하게만 살펴보겠습니다. 

예제를 작성하기에 앞서 액터에 대해서 간략하게 설명하자면, 독립적인 소프트웨어라고 이해를 하면 좋습니다. 액터끼리는 메세지 교환을 통해 소통을 하는데, 액터 간에 자신의 상태에 대해서는 공유하지 않습니다. 때문에 비동기적으로 동작하고, 베타적으로 자신의 상태에만 접근할 수 있으며, 액터 프레임워크가 스레드를 통해 안전하게 실행해주기 때문에, 오류가 발생할 확률이 낮습니다. 

위의 설명에 이해를 돕기 위해 기하학적 도형을 화면에 표시하는 프로그램을 만든다고 가정해봅시다. 먼저 도형에 대한 클래스를 선언하기 위해 Shape 이라는 이름의 클래스 계층을 생성하고, 그려주는 메소드인 draw () 메소드를 추가하도록 하겠습니다. 

[Scala Code - Shape.scala]

package Basic.shape

case class Point(x: Double = 0.0, y: Double = 0.0)

abstract class Shape() {
  // 각 Shape은 자신을 문자열로 바꾼 결과를 함수에 전달한다.
  def draw(f: String => Unit): Unit = f(s"draw: ${this.toString}")
}

case class Circle(center: Point, radius: Double) extends Shape
case class Rectangle(lowerLeft: Point, height: Double, width: Double) extends Shape
case class Triangle(point1: Point, point2: Point, point3: Point) extends Shape

위의 코드에서 먼저 선언한 Point 클래스는 생성자로, 인자는 클래스 이름과 본문 사이에 나열해야 합니다. 또한 클래스 명 앞에 case 키워드를 넣으면, 각 생성자 매개변수가 자동으로 Point 인스턴스의 읽기전용 필드로 변환됩니다. 이 때 해당 클래스를 가리켜, 케이스 클래스라고 하는데, 케이스 클래스에서의 인자 값은 변경이 불가하기 때문에 "point.y = 3.0" 과 같은 식으로 사용 시, 컴파일 오류가 발생합니다. 
위의 예시인 경우 x, y 좌표 모두 0.0으로 설정했기 때문에 별도의 값이 없다면 0.0 으로 초기화 한다고 볼 수 있습니다. 추가적으로 선언한 인자의 개수가 설정한 매개변수 수보다 적을 경우, 왼쪽에서 오른쪽으로 인자의 순서를 결정합니다. 이를 위해 아래 스칼라 스크립트를 실행하고 결과를 확인해보도록 합니다. 

[Scala Code - ShapeTest.sc]

import Basic.shape.Point

val p00 = new Point
val p20 = new Point(2.0)
val p20b = new Point(2.0)
val p02 = new Point(y = 2.0)

p00 == p20
p20 == p20b


[실행 결과]
>....import Basic.shape.Point
>....val p00: Basic.shape.Point = Point(0.0,0.0)
>....val p20: Basic.shape.Point = Point(2.0,0.0)
>....val p20b: Basic.shape.Point = Point(2.0,0.0)
>....val p02: Basic.shape.Point = Point(0.0,2.0)
>....val res0: Boolean = false
>....val res1: Boolean = true

위의 실행결과를 보면 알 수 있듯이, 인자를 지정하지 않을 경우, 기본값인 0.0 으로 설정됩니다. 인자를 하나만 지정하는 경우에는 Point 클래스의 매개변수 중 가장 왼쪽에 있는 매개변수 x의 인자로 설정되며, 나머지 값은 기본값으로 설정됩니다. 끝으로 p02의 경우 매개변수와 그에 대한 인자를 설정하였기에, 매개변수 y에 대한 인자로 2.0 이 설정되었고, 명시하지 않은 x에 대해서는 기본값이 설정됨을 알 수 있습니다. 

추가적으로 case 키워드를 사용하는 경우에는 컴파일러가 자동으로 메소드를 생성해 주는데, 대표적으로는 toString, equals, hashCode 등이 있습니다. 위의 스크립트에서 "p00 == p20" 이나 "p20 == p20b" 에 대한 연산에서 equals 메소드를 생성하지 않았지만, 두 객체가 동일한지를 비교하고 있다는 것이 그 이유입니다. 
이는 참조타입 만을 비교하는 자바와 다른 점이며, 자바에서는 논리적 비교를 위해 equals() 메소드를 명시해야만 가능한 반면, 스칼라에서는 단순히 == 비교 연산자만 사용함으로써 객체에 대한 논리적 비교까지 같이 수행된다는 점에서 차이가 있습니다. 

끝으로, 컴파일러가 모든 케이스 클래스에 대해 각 클래스와 이름이 같은 동반 객체를 생성한다는 특징이 있습니다. 앞서 컴파일러가 자동으로 생성하는 메소드들 중 하나이며, 동반 클래스의 생성자와 같은 인자를 받습니다. 때문에 아래 예시는 동일한 코드라고 볼 수 있죠.

val p1 = Point.apply(1.0, 2.0)
val p2 = Point(1.0, 2.0)

필요한 모형을 선언했으니, 다음으로는 액터를 정의해보도록 하죠. 이를 위해 아카 라이브러리를 설치해야 하며, build.sbt 파일에 아래와 같이 추가해주면 됩니다. 

[build.sbt]
...

ThisBuild / scalaVersion := "2.13.10"

...

libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.13"

우리가 사용할 액터는 ShapeDrawingActor 라는 클래스로 생성하고, 구체적인 코드는 다음과 같습니다.

[Scala Code - ShapeDrawingActor.scala]

package Basic.shape

import akka.actor.Actor

object Messages {
    object Exit
    object Finished
    case class Response(message: String)
}

class ShapesDrawingActor extends Actor{
    import Messages._

    def receive = {
        case s: Shape =>
            s.draw(str => println(s"ShapesDrawingActor: $str"))
            sender ! Response(s"ShapesDrawingActor: $s drawn")  // sender.!(Response(s"ShapesDrawingActor: $s drawn")) 와 동일함
        case Exit =>
            println(s"ShapesDrawingActor: exiting...")
            sender ! Finished
        case unexpected =>
            val response = Response(s"ERROR: Unknown message: $unexpected")
            println(s"ShapesDrawingActor: $response")
            sender ! response
    }
}

위의 코드에서 살펴볼 내용은 크게 3가지가 있는데, 첫 번째로 Actor를 정의하기 위해서는 반드시 추상 기반클래스의 서브클래스로 생성해야합니다. 상속받은 Actor 클래스가 대표적인 추상 클래스이며, 여기에는 반드시 구현해야하는 추상 메소드인 receive() 메소드를 포함하고 있습니다. receive() 메소드의 역할은 액터로 들어오는 메세지를 처리하는 방식을 정의할 수 있습니다. 

위에서 사용한 아카를 비롯해 대부분의 액터 시스템에는 액터가 메세지를 처리할 때까지 메세지를 보관하는 일종의 우편함이 액터별로 있으며, 메세지가 도착한 순서대로 처리되도록 보장합니다. 

다음으로 receive() 메소드의 내용을 보면 case 문이 정의되고, 각 케이스 마다 실행되는 코드가 다르다는 것을 알 수 있습니다. 위의 receive() 메소드의 내용과 같은 구조를 Partial Function 이라는 리터럴 구문인데 인자 값으로 Any, 반환 타입으로는 Unit 으로 설정되어 있습니다. Any 타입은 스칼라 데이터 타입 중 최상위 데이터 타입으로 모든 데이터 타입의 값을 인자로 받을 수 있으며, Unit 은 void 와 유사하게 동작한다고 이해하면 됩니다. 앞서 액터는 비동기로 동작한다고 설명했으므로, 위의 case 문에 정의한 메세지 이외는 반환할 값이 없이 완료처리될 것입니다. 

그리고 위에 정의된 case 문은 메세지의 패턴 매칭을 수행하는 것으로 함수 인자가 안보이는 이유는 컴파일러가 만들어내는 코드가 내부적으로 처리하기 때문에 표시하지 않은 것 입니다. 만약 패턴 중 하나가 일치하는 경우, 화살표와 다음 case 문 사이에 정의된 코드를 실행합니다. 만약 실행한 코드가 짧다면, 중괄호를 사용하지 않고, 같은 줄에 표시하는 것도 가능합니다. 

끝으로 sender 다음에 등장하는 ! 는 현재 처리중인 메세지를 보낸 액터에 대한 참조를 반환하는 비동기 메세지를 보내는 메소드라고 이해하면 됩니다. 

이제 준비는 다 되었으니, Actor를 실행하는 메인 프로그램을 작성해보겠습니다. 코드는 다음과 같습니다.

[Scala Code - ShapesDrawingDriver.scala]

package Basic.shape

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.ConfigFactory

// 파일 내 사용하는 메세지
private object Start

object ShapeDrawingDriver {
    def main(args: Array[String]): Unit = {
        val system = ActorSystem("DrawingActorSystem", ConfigFactory.load())
        val drawer = system.actorOf(
          Props(new ShapesDrawingActor), "drawingActor"
        )

        val driver = system.actorOf(
          Props(new ShapesDrawingDriver(drawer)), "drawingService"
        )

        driver ! Start
    }
}

class ShapesDrawingDriver(drawerActor: ActorRef) extends Actor {
    import Messages._

    def receive = {
        case Start =>
            drawerActor ! Circle(Point(0.0, 0.0), 1.0)                                  // 모양 생성
            drawerActor ! Rectangle(Point(0.0, 0.0), 2, 5)                              // 모양 생성
            drawerActor ! 3.14159                                                       // 오류 처리
            drawerActor ! Triangle(Point(0.0, 0.0), Point(2.0, 0.0), Point(1.0, 2.0))   // 모양 생성
            drawerActor ! Exit                                                          // 종료 (context를 통해 접근해 엑터 시스템을 종료)

        case Finished =>
            println(s"ShapeDrawingDriver: cleaning up...")
            context.system.stop(drawerActor)

        case response: Response =>
            println("ShapeDrawingDriver: Response = " + response)

        case unexpected =>
            println("ShapesDrawingDriver: ERROR: Received an unexpected message = " + unexpected)
    }


}


[실행결과]
ShapesDrawingActor: draw: Circle(Point(0.0,0.0),1.0)
ShapesDrawingActor: draw: Rectangle(Point(0.0,0.0),2.0,5.0)
ShapesDrawingActor: Response(ERROR: Unknown message: 3.14159)
ShapesDrawingActor: draw: Triangle(Point(0.0,0.0),Point(2.0,0.0),Point(1.0,2.0))
ShapesDrawingActor: exiting...
ShapeDrawingDriver: Response = Response(ShapesDrawingActor: Circle(Point(0.0,0.0),1.0) drawn)
ShapeDrawingDriver: Response = Response(ShapesDrawingActor: Rectangle(Point(0.0,0.0),2.0,5.0) drawn)
ShapeDrawingDriver: Response = Response(ERROR: Unknown message: 3.14159)
ShapeDrawingDriver: Response = Response(ShapesDrawingActor: Triangle(Point(0.0,0.0),Point(2.0,0.0),Point(1.0,2.0)) drawn)
ShapeDrawingDriver: cleaning up...

위의 실행결과를 보면 알 수 있듯이, 메세지는 비동기로 처리되고 있으며, 이를 위해, 액터 드라이버에 사용할 액터 인스턴스를 전달하는데, 이 때 객체를 넘기는 것이 아닌 ActorRef 라는 참조 값을 넘겨준다고 볼 수 있습니다. 좀 더 자세한 내용은 추후에 아카를 설정하는 부분에서 다룰 예정입니다.

여기까지해서 스칼라 언어가 어떻게 생겨났고, 어떤 특징을 갖는지를 예시와 함께 간단하게 살펴보았습니다. 비주류 언어이고, 진입 장벽이 높다는 단점이 존재하지만, 자바의 확장성을 갖고 있고, 스파크를 사용하는 유저라면, 꼭 한 번은 배워두면 좋은 언어라고 생각합니다. 

다음 장에서는 변수와 함수를 선언하는 방식부터 파일과 패키지를 구성하는 방법 등 기본적인 문법과 관례에 대해서 자세히 다루도록 하겠습니다.

728x90
반응형