Scala

[Scala] Programming in Scala - 7장. 내장 제어 구문(1)

아이캔두이 2023. 4. 14. 23:13
728x90
반응형

 

오랜만에 스칼라 학습 시간입니다. 오늘은 스칼라의 내장 제어 구문에 대해 알아보도록 하겠습니다.

스칼라가 제공하는 내장 제어 구문은 if, while, for, try, match, 함수 호출밖에 없습니다.

그리고 스칼라의 내장 제어 구문은 대부분 반환 값이 있습니다.

 


 

1. Intro

자바와 같은 명령어 언어에서는 삼항 연산자와 if 문이 별개의 문법으로 존재합니다.

*삼항 연산자 : 반환 값을 지닌 if문 입니다.(ex. 조건식 ? 반환값1 : 반환값2)

 

하지만, 스칼라의 if문은 기본적으로 반환 값을 가지고 있습니다.

그 외에 for, try, match 구문도 반환 값을 가지고 있습니다. 오직 while 내장 제어 구문만이 반환값을 가지고 있지 않습니다.

 

내장 제어 구문이 반환 값이 없는 명령어 언어에서는 동일한 동작을 위해 임시 변수를 선언해야 합니다.

임시 변수는 코드를 더 길게 만들고 버그의 원인이 되기도 합니다.

 

 


2. if 표현식

스칼라의 if는 다른 언어와 동일하게 동작합니다.

각 스타일로 작성한 코드의 예를 확인해 봅시다.

 

<명령형 스타일>

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

 

<선언형 스타일>

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

 

 

▶ 장점

  • 코드가 간결합니다.
  • var가 아닌 val을 사용할 수 있습니다.
    • 동일성 추론에 유리합니다.

 

 


3. while 루프

스칼라의 while는 다른 언어와 동일하게 동작합니다.

명령형 스타일로 작성한 코드의 예는 다음과 같습니다.

 

def gcd(x: Long, y: Long): Long = {
    var a = x
    var b = y
    while (a != 0) {
        val temp = a
        a = b % a
        b = temp
    }
    b // return 값이 b가 됩니다.
}

 

루프의 반환 타입은 Unit 입니다.

스칼라의 Unit 타입은 자바의 void와 다르게 0값을 가집니다.

 

 

3.1 Unit

결과 값이 있으면 함수, 없으면 프로시저라고 합니다.

Unit 타입은 () 값이 있기는 하지만 쓸모 없기 때문에 프로시저라 합니다.

 

<선언형 스타일>

var line = ""
while((line = readLine()) != "") // () != ""
    println("Hello World!")

 

스칼라는 Unit 타입과 문자열을 !=를 사용해 비교한 결과는 항상 참이라 정의합니다.

스칼라의 할당 결과는 항상 Unit 타입입니다. var line = "" 과 같은 부분 말이지요.

line = readLine() 의 결과는 항상 ()이기 때문에, 빈 문자열과 같을 수 없습니다.

 

  • ""는 String 입니다.
  • readLine()의 반환값은 String 입니다.
  • line = readLine()의 반환값이 Unit 입니다.

※ 현재 readLine은 Deprecated 되었습니다. StfIn.readLine()을 사용하시면 됩니다.

 

 

 

3.2 이게 함수형이다.

<선언형 스타일>

def gcd(x: Long, y: Long): Long =
    if (y == 0) x else gcd (y, x % y)

 

while 루프는 반환값이 없기 때문에 I/O를 수행하거나 var 변수를 갱신해야 합니다. 이는 명령형 스타일입니다.

하지만 var 변수와 루프를 사용하지 않고 문제를 해결하는 것이 함수형 프로그래밍입니다.

 

※ println() 은 I/O를 수행하기 때문에 부수효과(side-effect)가 있는 함수입니다.

 

 


4. for 표현식

스칼라의 for 표현식은 반복 처리를 위한 만능 칼입니다.

(스칼라의 for문은 표현식입니다!)

 

 

4.1 컬렉션 이터레이터

<비추천 방법>

for (i <- 0 to files.length - 1)
    println(files(i))

위와 같은 방법은 컬렉션에 직접 이터레이션 할 수 있기 때문에 스칼라에서는 좋지 않습니다.

인덱스를 다루는 방식은 오류가 쉽게 발생합니다.

 

그렇다면 for문에서 range를 사용하려면 어떻게 해야 할까요?
아래와 같이 to와 until을 사용할 수 있습니다.

for (i <- 1 to 4)    // 1, 2, 3, 4 가 출력된다. 마지막 값을 포함한다.
    println(i)
 
for (i <- 1 until 4) // 1, 2, 3 이 출력된다. 마지막 값을 포함하지 않는다.
    println(i)​

 

<추천 방법>

val files = (new java.io.File(".")).listFiles // 현재 디렉토리 "." 에 대한 파일을 생성한다. files 는 Array[file] 타입이다.
for (file <- files)                           // file 은 val 이다.
    println(file)                             // file.toString() 메서드가 실행된다.

위와 같이 선언형 스타일로 사용할 수 있습니다.

 

 

4.2 필터링

<비추천 방법>

for (file <- files)                      // 현 디렉토리에 있는 파일들 중
    if (file.getName.endWith(".scala"))  // 확장자가 .scala 인 파일들만
        println(file)                    // 출력해라

위의 방식은 자바에서 컬렉션의 특정 원소만 이터레이션할 때 사용하는 방법입니다.

스칼라에서는 전체 컬렉션 중 일부만 사용하고 싶을 때는 필터를 사용합니다.

스칼라의 for는 표현식이기 때문에 값을 반환합니다. for문의 결과 값은 타입이 정해지는 컬렉션입니다.

 

<추천 방법>

for (
  file <- files
  if file.getName.endWith(".scala")
) {
  println(number)
}

※ 사실은 이 방법도 좋은 방법은 아닙니다. 아직 컬렉션의 Map, Filter 기능들을 배우지 않았기 때문에 소개한 것입니다. 컬렉션에 대한 자세한 부분은 추후에 더 포스팅 하도록 하겠습니다.

 

 

4.3 중첩 이터레이션

이터레이션을 중첩하여 사용할 수도 있습니다.

 

import java.io.File
 
def fileLines(file: java.io.File) = scala.io.Source.fromFile(file).getLines()toArray
 
def grep(pattern: String) = {
  for (
    file <- files
    if file.getName.equals(".scala"); // 만약 for() 가 아닌, for{} 를 사용하면 ; 를 생략해도 된다.
    line <- fileLines(file)
    if line.trim.matches(pattern)
  ) {
    println(s"$file : ${line.trim}")
  }
}

 

 

4.4 for 중 변수에 바인딩하기

def grep(pattern: String) = {
  for {
    file <- files
    if file.getName.equals(".scala")
    line <- fileLines(file)
    trimmed = line.trim               // val 처럼 동작한다.
    if trimmed.matches(pattern)
  } {
    println(s"$file : $trimmed")
  }
}

4.3 에서의 예제를 보면 line.trim이 중복하여 사용됩니다. 이를 개선하여 변수로 선언한 후 중복 계산을 방지하도록 할 수 있습니다.

for 중 바인딩 되는 변수는 val을 사용하지 않아도 val 변수처럼 사용됩니다.

 

 

 

4.5 새로운 컬렉션 만들어내기

이터레이션의 매 반복 단계의 결과를 저장하기 위한 값을 만들 수도 있습니다.

def scalaFiles =                            // Array[File]
    for {
        file <- files
        if file.getName.endWith(".scala")
    } yield file

 

for 표현식에 대한 내용도 차차 배워 보도록 합시다.

 

내용이 너무 길어지는 관계로 다음 포스팅에서 이어서 배워보도록 합시다 :)

 

[Scala] Programming in Scala - 7장. 내장 제어 구문(2)

지난 포스팅에 이어 내장 제어구문에 대해 이어서 학습해 보도록 하겠습니다. 이번 포스팅에서는 try표현식을 학습할 예정이며, if/while/for에 대해서는 아래 글을 참고해 주세요 :) [Scala] Programming

icandhee.com

 

 


[참고 도서]

  • Programming in Scala

 

728x90
반응형