하마
7k
2016-07-05 10:48:34 작성 2016-09-03 21:05:25 수정됨
2
2798

스칼라 강좌 (7) - while 루프


while


스칼라에서 While 문은 없다로 생각하는게 좋습니다.  
개인적으론 Java 8 이상에서도 while , for 문은 없다라고 일단 생각하는게 좋지 않나 합니다. 
다른 방법  ( Stream API ,  map , flatmap,  filter, zip, fold, foreach, reduce, collect, partition, scan, groupBy 등) 을 먼저 생각하는게 좋습니다.


특징

* 스칼라의 while 은 다른 언어와 마찬가지로 동작합니다.

* if 나 for 가 표현"식" 인 반면에 while 은  "식" 이 아닙니다. 그냥 루프입니다.  즉 값을 내어 놓지 않습니다.

  예를들어 if 표현식의 경우 값을 내놓기 때문에 아래와 같이 코딩이 가능합니다.

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


예제 


// 20 번 돌겠군요.  
 
 var a = 0;
 while( a < 20 ){
     a = a + 1;
 }


// 최대 공약수 구하기
 
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

}

 

보통 while 문들 처럼  조건을 검사하고 조건이 참일때까지는 계속 반복 수행을 합니다. 

do while 루프도 있습니다.

var a = 0; do { a = a + 1; } while( a < 20 )

적어도 한번은 실행 할 수 있겠네요.


Unit 타입

루프의 결과는 그 타입이 Unit 입니다. 이 타입은 유니트 값 밖에 없으며 빈 괄호 () 로 표시합니다.

() 란 값이 존재한다는 점에서 자바의 void 와 다릅니다. 

def greet() { println ("hi"( } 를 실행하면 () 이며  var 변수에 대한 재 할당도 () 가 결과가 됩니다. 

var line = 0;

while ((line = readLine()) != "") // 작동하지 않음 !!
println ("Read: " + line)

( line = readLine() )  를 평가하면 () 으로  문자열 "" 과  != 로 비교하면 언제나 참입니다.

자바의 경우 할당받은 결과 값이겠지만,  스칼라에서 할당의 결과는 유니트 값인 () 이기 때문입니다.


따라서 


import scala.io.Source
for(line <- Source.fromFile("myfile.txt").getLines())
  println(line)

라든지 


import java.io.File
import scala.io.Source

Source.fromFile(new File("myfile.txt")).getLines.foreach { line =>
    // Do something with the line, for example print it
    println(line)
}


이런 방법을 고려해야 합니다. 사고 방식이 바뀌어야 한다는 말입니다.


이런식의 해결은 지양하고 있습니다.

while( {line = reader.readLine();  line!= null} ) { .... }


While 에서 탈출하기 

 2.8 버전부터 Breaks 가 생겼네요.

import scala.util.control.Breaks._
var largest = 0
// pass a function to the breakable method
breakable { 
    for (i<-999 to 1  by -1; j <- i to 1 by -1) {
        val product = i * j
        if (largest > product) {
            break  // BREAK!!
        }
        else if (product.toString.equals(product.toString.reverse)) {
            largest = largest max product
        }
    }
}

순수 함수형 언어에서 while

순수 함수형 언어에서는 while 의 결과가 특정 값을 내보내는게 아니기 때문에 종종 제외합니다. 그런 언어에서 "루프" 는 없습니다.하지만  스칼라에서는 존재하는데 때로는 명령형의 해법이 가독성이 더 뛰어나다고 믿기 때문입니다. 


재귀를 통한 while의 대체 

재귀와 while 은 공통점이 몇가지 있습니다.

둘다 body 를 반복한다는 점이구요. 콘디션을 점검해서 순회의 종료를 알린다는 점입니다. 


위의 최대공약수 예제를 재귀로 바꾸어 보겠습니다.


// 최대 공약수 구하기

def gcd (x : Long, y :Long) : Long = 

if (y == 0) x else gcd (y, x%y) 

while 문의 컨디션 점검하는위치가 body 내부로 들어왔다는 점하고 

함수이름을 호출함으로써 순회가 돌아진다는 점이 조금 달라졌을 뿐입니다. 


* var 사용이 없어졌습니다.


일반적으로 while 루프틑 var 변수와 마찬가지로 적게 사용하기  위해 노력할것을 권장합니다. 

 

* 다만  스칼라에서 재귀에 대한 다양한 얘기꺼리가 있습니다. 나중에 아래 문서를 번역하는것으로 대체를~~

http://blog.richdougherty.com/2009/04/tail-calls-tailrec-and-trampolines.html   참고하세요.


while vs 재귀 vs 콜렉션 (고차함수) 

위의 3가지에 관한 글을 링크해 봅니다. 스택오버플로우에 관련 질문/답변이  많네요. 


http://www.scala-lang.org/old/node/8113.html

http://alvinalexander.com/scala/scala-recursion-examples-recursive-programming

http://stackoverflow.com/questions/12496959/summing-values-in-a-list

http://stackoverflow.com/questions/18674743/is-there-any-advantage-to-avoiding-while-loops-in-scala

http://stackoverflow.com/questions/15138624/scala-recursion-vs-loop-performance-and-runtime-considerations

http://stackoverflow.com/questions/18645936/is-recursion-in-scala-very-necessary






이 시리즈는 스칼라언어의 창시자인 마틴 오더스키가 직접 저술한 Programming in Scala 2판을 참고로 하여 정리되고 있습니다.  잘못된 점이 있으면 지적해주시면 바로 수정하겠습니다.



1
  • 댓글 2

  • zepinos
    20k
    2016-07-05 18:08:00

    좋은 글 고맙습니다.


    그런데, 제일 처음 나오는 for/while 등은 없다고 생각하면 좋다고 하는 이유가 궁금합니다. 저는 stream 생성 비용, 특히 parallelStream 생성 비용 그리고 foreach() 의 break 을 이용한 강제 종료 불가 등...기존의 일반적인 loop 가 적은 수의 반복에서는 더 이익이라고 생각합니다만(덤으로 이해의 편리성, 함수형은 아무래도 이해가 어렵다는 단점이)...어떤 점이 더 나은 건지 궁금합니다.

  • 하마
    7k
    2016-07-05 19:55:18

    무조건 for, while 을 쓰지 마라라고 작성하지 않았는데  혹 오해한건 아닌지 모르겠습니다.

    1. 폴리글랏 연계성
    2. 가독성 / 개발 편의성   
    3. 부수효과 제거 / 오류 확율 낮춤
    4. 공짜성능 / 개선의 여지가 많음

    위의 측면에서 그렇게 언급된 것이구요. 물론 기호 및 상황에 따라서 달라 질 수 있습니다.

    Stream API
    LINQ
    STL algorithm
    컬렉션의 고차함수 사용

    위 기술들의  장점은  많이 언급되고 있으므로 여기 적을 필요는 없을거 같구요.
    물론 자바 8 Stream API 의 문제점을 논하는 포스트도 꽤 있는 것으로 압니다만 고차함수 사용의 파도는 위에 언급된 장점들 덕분에 앞으로 꾸준할 것이기 때문에  이런 기술들의 '장점을 취하고 버릇을 들여보자' 라는 '장려' 성 멘트라고 생각해 주시면 될 거 같습니다.


    ps.

    forEach 는 애초에 전체요소를 순회하라고  만든것이기에 break 를 못쓰는게 아쉽다면 다른 메소드를 검토해야하며 마땅한게 없으면 for 문을 쓰면 될거 같습니다.

    그리고  병렬스트림도 항상 좋은결과를 보인다는 보장을 못하기 때문에 효율성에 대한 판단 여하에 따라 사용여부를 결정하면 될거 같습니다.

  • 로그인을 하시면 댓글을 등록할 수 있습니다.