Posted in scala

Scala for the Impatient, Chapter 1, 2, 3



Comment

Chapter 1

(1). 스칼라의 REPL 은 엄밀히 말해서 인터프리터가 아니다. 입력받은 코드를 자바 바이트코드로 컴파일 한 후 자바 가상머신에서 실행시킨뒤 결과를 돌려준다.

(2). 스칼라는 문자열을 위한 추가적인 연산들을 제공하기 위해 java.lang.String 오브젝트를 StringOps 오브젝트로 변환한다. "Hello".intersect("World") 가 그 예다. 따라서 ScalaDoc 을 보려면 SpringOps 클래스를 살펴보는 편이 정신 건강에 좋다.

마찬가지로 Int, Double 등에도 편의 메소드를 추가한 RichInt, RichDouble, RichChar 등을 제공한다. 1.to(10) 에서 1RichInt 로 변환된 뒤에 to 메소드를 적용한다.

참고로 스칼라에서는 숫자간 타입 변환을 위해 캐스팅이 아니라 메소드를 사용한다. 99.44.toInt, 99.toChar, "99.44".toDouble

(3). 스칼라에는 ++ 연산자가 없다.

(4). 스칼라에는 Static Method 대신 Singleton ObjectCompanion Object 가 있다.

(5). 오브젝트를 수정하지 않는, 인자가 없는 메소드는 괄호를 사용하지 않는다.

"Hello".distinct

(6). ScalaDoc 에서 implicit 로 태그된 메소드는 자동변환이다. 예를들어 BigInt 오브젝트는 필요할때 자동으로 intlongBigInt 로 바꾼다.

Chapter 2

(1). 스칼라에서는 구문(Statement) 이 아니라 모든 것을 식(Expression) 으로 취급한다. 그런데, else 가 없는 if 문은 값이 없을 수 있는데, 스칼라에서는 Unit 클래스를 도입해서 해결한다. Unit값 없음 을 뜻하는 () 을 값으로 가진다.

(2). 스칼라에서 할당(assignment)Unit 타입의 값을 가진다. 따라서 할당을 묶어서 사용하지 않는다.

x = y = z = 1 // no

(3) for 루프에서 인덱스가 필요하면, until 을 사용하면 된다.

val str = "lambda"  
var sum = 0  
for (index < - 0until str.length)  
  sum += index

(4). return 이 없는 삶에 익숙해지자, 익명함수를 사용할 경우 리턴값이 쓸모가 없다. break 쯤으로 여기는 것이 마음 편하다.

(5). 재귀 함수는 타입을 반드시 명시해야 한다. ML 이나 하스켈 같은 일부 언어는 힌들리-밀너 알고리즘을 이용하여 재귀함수의 타입을 추론할 수 있지만, 이 알고리즘은 객체지향과는 잘 안맞는다.

(6). 가변인자가 필요하면, * 를 사용하자. 배열을 풀어헤치기 위해 사용하는 _*Splat operator

def sum(args: Int*) = {  
  var sum = 0;
  for(arg <- args) sum += arg
  sum
}

sum(1 to 5: _*)

def recursiveSum(args: Int*): Int = {  
  if (args.length == 0) 0
  else args.head + recursiveSum(args.tail : _*)
}

참고로, Object 타입의 가변인자를 받으면 42.asInstanceOf[AnyRef] 처럼 직접 변환해야 한다.

(7). = 가 없는 함수는 Unit 리턴 타입을 가지며, 프로시저 라 불린다. 값을 리턴하지 않기 때문에, 사이드 이펙트만를 위해 사용한다. 프로시저에서 Unit 을 직접 명시할수도 있다.

def proc(str: String) {  
  ...
  ... // side effect
  ...
}

(8). lazy val 을 이용하면, def 처럼 해당 변수가 사용되기 전까지 평가되지 않는다.

lazy val words = scala.io.Source.fromFile("words.txt").mkString  

그런데, lazy 로 선언되면 값을 접근할때 마다 스레드세이프하게 초기화가 되었는지 확인하는 검사가 필요하므로 비용이 든다.

(8). 스칼라에는 체크예외가 없다. 모두 런타임 예외다. 그리고, throwNothing 을 값으로 가지는데, if 문에서는 Nothing 대신 다른 분기의 타입으로 식의 값이 정해진다.

Chapter 3

(1). 고정길이 배열이 필요하면 Array 로 사용하면 된다. 만약 초기값이 필요없으면 new 를 사용하고, 아니면 new 를 제외한다.

val nums = new Array[Int](10)  
val strs = Array[String]("Hello", "World")  

고정 배열은 JVM 내부에서 자바의 일반 배열로 처리된다.

(2). 가변빌이 배열이 필요하면 ArrayBuffer 를 사용하면 된다. 자바의 ArrayList 라 보면 된다.

val ab = ArrayBuffer[Int]()  
ab += 1  
ab += (2, 3)  
ab ++= Array(4, 5)  

ArrayBuffer 끝에 원소를 삽입하고 삭제하는건 성능이 괜찮지만, 중간에 삽입하고 삭제하면 기존의 원소를 옮겨야 하므로 성능이 떨어진다는 점에 주의하자.

만약 ArrayArrayBuffer 로 만들려면 toBuffer 메소드를, ArrayBufferArray 로 만들려면 toArray 를 호출하면 된다.

(3). untilRichInt 클래스에 속한다. 만약 0부터 10까지 2씩 건너뛰고 싶으면

0 to (10, 2)

거꾸로 순회하고 싶으면

(0 to 10).reverse

(4). 만약 ArrayBuffer 에 있는 음수 중, 처음 것만 제외하고 모두 삭제하고 싶을때 Flag 를 사용하면 다음과 같은 코드를 만들 수 있다.

val first = true  
val n = arr.length  
var i = 0

while(i < n) {  
  if (arr(i) >= 0) i += 1
  else {
    if (first) { first = false; i += 1}
    else {
      arr.remove(i); n -= 1
    }
  }
}

그런데, 배열 버퍼 중간에 있는 원소를 삭제하는건 비효율적이기 때문에, 차라리 인덱스를 보존하고 한꺼번에 옮겨 자르는 편이 더 낫다.

val indexes = for(i until arr.length if arr(i) >= 0 || first) yield {  
  if (a < 0) first = false;
  i
}

for(j until indexes.length) {  
  arr(j) == arr(indexes(j))
}

arr.trimEnd(arr.length - indexes.length)  

(4). for comprehensionguardyield 를 사용하든, collectionfiltermap을 사용하든 하는일은 같다.

(5). ArrayBufferArraymax, min, sum 과 같은 메소드 들을 가지고 있다. 그리고 min, max 혹은 scala.util.Sorting.quickSort 에 들어갈 컬렉션의 원소타입들은 반드시 비교 연산을 가지고 있어야 하는데, 숫자, 문자열, Ordered Trait 을 가지는 타입이 해당된다.

(6). 컬렉션의 원소를 이쁘게 출력하고 싶으면 mkString 을 이용하자.

Array(1, 2, 3).mkString(" and ")  
// "1 and 2 and 3"
Array(1, 2, 3).mkString("<", ", ", ">")  
//  "<1, 2, 3>"

(7) ArraytoStringArrayBuffer 와는 달리 쓸모가 없다.

scala> Array(1, 2, 3).toString  
// res28: String = [I@412d54b3

(8). ScalaDoc 을 여행하다보면 기기묘묘한 것들을 만날 수 있다.

def appendAll(xs: TraversableOnce[A]): Unit 같은 경우, xsTraversableOnce 트레이트를 구현하는 콜렉션이라 보면 된다.. 스칼라의 모든 컬렉션은 TraversableOnce 또는 흔하게 볼 수 있는 컬렉션의 트레이트로 TraversableIterable 이 있다.

def += (elem: A): ArrayBuffer.this.type 의 경우 체이닝이 가능하도록 자기 자신을 리턴하는 메소드다. 이를테면 b += 4 += 5. 처럼

def copyToArray[B >: A] (xs: Array[B]): Unit 의 경우 ArrayBuffer[A] 의 모든 원소를 Array[B] 로 복사하는데 BA 의 하위 타입이다. ArrayBuffer[Int], Array[Any] 처럼

(9). 자바처럼 당연히 컬럼이 고정되지 않은 다차원 배열도 만들 수 있다.

val triangle = new Array[Array[Int]](10)  
for(i <- 0 to triangle.length)  
  triangle(i) = new Array[Int](i + 1)

만약 컬럼이 고정된 다차원 배열을 만든다면 ofDim 메소드를 이용하면 된다. 접근하려면 괄호를 두번 사용한다.

val matrix = Array.ofDim[Double](3, 4)

maxtric(0)(1)  

(10). 스칼라 배열은 자바 배열로 구현되므로, 당연히 주고 받을 수 있다. java.util.List 를 받거나 리턴하는 자바 메소드를 호출하면 스칼라 코드에서 ArrayList 를 사용할 수 있지만, 이것 대신 scala.collection.JavaConversions 에 속한 메소드들을 임포트하면, 스칼라 버퍼를 자동으로 자바 리스트로 변환할 수 있다.

아래 예제에서 자바의 java.lang.ProcessBuilderList<String> 을 받는 생성자를 가지고 있는데, JavaConversions.bufferAsJavaList 를 임포트하면 스칼라 버퍼가 java.util.List 인터페이스를 구현한 자바 클래스 오브젝트로 감싸진다.

iport scala.collection.JavaConversions.bufferAsJavaList  
import scala.collection.mutable.ArrayBuffer

val command = ArrayBuffer("ls", "-al" "/home/user")  
val processBuilder(command)  

반대로 자바 메소드가 java.util.List 리턴하면 Buffer 로 자동으로 변환할 수 있다. 아래 예제에서 cmd == command 다. 바로 ArrayBuffer 로 받지 않는다는 점에 주의하자. Buffer 만 보장한다.

import scala.collection.JavaConversions.asScalaBuffer  
import scala.collection.mutable.ArrayBuffer  
sd  
val cmd: Buffer[String] = pb.command()  

참고로, 스칼라의 컬렉션은 immutable 이 기본이고, 자바의 컬렉션은 mutable 이 기본이다.

Author

1ambda

Functional, Scala, Akka, Rx and Haskell