개선된 REPL
REPL의 입력핸들러인 jline이 개선되었고 커서핸들링은 더 신뢰할 수 있게 수정되었으며 bash 스타일, ctrl-R히스토리 검색, :imports, :implicits, :keybindings등의 새로운 명령어가 추가되었습니다.
scala> :keybindings
Reading jline properties for default key bindings.
Accuracy not guaranteed: treat this as a guideline only.
1 CTRL-A: move to the beginning of the line
2 CTRL-B: move to the previous character
4 CTRL-D: close out the input stream
5 CTRL-E: move the cursor to the end of the line
6 CTRL-F: move to the next character
7 CTRL-G: abort
8 BACKSPACE, CTRL-H: delete the previous character 8 is the ASCII code for backspace and therefor deleting the previous character
9 TAB, CTRL-I: signal that console completion should be attempted
10 CTRL-J, CTRL-M: newline
11 CTRL-K: erase the current line
12 CTRL-L: clear screen
13 ENTER: newline
14 CTRL-N: scroll to the next element in the history buffer
15 CTRL-O: move to the previous word
16 CTRL-P: scroll to the previous element in the history buffer
18 CTRL-R: search history
20 CTRL-T: move to next word
21 CTRL-U: delete all the characters before the cursor position
22 CTRL-V: paste the contents of the clipboard (useful for Windows terminal)
23 CTRL-W: delete the word directly before the cursor
24 CTRL-X: delete the word directly after the cursor
127 DELETE, CTRL-?: delete the next character 127 is the ASCII code for delete
:keybindings를 입력하면 REPL내에서 사용할 수 있는 단축키를 확인할 수 있습니다.
scala> :type
:type <expression>
scala> :type 1
Int
scala> :type "test"
java.lang.String
scala> :type 1 :: 2 :: Nil
List[Int]
:type를 사용하면 평가없이도 타입을 확인해 볼 수 있습니다.
(reverse-i-search)`cla': class A {
Ctrl+R을 입력하면 REPL에서 입력한 히스토리를 검색할 수 있습니다. 위 쉘은 Ctrl+R을 입력하고 'cla'를 입력한 화면이고 입력하는 문자열로 시작하는 명령어를 찾아주고 엔터를 입력하면 해당 명령어를 실행해줍니다.
// Converter.scala
package Test
class Converter(n: Int) {
def hour = { n + " hours" }
}
object Converter {
}
// ImplicitTest.scala
package Demo
import Test.Converter
object ImplicitTest {
implicit def converter(n: Int) = new Converter(n)
}
scala> :implicits
No implicits have been imported other than those in Predef.
scala> import Demo.ImplicitTest._
import Demo.ImplicitTest._
scala> :import
1) import java.lang._ (163 types, 168 terms)
2) import scala._ (798 types, 806 terms)
3) import scala.Predef._ (16 types, 167 terms, 96 are implicit)
4) import Demo.ImplicitTest._ (25 terms, 1 are implicit)
scala> :implicits
/* 1 implicit members imported from Demo.ImplicitTest */
/* 1 defined in Demo.ImplicitTest */
implicit def converter(n: Int): Test.Converter
:implicits를 사용하면 현재 범위내에서 임포트된 implicit 메서드를 보여줍니다. (물론 REPL에서 사용하기 전에 위의 두 파일을 컴파일 해주어야 합니다.) 여기서 나오는 implicit는 임포트된것만 보여주기 때문에 REPL에서 정의한 것은 나오지 않습니다. Predef에 나온 implicits의 리스트를 보고 싶다면 :implicits -v를 사용합니다.
scala> :imports
1) import java.lang._ (156 types, 161 terms)
2) import scala._ (798 types, 806 terms)
3) import scala.Predef._ (16 types, 167 terms, 96 are implicit)
:imports를 입력하면 현재 import되어 있는 클래스들을 보여줍니다.
scala> class A { private def test {println("test")}}
defined class A
scala> object A { def method { var a = new A; a.test }}
<console>:8: error: method test in class A cannot be accessed in A
object A { def method { var a = new A; a.test }}
^
REPL은 라인단위로 실행하고 평가하기 때문에 위와 같이 companion object같은 경우는 정의할 수 없습니다. 이런 경우는 :paste를 사용합니다.
scala> :paste
// Entering paste mode (ctrl-D to finish)
class A { private def test {println("test")}}
object A { def method { var a = new A; a.test }}
// Exiting paste mode, now interpreting.
defined class A
defined module A
scala> A.method
test
:paste를 사용하면 paste mode에 들어가며 여기서 여러라인을 한꺼번에 입력할 수 있고 입력이 완료되면 ctrl+D로 종료하면 한꺼번에 실행되기 때문에 companion object를 정의할 수 있습니다.
scala> :javap A
Compiled from "A.scala"
public class A extends java.lang.Object implements scala.ScalaObject{
public static final void methodB();
public void methodA();
public A();
}
scala> class Test
defined class Test
scala> :javap Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public Test();
}
:javap 명령어를 통해서 scala파일이나 클래스를 디컴파일해서 볼 수 있습니다.
SBT의 Process
SBT의 Process 라이브러리가 Scala에 포함되었고 sys.process 팩키지에서 사용할 수 있습니다.
temp $ cat SBTDemo.scala
import sys.process._
Process("pwd") !
temp $ scala SBTDemo.scala
/Users/outsider/projects/scala/temp
Process 라이브러리에 대해서는 Wiki문서를 참고하세요.
App Trait
2.9.0에서 추가된 App trait는 deprecated된 Application trait보다 안전하고 강력해 졌습니다. 이제 메인 애플리케이션은 다음과 같이 작성합니다.
object Echo extends App {
println("Echo" + (args mkString " "))
}
Application trait를 상속받았을 때는 쓰레드 세이프하지 않았고 종종 VM이 최적하지 못했기 때문에 애플케이션의 바디가 오브젝트의 초기화 과정에서 실행되었습니다. App trait를 상속받으면 Scala 2.9의 지연 초기화기능으로 메인메서드의 일부로 바디를 실행합니다. 또한 기존에 불가능했던 커맨트라인 아규먼트를 args를 통해서 접근할 수 있습니다.
DelayedInit Trait
DelayedInit trait는 클래스와 객체의 초기화 순서를 커스터마이징해줍니다. 클래스나 오브젝트가 DelayedInit trait를 상속받았을 때 모든 초기화 코드는 클로저안에 묶이고 delayedInit 메서드에 아규먼트로 전달됩니다. delayedInit는 DelayedInit trait의 추상메서드로 정의되어 있기 때문에 delayedInit를 맘대로 구현할 수 있습니다. 예를들 어 스칼라의 새로운 App trait는 모든 초기화 순서를 내부 버퍼에 저장하고 오브젝트의 메인메서드가 호출될 때 실행합니다.
초기화 코드는 클래스안에만 담겨지고 오브젝트는 DelayedInit으로 전달됩니다. trait안에 있는 초기화 코드는 영향받지 않습니다.
// DelayTest.scala
class A {
println("before Child Class")
println("after Child Class")
}
class B extends A {
println("initialized B")
}
class C extends A {
println("initialized C")
}
val childB = new B
println("----")
val childC = new C
A 클래스를 상속받은 B와 C클래스가 있고 B와 C를 생성하면 다음과 같은 결과나 나옵니다.
$ scala DelayTest.scala
before Child Class
after Child Class
initialized B
----
before Child Class
after Child Class
initialized C
A가 생성자가 실행되고 B와 C의 생성자가 실행됩니다. 자식의 생성자가 실행되는 앞뒤로 어떤 액션을 할 필요가 있다면 다음처럼 작성할 수 있습니다.(간단한 사용테스트 일뿐 정확한 DelayedInit의 용도는 찾아봐야 할듯 합니다.)
// DelayTest2.scala
class A extends DelayedInit {
println("initialized A")
override def delayedInit(body: => Unit) {
println("before Child Class")
body
println("after Child Class")
}
}
class B extends A {
println("initialized B")
}
class C extends A {
println("initialized C")
}
val childB = new B
println("----")
val childC = new C
A 클래스에서 DelayedInit을 상속받아서 내부에서 delayedInit을 재정의하였습니다. delayedInit은 파라미터로 생성자의 바디를 받게 되고 함수 내부에서 원하는대로 사용할 수 있습니다. 이 코드를 실행하면 다음과 같이 됩니다.
$ scala DelayTest2.scala
before Child Class
initialized A
after Child Class
before Child Class
initialized B
after Child Class
----
before Child Class
initialized A
after Child Class
before Child Class
initialized C
after Child Class
Scala Runner
스칼라코드를 다음과 같은 방법으로 실행할 수 있습니다:
- scala <jarfile> : 메인클래스를 실행하며 java -jar와 비슷합니다.
- scala <classname> : 클래스의 메인메서드를 실행합니다.
- scala <sourcefile> : 스칼라 스크립트로 실행합니다
- scala <sourcefile> : 소스파일이 스크립트가 아니면 최상위 객체의 메인 메서드를 찾아서 실행합니다. scalac를 하고 바로 실행하는 것과 같습니다.
- scala -save <sourcefile> : 컴파일된 소스로 jar파일을 생성해서 나중에 scala <jarfile>로 실행할 수 있습니다.
Java Interop
@Strictfp 어노테이션을 지원합니다.(자바의 @Strictfp를 지원하도록 추가된 것이라는데 자세히 몰라서 설명을 패스합니다.) JavaConverters와 JavaConversions에서 많은 부분이 수정되어 더욱 자연스럽게 상호작용할 수 있게 되었습니다. 프리미티브타입과 박싱된 타입은 암묵적으로 변환됩니다.
그 외 기능들...
try-catch-finally의 일반화되었습니다.
try body
catch handler
finally cleanup
body와 cleanup에는 임의의 표현식을 사용할 수 있고 handler에는 유효한 예외핸들러(PartialFunction[Throwable, T])는 어떤 것이든 사용할 수 있습니다.
그 외 collectFirst, maxBy, minBy, span, inits, tails, permutations, combinations, subsets가 컬렉션에 추가되었고 AnyRef의 전문화로 타입파라미터에 AnyRef의 서브타입의 사용이 가능해져서(class Foo[@specialize(AnyRef) T](arr: Array[T]) { … }) 배열의 인덱싱과 갱신이 효율적이 되었습니다. 그리고 많은 버그가 수정되고 성능향상이 이루어졌습니다.
덧) 2.9의 대표적인 기능인 Parallel Collection은 내용이 많아서 따로.. ㅎ
[참고 문서]
Scala 2.9.0 final
On Scala 2.9's road...
scala: how to wrap execution of subclass constructor?
1. 우와, 이제 App trait가 강력해졌으니 굳이 main() 를 써서 구현할 필요가 전혀 없어진 듯 하네요. args, multi thread, vm 최적화 문제가 다 해결되었으니까요.
2. strictfp 는 annotation이 아니라 java의 예약어인 strictfp를 언급한 듯 하네요. strictfp는 부동소수점 연산을 좀 더 엄격하게 하라고 명시하는 지시어입니다. (strict floating point?) strictfp를 붙이지 않을 경우, 플랫폼 내부적으로 좀 더 최적화된 방식으로 연산을 수행합니다. 그리고 연산 과정의 중간 결과가 지정된 type의 크기를 넘억갈 수 도 있습니다. 즉, 32bit인 float과 32bit인 float을 곱한 중간 결과가 32bit를 넘어갈 수도 있습니다. 물론 최종 결과는 다시 float에 맞게 변환이 되어야 겠지요. 단순한 사칙연산이라면 의미가 없겠지만 method나 class에 붙일 경우엔 나름의 의미를 갖게 됩니다. 이렇게 하면 1. 중간 계산 결과가 지정된 크기를 넘어가버리는 연산도 제대로 된 결과를 반환할 수 있으며, 2. 좀 더 연산이 빠릅니다. 3. 그러나 연산의 중간 값이 플랫폼에 따라 달라질 수 있기 때문에 이식성이 떨어집니다. 즉, 플랫폼 별로 서로 다른 결과가 나올 수 있습니다.
반면 strictfp를 붙이면 1. 중간 연산 결과도 지정된 타입의 크기를 벗어나지 않으며, 2. 속도는 조금 떨어지지만 3. 모든 플랫폼에서 동일한 결과를 얻을 수 있으므로 이식성을 보장할 수 있습니다.
위키 페이지에도 잘 나와있습니다. http://en.wikipedia.org/wiki/Strictfp
우와~ 저 위키페이지 보긴 했는데 좀 어려워 보이고 길어서 제대로 안보았는데 자게한 설명감사합니다.
사실 strictfp를 잘 몰라서 2.9 릴리즈 페이지의 "The @strictfp annotation is now supported."를 그대로 가져다가 쓴 것입니다. 찾아보니 애노테이션에 대한 정보는 안나오는군요.
이 기능이 추가된 이슈는 https://issues.scala-lang.org/browse/SI-1708 인듯 한데 여기서도 애노테이션이라고 말하고 있기는 한데 애노테이션이 따로 있는지 어떤지는 잘 모르겠군요. ㅎㅎㅎ
암튼 감사합니다.