7. 내장 제어 구문

dev.jhjhj·2021년 8월 31일
0

Scala

목록 보기
6/10

if 표현식

명령형 언어 스타일

var filename = "default.txt"
  if (!args.isEmpty)
    filename = args(0)

if 표현식에서 조건 결과가 참일 여부에 따라 분기 중 하나를 실행

스칼라 스타일

val filename = if (!args.isEmpty) args(0) else "default.txt"

스칼라 if 표현식은 결과로 값을 내놓는다.

var 대신 val로 선언하면 좋은 점

  • 변수가 사용되는 범위 내에서 값의 변화 여부를 판단하기 위해 모든 코드 살펴볼 필요 X
  • 동일성 추론 : 해당 표현식에 사이드 이펙트가 존재하지 않기 때문에 값과 동일시하여 사용 가능
// 예시
println(filename)println(if (!args.isEmpty) args(0) else "default.txt")

while 루프

  • while과 do-while은 표현식이 아니라, loop라고 부름
  • loop의 결과 타입은 Unit이다. 그래서 어느정도의 side effect가 발생할 수 있음
  • Unit 타입에는 Unit value 밖에 없고, 이 값을 빈 괄호()로 표기
// 최대 공약수 구하기
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
}

// 결과
scala> gcdLoop(10, 20)
val res0: Long = 10
 
scala> gcdLoop(4, 6)
val res1: Long = 2
  • ()은 값이 존재한다는 점에서 자바의 void와 다른 의미이다.
scala> def greet() = { println("hi") }
def greet(): Unit
 
scala> () == greet()
hi
val res10: Boolean = true

greet는 결과 타입이 Unit인 프로시저라고 한다.
(결과 값이 있으면 함수, 결과 값이 없으면 프로시저라고 부른다.)

  • ()은 당연히 ()를 반환할 것이고, greet() 또한 Unit 함수이기 때문에 ()를 반환하므로 서로 동일하다.

  • 다음과 같이 일반적인 형태로 while 루프를 스칼라에서 사용하면 문제가 생긴다.

scala> while ((line = scala.io.StdIn.readLine()) != "")
     |   println("Read: " + line)
                                                 ^
       warning: comparing values of types Unit and String using `!=` will always yield true
Read: ddd
Read: ddd
Read:
Read:
  • 위의 readLine 함수는 Unit 타입으로 정의되어 있다.
    => 결과 값은 ()일 것이고 해당 값은 빈 문자열인 ""와 같을 수 없을 것이다.

  • 자바에서는 위와 관련된 I/O 함수가 실행되었을 때 line 이라는 변수에 값이 할당이 되겠지만 스칼라는 해당 함수가 Unit 타입이면 항상 ()를 반환한다.

이를 해결 하기 위해서...

scala> while({line = scala.io.StdIn.readLine(); line != ""})
     |   println("Read: " + line)

for 표현식

여러가지 방법으로 조합해서 다양한 반복 작업을 처리할 수 있다.
연속적인 정수에 대해 작업을 처리하는 것도 간략하게 가능하며, 여러 종류 컬렉션을 대상으로 조건에 맞는 요소를 가려내고 새로운 컬렉션을 만들 수 있다.

컬렉션 이터레이션

val filesHere = (new java.io.File(".")).listFiles
val filesHere: Array[java.io.File] = Array(.\fsc, .\fsc.bat, .\scala, .\scala.bat, .\scalac, .\scalac.bat, .\scaladoc, .\scaladoc.bat, .\scalap, .\scalap.bat)
 
 scala> for (file <- filesHere) {
     |   println(file)
     | }
.\fsc
.\fsc.bat
.\scala
.\scala.bat
.\scalac
.\scalac.bat
.\scaladoc
.\scaladoc.bat
.\scalap
.\scalap.bat
  • 자바 API를 사용해서 디렉토리 내의 모든 파일을 출력하는 코드
  • file <- filesHere 은 제너레이터(generator)라고 부른다. 각 단계마다 file 이라는 val 원소 값으로 초기화 한다.
  • 컴파일러는 files가 Array[File] 타입이기 때문에 file의 타입도 File을 추론할 수 있다.

필터링

컬렉션 모든 원소 접근 않고 일부만 사용하고 싶은 경우, for 문에 if 사용 가능하다.

var filesHere = (new java.io.File(".")).listFiles
var filesHere: Array[java.io.File] = Array(.\fsc, .\fsc.bat, .\scala, .\scala.bat, .\scalac, .\scalac.bat, .\scaladoc, .\scaladoc.bat, .\scalap, .\scalap.bat)
 
 
 for (file <- filesHere if file.getName.endsWith(".bat")) {
     |   println(file)
     | }
.\fsc.bat
.\scala.bat
.\scalac.bat
.\scaladoc.bat
.\scalap.bat
  • 위 예제는 if 문을 활용해서 조건 별로 file들을 대상으로 명령 수행 가능

    for 표현식은 사용하기 위한 값을 결과로 내놓는다.

try 표현식

Scala도 try 를 이용해서 예외를 처리하거나 종료할 수 있다.

예외 발생시키기

스칼라에서 예외를 발생시키는 방법은 throw 키워드를 사용해 생성한 예외를 던지면 된다.

스칼라에서는 throw가 결과 타입이 있는 표현식이다.

val half =
	if (n%2==0)
		n/2
	else
		throw new RuntimeException("n must be even")

n이 짝수이면, half에는 n의 반에 해당하는 값이 할당
n이 짝수가 아니면, 어떤 값으로도 초기화하지 않고 그 전에 예외를 발생
따라서, else 부분에서 어떤 예외를 던지더라도 이를 원하는 타입으로 다뤄도 문제 없다.
기술적으로 예외는 Nothing이라는 타입을 갖는다.

  • Nothing 타입은 모든 타입의 서브타입, 값을 도출해내는 과정에서 예외가 있더라도 타입 에러에 관한 문제는 없다.

발생한 예외 잡기

try-catch 구문 사용, 다른 언어와 마찬가지로 예외가 발생하면 catch 구분에서 예외처리 시도
throws 선언하지 않고, 예외 메소드만 명시 가능 => @throws 어노테이션 명시 가능

import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

try {
	val f = new FileReader("input.txt") // 파일을사용하고닫는다.
} catch {
	case ex: FileNotFoundException => // 파일을못찾는경우 처리
	case ex: IOException => // 그 밖의 10 오류 처리
}

finally 절

항상 실행하고 싶은 코드가 있을 경우에 finally로 감싸서 사용

import java.io.FileReader

val file = new FileReader("input.txt")
try {
  // 파일을사용한다.
} finally {
	file.close() // 파일을확실히 닫는다.
}

값 만들어내기

try-catch-finally의 결과는 값이다.
예외가 발생하지 않을 경우 -> 전체 결과는 try 절의 수행결과가 전체 결과
예외가 발생하여 예외를 잡은 경우 -> catch절의 수행 결과가 전체 결과
finally 절에 결과 값이 있다면 버려진다. finally 절은 파일을 닫는 등 정리 작업을 수행하므로, try 절, catch 절의 결과를 수정하지 않아야한다.

import java.net.URL
import java.net.MaiformedURLException

def urlFor(path: String) = 
  try {
	new URL(path)
  } catch {
	case e: MaiformedURLException =>
		new URL("http://www.scala-lang.org")
  }

match 표현식

스칼라의 match 표현식은 switch문과 유사하지만, 다음과 같은 차이점 존재

  • java의 case 문에는 enum 값이나, 정수 type의 값 또는 문자열 값만 쓸 수 있지만, 스칼라는 어떤 종류의 상수도 사용 가능
  • break 구문이 없다. 따라서 코드가 짧고 실수로 break을 넣지 않았을 때의 발생하는 오류를 막을 수 있다.
  • match 표현식은 결과가 곧 값이다.
  • case _ 는 switch 문에서 default 문과 비슷한 역할을 한다.
  • _는 placeholder로 완전히 알려지지 않은 값을 표시하기 위해서 사용
val firstArg = if(!args.isEmpty)
					args(0)
				else
                  	""
 
val friend = {
    firstArg match {
        case "salt" => "paper"
        case "chips" => "salsa"
        case "eggs" => "bacon"
        case _ => "huh?"
    }
 
println(friend)
}

break, continue 문 없이 살기

break, continue는 스칼라에서 자주 쓰이는 함수형 리터럴과 어울리지 않기 때문에 많이 사용되지 않는다.
break, continue를 사용하지 않고 프로그램을 제저할 수 있다.

  • 모든 continue 문을 if로, 모든 break 문을 boolean 변수로 대체한다.
  • 재귀함수를 이용한다.
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
}

foundIt 변수가 boolean 변수의 역할을 하고 있다.
중첩 if문 내에서 continue로 인해 pass되는 코드들이 존재한다.

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)   
}
 
varl i = searcmFrom(0)
  • 스칼라 컴파일러는 위 코드에 대해서 재귀함수를 만들지 않는다.
  • 모든 재귀 호출은 꼬리 재귀 호출이기 떄문에 컴파일러는 while 루프와 비슷한 코드를 만들어내어 최적화를 한다.

변수 스코프

자바와 스칼라의 스코프 규칙이 자바와 거의 동일
한가지 차이점 -> 스칼라의 내부 스코프에서 동일한 이름의 변수를 정의해도 된다.

자바는 바깥 스코프에 존재하는 변수와 동일한 이름의 변수를 안쪽 스코프에 선언하지 못한다.
하지만, 스칼라에서는 동일한 이름의 변수를 선언하면, 안쪽 스코프에서 바깥쪽 스코프에 있는 동일한 이름의 변수가 보이지 않는 것 처럼 동작 -> 안쪽 변수가 바깥 스코프의 변수를 가렸다. (shadow)라고 표현

명령형 스타일 코드 리펙토링

// 명령형 스타일로 코딩
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) {
              print(" ")
              k += 1
            }
            print(prod)
            j += 1
          }
          println()
    	     i += 1
        }
      }
      
      
// 함수형 스타일로 코딩
def makeRowSeq(row:Int) = {
        for(col <- 1 to 10) yield {
          val prod = (row * col).toString
          val padding = " " * (4 - prod.length)
          padding + prod
        }
      }
profile
어제보다 더 나은

0개의 댓글