3장 next steps in scala - codeport/scala GitHub Wiki

Step 7. Parameterize arrays with types

  • new를 사용해서 오브젝트나 클래스 인스턴스를 생성한다.
    • val list = List(1,2,3) 경우는 List.applyFactory method로 만들어져 있어서 new가 없다.
  • 클래스 인스턴스를 생성할 경우에 type parameter와 함께 생성 할 수 잇다.
    • val lang = new Array[String](3) // String type에 Array를 생성, 자바에 generic과 비슷 여기서 타입은 Array[String](3) 이 아닌 Array[String] 이다.
  • 자바와 달리 배열에 대한 접근은 [] 가 아닌 () 이다.
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
  • 변수를 val로 정의했을 때 변수는 재할당할 수 없지만 참조하고 있는 객체는 변경될 수 있다.
  • 0 to 2 이라고 호출하면 실제로는 (0).to(2) 가 실행된다. 이 문법(괄호와 . 의 생략)은 명시적인 리시버가 있을때만 가능하다.
println 10 // (X)
Console println 10 // (O)
  • 하나 이상의 값을 감싸는 괄호를 변수에 적용했을때 해당 변수의 apply 메서드를 호출한다.
    • ex) greetStrings(i)greetStrings.apply(i)가 된다.
    • 이와 유사하게 값을 할당할 때, greetStrings(0) = "hello"greetStrings.update(0, "hello") 가 된다.

Step 8. Use lists

  • 함수형 프로그래밍의 주요 개념 중 하나는 사이드이펙트를 갖지 않는 것이다.
    • 메서드의 유일한 동작은 계산 후 값을 리턴해야한다.
    • 장점으로는 좀 더 덜 꼬이고, 더 신뢰할 수 있고 재사용가능하다.
    • 이렇게 하면, 믿을 수 있고 또한 재사용할 수 있다.
    • 컴파일 타임에 타입 오류 체크를 통해 로직 에러를 확인 할 수 있다.
    • 객체를 불변하게 만드는 것을 통해 이 함수형 언어의 철학을 반영할 수 있다.
  • Scala의 List(scala.List)는 항상 불변(immutable)하다.(Java의 java.util.List 타입과는 다르다.)
  • :::는 리스트를 이어붙힌다.(concat)
  • ::는 cons라고 읽으며 리스트의 앞에 새로운 엘리먼트를 추가한 새로운 리스트를 리턴한다.
    • cons로 리스트를 생성하면 마지막에 Nil을 추가한다.(이유는 아래를 참고)-> Nil에 엘리먼트가 추가되는거 아닌가요? -> Nil에 엘리먼트가 추가되는것 맞습니다.
  • 콜론으로 끝나는 메서드는 right operand(우측 결합)이다. 언어 설계의 실수가 아니다.
    • 1::twotwo.::(1)이다.
    • 배열의 끝에 추가하면 배열 크기만큼 선형시간이 걸린다. 반대로 Scala의 경우, 상수시간만큼만 소비. 굳이 쓰고 싶다면, ListBuffer

Step 9. Use tuples

  • tuple은 유용한 컨테이너 객체이다.
    • 튜플은 불변하다.
    • 다른 종류의 타입을 함께 담을 수 있다.
    • 메소드에서 여러 오프젝트를 리턴해야하는 경우 유용하다.
      • 자바에서는 빈즈 객체를 만들어야하지만, 스칼라는 tuple을 리턴하도록 하면 된다.
    • 개념상으론 길이 제한 없이 Tuple을 만들 수 있지만, 현재의 스칼라는 Tuple22(22개)까지 지원한다.
    • 튜플의 엘리먼트에 접근할 때는 tuple._1과 같이 접근한다.
      • tuple(n)으로 접근하지 않는 이유는 apply 메서드는 항상 같은 타입을 반환해야하지만 tuple은 같은 타입이 아닌 경우가 있기때문이다.
    • 튜플의 인덱스는 1부터 시작한다. 이는 다른 전통적 함수형 언어들(Haskell, SML 등)과 같은 접근법이다.
val pair = (99, "Luftballons")
println(pair._1) // 99
println(pair._2) // Luftballons

Step 10. Use sets and maps

스칼라는 functional style과 imperative style의 장점을 취할 수 있게 도와준다. 컬렉션 라이브러리의 mutable collection과 immutable collection에 차이가 생겼다.

Set

  • 스칼라 Set의 클래스 계층(trait는 자바의 인터페이스와 유사하다) Set 계층 구조
  • mutable set은 재할당될 필요가 없으므로 변수가 val이 될 수 있고 immutable set은 재할당이 되어야 하므로(+=를 사용해서) 반드시 변수가 var가 되어야 한다.
    • immutable : obj = obj + "string"
    • mutable : obj.+=("string")
  • Map은 기본적으로 immutable이다.(no import)
  • mutable set에는 += 메서드가 있지만 immutable set에는 += 메서드가 없다.
  • mutable set을 사용하려면 import해야 한다.
import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"

Map

  • 스칼라 map의 클래스 계층
    • scala.collection.Map <<trait>>
      • scala.collection.immutable.Map <<trait>>
        • scala.collection.immutable.HashMap
      • scala.collection.mutable.Map <<trait>>
        • scala.collection.mutable.HashMap
  • 기본값은 immutable map이고 mutable map을 사용하려면 import해야 한다.
val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V") // scala.collection.immutable.Map[Int,String]
println(romanNumeral(4)) // IV

Step 11. Learn to recognize the functional style

  • 함수형 프로그래밍을 배우면 더 나은 프로그래머가 될 수 있다.
  • var가 있으면 십중팔구는 imperative style이라 볼 수 있으며, val만 있으면 십중팔구는 functional style이라고 할 수 있다.
  • 평셔널 코드가 더 깔끔하고 간결하다.
  • 함수의 리턴타입이 Unit이면 사이드이펙트가 존재한다는 신호이다.
  • 사이드 이펙트를 최소화 하면 테스트가 쉬운 이점도 있다.
  • 스칼라 프로그래머의 자세
    • val, immutable 객체, 사이드이펙트 없는 메서드 선호
    • 그 다음 필요할 때 var, mutable 객체, 사이드이펙트있는 메서드 사용
    • 절차형, 함수형 어느 한쪽이 나쁜게 아니다. 모두를 사용할 수 있다면 두가지 툴을 가지게 된 것이다.
// imperative style
def printArgs(args: Array[String]): Unit = {
  var i = 0
  while (i < args.length) {
    println(args(i))
    i += 1
  }
}
// functional style
def printArgs(args: Array[String]): Unit = {
  for (arg <- args)
    println(arg)
}
// functional style
def printArgs(args: Array[String]): Unit = {
  args.foreach(println)
}

만일에 imperative style이 더 좋은 상황을 맞이하게 된다면, imperative style를 사용해라.

Step 12. Read lines from a file

  • 파일을 읽어서 각 라인의 글자수를 각 라인앞에 출력해 주는 예제
// countchar.scala
import scala.io.Source

if (args.length > 0) {
  for (line <- Source.fromFile(args(0)).getLines())
    println(line.length + " " + line)
}
else
  Console.err.println("Please enter filename")
$ scala countchar.scala countchar.scala
22 import scala.io.Source
0
22 if (args.length > 0) {
51   for (line <- Source.fromFile(args(0)).getLines())
37     println(line.length + " " + line)
1 }
5 else
46   Console.err.println("Please enter filename")
import scala.io.Source

def widthOfLength(s: String) = s.length.toString.length

if (args.length > 0) {
  val lines = Source.fromFile(args(0)).getLines().toList

  val longestLine = lines.reduceLeft(
    (a, b) => if (a.length > b.length) a else b
  )
  val maxWidth = widthOfLength(longestLine)

  for (line <- lines) {
    val numSpaces = maxWidth - widthOfLength(line)
    val padding = " " * numSpaces
    println(padding + line.length + " | " + line)
  }
}
else
  Console.err.println("Please enter filename")

질문

  • p87의 Why not append to lists?는 리스트라서 뒤에 붙히는게 느리다는 건가요?
    • += 단방향 리스트 이기 때문에 리스트 끝에 새로운 원소를 더하려면 리스트 길이에 따른 O(N)연산이 되기 때문에 느립니다, cons 등과 같이 리스트 앞쪽에 붙이는 연산은 항상 O(1)의 속도를 가집니다.
  • Set등의 경우 mutable과 immutable이 이름이 동일한데 혼동될 여지는 없나요?
  • 추론되는 타입에 대해서도 import를 해야하나요?
  • Listing 3.10의 컨벤션은 좀 혼란스럽지 않은가요?

drypot님의 한글 번역