object HelloWorld extends App { println("Hello, World!") }
목차
1 개요
파일:Scala-logo.png
다중패러다임 프로그래밍 언어로, 객체지향 언어의 특성과 함수형 언어의 특성을 함께 가진다. JVM 상에서 구동되고 Java와 상호 호환이 가능하다. EPFL(École Polytechnique Fédérale de Lausanne, 스위스 로잔 연방 공과대학교)의 마틴 오더스키(Martin Odersky)가 개발했다. 트위터, 링크드인 등의 기업에서 활발하게 사용 중이지만, 메이저 언어의 자리는 아니다. 마이너 중에서 메이저인 정도.
Scala라는 명칭은 이탈리아어로 계단을 뜻하기도 하지만[1], Scalable Language에서 따온 것이기도 하다. 후자의 이유로 scala는 스칼라가 아니라 스카라(skah-lah)라고 읽어야 한다고 마틴 오더스키가 공인했지만, 국내는 물론이고 국외에서도 다들 스칼라라고 읽는다(...).
여담으로, Pascal 언어를 만든 Niklaus Wirth 교수는 ETH(Eidgenössische Technische Hochschule) Zürich 에 재직할 당시 Martin Odersky를 지도했고, Odersky 는 이 연구실에서 박사학위를 받았다. 이후 Odersky가 EPFL에 교수로 재직하면서 함수형 언어와 함께 Java의 기능을 확장에 대한 연구 도중 탄생한 언어가 Scala 다. 제대로 프로그래밍 언어론을 연구한 사람이 만들어서 그런지 매우 강력하나, 그 만큼 제대로 공부하고 사용하는 것이 생각보다 쉽지 않다. 그래도 Java 와 문법이 완전히 다른 Clojure나, 비슷하면서 다른 방향으로 어려운 Haskell보다는 Java개발자에게 진입장벽이 상대적으로 낮다 카더라.
2 주요 특징
스칼라는 마틴 오더스키가 자바 제네릭 컴파일러를 개발하며 느꼈던 자바의 여러가지 단점들을 근본적으로 수정하고, 추후 프로그램 언어 연구를 위한 연구 플랫폼으로 함께 사용하기 위하여 디자인한 언어이다. 따라서 언뜻 보기에는 자바와 비슷해 보일지 모르나 여러가지 측면에서 더욱 발전된 형태를 가지고 있다.
2.1 쉬운 언어 확장과 DSL
언어 이름이 괜히 Scalable[2] Launguage인 게 아니다. 쉽게 언어를 확장하여 도메인 특화 언어(Domain Specific Launguage;DSL)을 만들 수 있다. 스칼라에서는 DSL 제작을 언어 차원에서 작정하고 밀어주기 때문에 DSL 제작이 적은 소스로도 용이하며 DSL과 오리지널 스칼라를 섞어 쓰기도 쉬워 DSL 사용도 어렵지 않다[3]. DSL 정의의 단적인 예로 BASIC 문법을 정의한 BAYSICK 이 있다. #
2.1.1 연산자
스칼라에서는 자바에선 불가능했던 식별자명[4]을 식별자에 붙일 수 있다. 예를 들어 +라는 메소드를 정의하면 first.+(second)같은 식이 유효해진다. 여기에 더해 스칼라에선 a.f(b)를 a f b의 형태로 쓸 수 있기 때문에 first + second도 유효하다. 이런 식으로 자바에선 불가능했던 연산자 오버로딩을 할 수 있다. [5]
2.1.2 암묵
암묵적으로 어떤 객체를 다른 타입으로 변환할 수 있다. 이것을 이용해서 위임을 편하게 만들어 원래 타입에 존재하지 않는 메소드를 애초에 있던 것처럼 호출할 수 있다. 예를 들어 자바의 BigInteger를 사용하려 하는데 .add()등을 사용하지 않고 암묵 변환을 이용해서 보조 클래스를 만들면 BigInteger에 + 등의 연산자를 적용할 수도 있다. 또한 암묵적으로 메소드 인자에 쓰일 값을 받을 수 있다[6]. 별 생각 없이 쓰던 메소드의 정의를 살펴보면 듣도 보도 못한 인자 몇개가 붙어있는 것을 가끔 볼 수 있다.
2.1.3 이름으로 평가(call-by-name evaluation)
인자로 함수를 넘기다 못해 아예 코드 블록을 넘길 수 있다. 사실 구현 자체는 단순히 인자 없는 익명 함수에서 인자를 받는 부분을 생략하는 것 뿐이지만, 호출 측에서는 코드를 그대로 넘겨서 호출할 수 있다. 즉 아예 처음부터 언어에 있었던 것 같은 기능을 만드는 것도 가능하다.
2.2 트레이트(trait)
얼핏 보면 클래스는 아닌데 상속[7]을, 그것도 여러 번 할 수 있는 점에서 자바 인터페이스와 비슷해 보일 수도 있다. 실제로 자바 인터페이스를 스칼라에서는 트레이트로 인식하며, 그냥 트레이트를 자바 인터페이스처럼 사용해도 좋다. 하지만 트레이트는 자바 인터페이스와는 달리[8] 구체적인 구현을 담고 있으며[9], 믹스인이 가능하다. 예를 들어, Server extends Logger라는 클래스가 있다고 쳐보자. Server의 인스턴스를 만들 때, Server extends FileLogger라고 선언하면 파일로 로깅하며, Server extends NetworkLogger라고 하면 네트워크롤 통해 로그를 남긴다. 아니면 Server extends FileLogger with NetworkLogger라고 하면 파일과 네트워크에 모두 로그를 남길 수 있다(물론 상응하는 FileLogger와 NetworkLogger는 미리 작성해두어야 한다.). 즉 AOP를 언어 차원에서 지원한다. 또한 자바 인터페이스와 마찬가지로 구현하지 않은 메소드는 상속하는 쪽에서 구현해야 하므로 트레이트를 추상 클래스 급으로 만들어 놓고 믹스인으로 의존성을 주입할 수도 있다. 이런식으로 인터페이스처럼 보이지만 실은 믹스인할 수 있는 모듈이라고 보면 된다. 트레이트를 모듈로 쓰게 되면 with으로 트레이트를 쭉 쌓게 되는데, 여기서 착안해서 이런 방식을 케이크 패턴이라고 부른다.
2.3 짧은 소스
자바의 장황함이 많이 개선되었다. 하지만 길고 아름다운 import 목록은 어쩔 수가 없다.
2.3.1 생성자, 상속
클래스의 생성자, 슈퍼클래스 상속, 슈퍼클래스 생성자 호출이 클래스 선언과 융합(...)되었다. 보통 class 클래스명(인자1,인자2,...) extends 슈퍼클래스명(인자1, 인자2, ...){...}의 형태가 된다. 클래스명 옆의 인자 목록은 기본 클래스 생성자의 인자 목록이며, 슈퍼클래스명 옆의 인자 목록은 호출할 슈퍼클래스의 생성자에 해당하는 인자 목록이다. 이 외에 생성자를 오버로딩하고 싶다면 보조 생성자를 추가로 만들 수 있다. 자바와는 달리 보조 생성자의 이름은 무조건 this이다. 위 형태에서 기본 생성자의 본문이 어디있나 궁금할 수도 있는데, 클래스 본문 전체가 기본 생성자의 본문이 된다는 구성이다.
2.3.2 게터/세터 자동 생성
필드를 선언하면 그 필드에 따라[10] 내부적으로 게터/세터가 생성된다. "객체.필드"이나 "객체.필드=값"으로 게터와 세터를 호출할 수 있다. 자동 생성된 게터와 세터는 명시적으로 오버라이드 가능하다. 단, 내부적으로 생성되는 게터/세터는 자바 스타일[11]이 아니기 때문에 자바 쪽에서 쉽게 호출할 수 있게 하려면 @BeanProperty 어노테이션을 사용해야 한다.
2.3.3 싱글톤
object 키워드로 싱글톤 오브젝트를 만들 수 있다[12]. 또한 스칼라에는 자바의 static에 대응하는 키워드가 없고 클래스와 같은 이름의 오브젝트(컴패니언 오브젝트)를 만들 수 있다. 때문에 자바에서라면 한 클래스 안에 존재했을 정적 코드와 클래스 코드가 자연스럽게 분리된다. 싱글톤을 원클릭으로 만들 수 있는 것도 장점이지만 static을 일일이 붙이지 않아도 되는 것도 사소한 장점이다.
2.3.4 타입 추론
변수의 타입, 함수의 반환값의 타입 등을 컴파일러가 추론해준다. 별 거 아닌 것 같아 보여도 코드 양이 줄어드는 데 큰 공여를 한다. 자바에서 Person p=new Person();이라고 썼던 것을 생각해보자. 스칼라에서는 같은 타입 이야기를 두번 하지 않아도 된다. 함수 반환값 추론은 사실 의식적으로는 거의 쓰이지 않는다. 재귀에서는 추론이 불가능하며, 재귀가 아니더라도 클래스의 public 함수에서는 타입을 명시하는 게 관행이기 때문. 함수 반환값 추론이 빛을 발할 때는 익명 함수를 사용할 때이다.
2.3.5 apply
apply는 C++에서 () 연산자를 오버라이드하는 것을 떠올리면 된다. 예를 들어 배열을 임의 참조할 때 자바는 배열이 특수 객체로서 []을 사용할 수 있지만, 스칼라에서는 배열도 보통 객체 취급을 받으며 대신 apply가 구현되어 있어 ()로 임의 위치를 참조 가능하다. 또한 컴페니언 오브젝트에 apply를 구현해두면 생성자 대용으로도 쓸 수 있는데, 배열 등을 중첩할 때 new를 사용하지 않아도 되므로 편리하다. 심지어는 사실 함수 객체도 apply로 구현되어 있다(...).
2.3.6 그 외 편의 문법
익명 함수에 대한 편의 문법을 제공한다. 예를 들어 (a,b)=>a+b라는 익명 함수가 있다고 쳐 보자. 이것만으로도 익명 클래스에 메소드를 구현하는 것에 비해 충분히 짧지만, _+_라는 단 세글자로, 그것도 가독성이 매우 높게 축약하는 게 가능하다.
2.4 패턴 매칭
굳이 따지자면 자바의 switch~case문에 대응한다. 겉으로 보이는 차이점은 fall-through[13]가 없다는 점 정도지만, 가드와 unapply 메소드와 연계되어 switch문보다 복잡하게 실행된다. 사실상 unapply가 없으면 별 거 없는 그냥 시체가 된다.
2.4.1 unapply
unapply는 이름처럼 apply의 반대인데, apply처럼 괄호 안에 목록을 집어넣으면 그 안에 값을 집어넣는다. 즉 디스트럭처링(destructuring)을 손쉽게 하게 해 준다. 예를 들어, 조금 특수한 경우기는 하지만, ("namu","나무위키")라는 순서쌍이 t라는 이름으로 선언되어 있을 때, (key, val)=t라고 써 넣으면 key와 val에 각각 "namu"와 "나무위키"가 들어간다. 이 예제처럼 단독으로 쓰일 수도 있지만, 보통은 패턴 매칭에서 구조를 분해하는 데 사용한다.
2.5 함수형 프로그래밍 언어
스칼라는 함수형 프로그래밍을 매우 잘 지원한다. 다른 거의 대부분의 함수형 언어가 그렇듯이, 함수도 다른 값처럼 변수에 담기고, 인자로 넘어가고, 값으로 반환될 수 있다. 불변이 잘 지원되어 val로 불변 변수를 선언할 수 있고[14][15], 불변 콜렉션도 가변 콜렉션의 수와 별 차이가 없으며, 당연히 익명 함수도 지원하며, map, reduce등의 고차 함수와 for 컴프레헨션도 기본으로 있다. 무한 스트림도 있다.
2.6 자바와 상호 호환
스칼라에서 별다른 절차 없이 자바 코드를 100% 가져다 쓸 수 있다[16]. 원하는 자바 라이브러리가 있으면 sbt에서 불러오게 하면 된다. 자바에서 스칼라 코드를 그대로 쓸 순 없는데, 컴파일 결과물은 자바 바이트코드지만 스칼라에서 정의한 연산자, apply 등을 자바에서는 (당연히) 스칼라에서 쓰던 식으로 쓸 수 없기 때문이다. 명시적으로 바뀐 이름으로 호출할 수 있긴 하지만, 그렇게 하면 가독성이 안드로메다로 가므로 스칼라 쪽에서 자바 스타일로 인터페이스 계층을 깔자.
2.7 XML 지원
XML 리터럴을 지원한다. 즉 XML 문서를 만들어내고 싶으면 스칼라 소스 내에 XML을 하드코딩하는 행위가 가능하다. 또한 XML 리터럴 내에서 스칼라 코드를 집어넣어 동적으로 XML을 생성할 수 있다[17]. 또한 XPath 탐색을 지원해서 XML 파싱이 쉽다.
그러나 현재 가장 많이 사용되는 Semi-structured Document 를 위한 사실상의 표준은 JSON 이며, 이 때문에 왜 Scala 가 XML 을 문법 수준에서 지원하는지 의아할 수 있다. 이에 대해 한 발표회에서 밝힌 Scala 의 아버지 Martin Odersky 교수의 대답은 "Scala 를 처음 만들 당시에는 XML 이 잘 나갈 줄 알았다." 였다. 참고로 2000년대 초중반에는 이제 막 등장했던 XML 에 대한 관심과 열기가 지금과 달리 대단했으므로 Odersky 교수가 저렇게 판단한 것도 무리는 아니다.
3 기타
컴파일 언어[18]이며 인터프리터가 따로 있는 것도 아닌데도 REPL[19]을 지원한다. 원리가 뭔가 하면 입력->컴파일->실행->출력의 과정을 거친다. (사실 REPL의 원조인 LISP 인터프리터도 이렇게 동작한다) 스칼라는 그렇게 느린 편은 아니라 컴파일까지 해도 웬만큼 긴 소스가 아니라면(사실 그만큼 긴 소스를 REPL에 넣는 게 이상하지만) 체감되진 않지만, 이 때문에 웹이나 안드로이드 등으로 스칼라 REPL을 포팅하기는 매우 귀찮다어렵다. 라이벌(?) 언어인 Clojure는 태생이 스크립트 언어라 안드로이드 REPL까지 나오는데도 스칼라는 소식이 없다[20].
IDE는 이클립스(통합 개발 환경)[21]와 이클립스 기반으로 개발된 ScalaIDE가 있지만, Scala 지원을 공식적으로 해주는 Intellij의 사용도가 더 높다. 자바에서 장황함이 많이 개선되었다지만 import 목록은 버틸 수가 없으니 날코딩은 추천하지 않는다.
빌드 툴은 ant, maven, gradle이 삼분하고 있는 자바 빌드와는 달리, SBT(Simple Build Tool)이 사실상 표준이며, 나머지 자리는 maven과 gradle이 조금씩 차지하고 있다. 웬만한 스칼라 라이브러리 설치 가이드엔 대부분 sbt에서 설치하는 법이 포함되어 있다. build.sbt 파일에 빌드 스크립트를 작성하는데, 사실 sbt파일도 약간의 암묵적인 정의가 들어간 스칼라 DSL이기 때문에 스칼라 코드의 대부분을 작성할 수 있다. 대신 build.sbt 대신 build.scala을 쓴다면 써야 할 양이 늘어나지만 일반 스칼라 코드와 완전 동일하게 작성할 수 있다.
테스팅 프레임워크는 ScalaTest가 폭넓은 테스트 선택폭[22]으로 인해 널리 쓰인다. ScalaTest 외에도 Spec2, Scalacheck등의 프레임워크가 있다. JUnit을 그냥 사용하는 것도 가능하다.
4 관련 링크
- ↑ 더 나은 프로그래밍 언어로 나아가는 계단의 역할을 한다고 한다. 문서 상단의 공식 로고도 로잔 연방 공과대학교의, Scala가 개발된 곳에 있는 나선형 계단을 형상화한 것이다. #
- ↑ 확장이 용이한
- ↑ 단 연산자 우선순위 문제가 있으므로 DSL 제작 시에
모양 좋아보이는 것만 고르지 말고적절한 연산자를 골라야 한다. - ↑ +,% 등.
- ↑ ++와
가 스칼라에는 없는데, 이 규칙의 일관성을 위해서다. 겨우 한글자 덜 타이핑하자고 예외 조항을 만들기는 싫었다는 듯. 실제로 C++에서는 ++/의 오버로딩이 가능한데, 좀 복잡하다. 어차피 함수형 성격이 강해서 고전적인 루프문을 쓸 일이 적으므로 ++/--의 부재가 크게 느껴지진 않을 것이다. - ↑ 디폴트 인자와는 다르다. 디폴트 인자는 메소드 정의에서 값을 써 놓는 반면 암묵 인자는 메소드 정의에선 암묵 인자를 사용한다는 선언만 하고, 외부 범위에서 값을 받아온다
- ↑ 자바와는 달리, 스칼라에서는 트레이트도 그냥 extends 키워드를 사용한다.
- ↑ 자바 7까지 한정
- ↑ 구체적인 구현이 있는데 다중상속이 가능하면 다이아몬드 문제가 일어날 지도 모른다고 생각할 수도 있지만, 트레이트를 믹스인하는 순서에 따라 호출 메소드가 결정되므로 문제는 없다.
- ↑ 수정 불가능한 val일 경우 세터는 생성되지 않는다.
- ↑ 정확히는 자바빈 스타일. getXXX나 setXXX꼴을 말한다.
- ↑ 문서 상단의 예제가 바로 이 키워드를 사용한다.
- ↑ case에서 break;가 없으면 다음 case로 넘어가는 것
- ↑ 가변 변수는 아예 가변이라고 var로 명시해야 한다.
- ↑ 물론 이쪽의 불변도 C++의 const보다 자바의 final에 가까워서 레퍼런스는 유지해줘도 참조되는 객체의 내부 내용이 바뀌는 것은 막지 못한다.
- ↑ 물론 자바 스타일로. 스칼라 스타일로 자바 코드를 쓰고 싶다면 작은 DSL을 구축하면 된다.
- ↑ XML 리터럴 내에 있는 스칼라 코드 안에 XML 리터럴을 집어넣고...하는 무한 루프도 가능하다.
- ↑ 물론 자바 바이트코드로
- ↑ read-eval-print loop. 입력받은 것을 평가(실행)해서 그 결과를 출력하는 것을 반복하는 것.
- ↑ 다만 스칼라가 이런
정신나간짓을 한 덕분에 자바 9에서도 JShell이라는 이름으로 REPL이 추가될 예정이므로, 안드로이드에서도 볼 수 있을지도 모른다. - ↑ 물론 플러그인 필요
- ↑ 기본적으로 BDD이며, JUnit식 테스트부터 spec식 테스트까지 다양한 테스트 스타일이 가능하다.
- ↑ 한국어 번역
- ↑ 트위터 사 제작.
- ↑ 역시 트위터 사에서 제작했다. 다른 이펙티브 시리즈(이펙티브 자바, 이펙티브 C++ 등)와는 달리 책이 아니라 웹 문서이다.