7장 built in control structures - codeport/scala GitHub Wiki
- 스칼라의 제어구문(syntax) :
if,while,for,try,match, 함수호출 - Scala의 대부분의 제어 구문은 값을 반환함. <- functional language들이 이렇게 동작한다고 함.
- Java의 ternary operator인 ? 의 경우, Scala의
if로 대체 가능함. 이는if나else블럭의 마지막 평가값이 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 != "")
while과do-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도 결과값을 가지는 표현식이고 기술적으로는throw는Nothing타입이 된다. 어떤 타입도 될 수 있으며 이 반환값을 사용할 수는 없다. --> 이건 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
- 스칼라에는
break와continue가 없다. 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")
}