7장 built in control structures - codeport/scala GitHub Wiki

  • 스칼라의 제어구문(syntax) : if, while, for, try, match, 함수호출
  • Scala의 대부분의 제어 구문은 값을 반환함. <- functional language들이 이렇게 동작한다고 함.
  • Java의 ternary operator인 ? 의 경우, Scala의 if로 대체 가능함. 이는 ifelse블럭의 마지막 평가값이 return되기 때문으로 이해했음. 이런 형태를 취함으로써 임시 변수의 필요성을 줄일 수 있다고 함.
int a = 1 > 10 ? 1 : 0; //java
val a:Int = if( 1 > 10 ) 1 else 0 //scala

scala> val k = try { val a = 1; }  
k: Unit = () 

scala> val a = 1 match { case 1 => 1; case 2 => 2; }
a: Int = 1

7.1 If expressions

  • 일반적인 if문의 기능을 수행한다.
val filename = 
  if (!args.isEmpty) args(0)
  else "default.txt"
  • var보다 val을 사용하면 좋은점.
    • 자바의 final변수처럼 사용할 수 있다.
    • 등식추론에 더 도움이 된다. "equational reasoning"
    • 읽기도 쉽고 리팩토링하기도 쉽다.

7.2 While loops

  • 일반적인 while문의 기능을 수행한다.
def gcdLoop(x: Long, y: Long): Long = {
  var a = x
  var b = y
  while (a != 0) {
    val temp = a
    a = b % a
    b = temp
  }
  b
}
  • do-while문도 있다.
var line = ""
do {
  line = readLine()
  println("Read: " + line)
} while (line != "")
  • whiledo-while은 반환형이 Unit이기 때문에 표현식이 아니라 루프라고 한다.
scala> val a = while( false) { }
a: Unit = ()
  • Unit타입이 반환하는 값은 unit value라고 하며 ()로 표기한다.
    • 자바와는 다르다.
    • 자바의 void와는 다르게 함수의 값을 비교할 수 있다.
    • 대입식을 비교하는 구문으로 사용하면 루프를 쓰지 말아야 한다.
      (대입하면 스칼라에서는 결과가 unit value, ()라서 ""이 될 수 없다. > 무한루프로 빠진다. pg.118)
  • while문 대신 함수형으로 재귀호출을 쓸 수 있다.
  • java의 할당문은 할당된 값을 반환하지만, Scala의 할당문은 Unit 값을 반환함.
//java
public class A {
  public static void main(String[] args)
  {
    String a = null;
    System.out.println( a= "a");
    }
  }
// a //실행결과
//scala
scala> val a = { val b = 3; }
a: Unit = () 

7.3 For expressions

  • scala의 for문은 상당히 강력하다.
  • collection iteration
val filesHere = (new java.io.File(".")).istFiles
for (file <- filesHere)
  println(file)
* `file <- filesHere`부분을 generator라보 부르고 콜렉션 요소로 반복문을 수행한다.
* `Range`를 사용해서 `for`문을 사용할 수 있다.
scala> for (i <- 1 to 4) println("Iteration " + i)
Iteration 1
Iteration 2
Iteration 3
Iteration 4
scala> for (i <- 1 until 4) println("Iteration " + i)
Iteration 1
Iteration 2
Iteration 3
  • 스칼라에서 정수를 반복하는 일반적인 방법.
// Not common in Scala....
for (i <- 0 to filesHere.length - 1)
  println(filesHere(i))
  • 스칼라에서는 켈럭션에서 직접 반복을 할 수 있기 때문에 거의 사용되지 않는 방법

  • Filtering

    • for문 안에 if절을 넣어 필터링을 할 수 있다.
    • for문은 표현식.(타입을 가진 콜렉션이 결과값으로 나오기 때문)
val filesHere = (new java.io.File(".")).istFiles
for (file <- filesHere if file.getName.endsWith(".scala"))
  println(file)
// 다음과 동일하다.
for (file <- filesHere)
  if (file.getName.endsWith(".scala"))
    println(file)
* 필터링이 여러개 필요하면 필요한 만큼 쭉 if 를 붙이면 됨. && 로 덧붙이는 게 아님. 
for (
  file <- filesHere
  if file.isFile
  if file.getName.endsWith(".scala")
) println(file)
* _질문:  && 로 덧붙이는 것이랑 if 를 여러개 붙이는 것이랑 차이가 있을까?_ -> 별 차이 없는 듯 한데.
scala> val a = for( i <- 1 to 10 ; if i %2 == 0 ;  if i %3 == 0 ) yield i
a: scala.collection.immutable.IndexedSeq[Int] = Vector(6)
scala> val a = for( i <- 1 to 10 ; if i %2 == 0 && i %3 == 0 ) yield i
a: scala.collection.immutable.IndexedSeq[Int] = Vector(6) 
  • Nested iteration
    • <-절을 추가하여 중첩루프로 쓸 수 있다.
def fileLines(file: java.io.File) = scala.io.Source.fromFile(file).getLines().toList
def grep(pattern: String) =
  for (
    file <- filesHere
    if file.getName.endsWith(".scala");;
    line <- fileLines(file)
    if line.trim.matches(pattern)
  ) println(file + ": " + line.trim)
grep(".*gcd.*")
* `{}`를 `()`대신 쓸 수도 있다.(`{}`를 쓰면 `;`을 안써도 된다.)  
  • Mid-stream variable bindings
    • 반복되는 표현식이의 계산을 한번만 하고 싶다면 =를 사용해서 새로운 변수에 바인딩할 수 있다. 여기서 바인딩된 변수는 val은 없지만 val처럼 사용한다.
def fileLines(file: java.io.File) = scala.io.Source.fromFile(file).getLines().toList
def grep(pattern: String) =
  for (
    file <- filesHere
    if file.getName.endsWith(".scala");;
    line <- fileLines(file)
    trimmed = line.trim
    if trimmed.matches(pattern)
  ) println(file + ": " + trimmed)
grep(".*gcd.*")
  • Producing a new collection
    • 지금까지는 반복문만 순회했지만 각 반복에서 생성된 값을 yield를 사용해서 생성할 수 있다.
for clauses yield body
def scalaFiles =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
  } yield file
* [yield에 대한 토론](http://stackoverflow.com/questions/1052476/can-someone-explain-scalas-yield)  

7.4 Exception handling with try expressions

  • 예외를 던지는 것은 자바와 동일하다.
throw new IllegalArgumentException
  • throw도 결과값을 가지는 표현식이고 기술적으로는 throwNothing타입이 된다. 어떤 타입도 될 수 있으며 이 반환값을 사용할 수는 없다. --> 이건 126pg 에서 else 문에 throw가 들어가도 문법에 어긋나지 않음을 설명하기 위함이라고 봄.
  • catch절은 패턴매칭을 이용해서 예외를 처리한다.
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

try {
  val f = new FileReader("imput.txt")
} catch {
  case ex: FileNotFoundException => // handle missing file
  case ex: IOException => //Handle other I/O error
}
* 스칼라는 throws 절에서 checked exception를 잡거나 선언하도록 하지 않는다. 필요하다면 `@throws` 어노테이션으러 선언할 수 있다.
  • filnally로 구문이 종료되는 것과 상관없이 항상 수행되어야 하는 코드를 처리한다. 자바랑 다를 바 없는 듯. 9.4절에 나오는 loan pattern을 고려할 수 있다고 함.
  • 예외가 던져졌는데 catch되지 앟으면 표현식의 결과값은 없다.
  • try, catch, finally도 value를 반환함. (java랑 다름!) 다만 finally의 평가 결과는 무시됨. 따라서 finally에서 명시적으로 return을 하지 않는 이상 finally의 평가 값은 버려짐.

7.5 Match expressions

  • switch문처럼 여러 대안을 선택할 수 있다.(int, enum만 쓸 수 있는 java의 switch 보다 훨씬 강력)
  • default_
  • 자바의 switch문과는 다르다.
    • 다양한 종류의 타입을 쓸 수 있다.
    • break가 없다.(case 뒤에 break;를 붙일 필요 없음. 무조건 break.)
    • 값이 도출된다.
대상 match {
  case 1 => expr1
  case 2 => expr2 ...
  case _ => default
}
  • match 표현식도 값을 반환함.

###7.6 Living without break and continue

  • 스칼라에는 breakcontinue가 없다.
  • boolean 타입의 필드를 사용하면 break, continue를 쓰지 않을 수 있음.
  • 모든 continue는 if문으로 break문은 boolean값으로 바꿀 수 있다.
// This is Java
int i = 0;
boolean foundIt = false;
while (i < args.length) {
  if (args[i].startsWith("-")) {
    i = i + 1;
    continue;
  }
  if (args[i].endsWith(".scala"")) {
    foundIt = true;
    break;
  }
  i = i + 1;
}
// looping without break or continue
var i = 0
var foundIt = false

while (i < args.length && !foundIt) {
  if (!args(i).startsWith("-")) {
    if (args(i).endsWith(".scala"))
      foundIt = true
  }
  i = i + 1
}
// 재귀로 다시 구현
def searchFrom(i: Int): Int =
  if (i >= args.length) -1
  else if (args(i).startsWith("-")) searchFrom(i + 1)
  else if (args(i).endsWith(".scala")) i
  else searchFrom(i + 1)

val i = searchFrom(0)
  • 필요하다면 scala.util.control패키지에 있는 Breaks클래스에서 break메소드를 제공한다.
  • Breaks클래스는, breakable메소드에서 처리하는 예외를 던져서 break를 구현한다.
import scala.util.control.Breaks._
breakable {
  while (true) {
    if () break
  }
}

###7.7 Variable scope

  • 변수를 선언하면 변수는 scope를 가지며 일반적으로 {}가 새로운 범위를 갖는다.
  • 같은 scope에서는 동일한 이름의 변수를 쓸 수 없다. 단, 하위 scope에 상위 scope에 이미 선언된 변수를 선언할 수 있음(java에선 안됨).
  • repl 에서 중복 선언이 가능한 것도 이런 특성에 기인함. repl에선 매 문장마다 nested scope를 만들기 때문이라 함
val a = 1;
{
  val a = 2;
  {
    // ...
  }
}
  • 외부의 변수와 같은 이름인 내부변수를 shadow라고 부른다.
  • 변수는 새 이름으로 의미있게!

7.8 Refactoring imperative-style code

  • imperative 코드를 functional하게 바꿔보자.
// imperative style
def printMultiTable() {
  var i = 1
  while (i <= 10) {
    var j = 1
    while (j <= 10) {
      val prod = (i * j).toString
      var k = prod.length
      while (k < 4) {
        println(" ")
        k += 1
      }
      print(prod)
      j += 1
    }
    println()
    i += 1
  }
}
// funtinal style
def makeRowSeq(row: Int) =
  for (col <- 1 to 10) yield {
    val prod = (row * col).toString
    val padding = " " * (4 - prod.length)
    padding + prod
  }

def makeRow(row: Int) = makeRowSeq(row).mkString

def multiTable() = {
  val tableSeq =
    for (row <- 1 to 10)
    yield makeRow(row)
  tableSeq.mkString("\n")
}