하마
7k
2016-07-23 17:39:22 작성 2016-11-08 00:49:52 수정됨
0
2684

스칼라 강좌 (10) - 함수와 메소드


함수와 메소드

이번 포스트 내용은 지금까지 그리고 앞으로 나올 강좌중에서 가장 중요한 강좌라고 생각합니다.   
스칼라에서 함수/메소드는 그 만큼 중요합니다.  본문의 내용이 불분명하거나 모자르면 반드시 
다른 곳에서 보충하시길 바랍니다.


스칼라에서 함수종류 

* 객체의 멤버로 있는 함수인 메소드
* 함수안에 정의한 내포 함수
* 함수 리터럴
* 함수 값 


메소드  


기초


def를 사용해 메소드를 만들 수 있다.  
리턴 타입이 뒤에 있다.( : Int  처럼 ) 타입추론으로 인하여 생략도 가능하다.  
파라미터에서 타입이 뒤에 있다  (m : Int  처럼) 
메소드 내용을 둘러싼 중괄호가 없을 수 있다. 
중간에 = 기호가 있기도 하고 없을 수 도 있다.

def addOne(m: Int): Int = m + 1

인자가 없는 함수의 경우 호출시 괄호를 생략할 수도 있다.

def three() = 1 + 2

three()
결과 : Int = 3

three  // 괄호 생략 
결과 : Int = 3

특성 

스칼라 메소드 파라미터에서 중요한 점은 이들이 val 이라는것이다.

  

  def add(b : Byte) : Unit = {

b = 1  // 파라미터가 val 이라 불가능 !

sum += b

   }


 * return 을 명시적으로 사용하지 말라. 

   스칼라는 메소드가 한 값을 계산하는 표현식인 것 처럼 생각하는걸 권장한다.

 * 메소드가 오직 하나의 표현식만 계산하는 경우 {} 중괄호를 없앨 수 있다.

   def add(b:Byte) : Unit = sum += b


 * 메소드의 결과 타입이 Unit 인 경우는 부수 효과를 위해 사용한다는 뜻이다. 

 

 * Unit 생략하고 = 를 없애서 {} 로 메소드를 나타낼 수 있는데, 이건 프로시저 처럼 보이게 하는데 
   프로시저란 오직 부수 효과를 얻기 위해서만 사용하는 메소드를 의미한다. 


   즉 def add(b: Byte) { sum += b}  처럼 사용한다는 뜻이데, 여기서 함정이 있는게 


   def add2 () { " test string " }  이건 Unit 를 리턴하며 
   def add3 () = { " test string " }  이건 String 을 리턴한다는 것이다. 


   즉 프로시저 처럼 작성하면 리턴이 무조건 Unit 이 된다는점~ 



예제 1


 import scala.io.Source

object LongLines {

def processFile(filename : String, width : Int ) {
  val source = Source.fromFile(filename)
  for (line <- source.getLines())
  processLine(filename, width, line)
}

private def processLine(filename: String, width: Int, line: String)  {
  if (line.length > width)
  println(filename + ": " + line.trim)
}
}


두개의 메소드로 이루어져 있다.

아래함수는 private 이라 외부에 노출 안되며

위의 함수는 아무것도 없어서 public  이다.



지역 함수

특성 

함수안에 함수를 정의 할 수 있다.


예제 2 (예제 1 의 변형 ) 

 

import scala.io.Source

object LongLines {

def processFile(filename : String, width : Int ) {

   def processLine(filename: String, width: Int, line: String)  {
      if (line.length > width)
      println(filename + ": " + line.trim)
    }

    val source = Source.fromFile(filename)
    for (line <- source.getLines())
      processLine(filename, width, line)
  }

}


도우미 함수들은 프로그램의 네임스페이스를 오염 시킬 수 있기 때문에  자바에서는 주로 비공개 메소드를 이용해 이런 목적을 달성하지만 스칼라에서는 함수안에 함수를 정의해서 처리 하기도 한다. 

함수안의 함수는 그 외부 함수안에서만 접근 할 수 있다. 


예제 3 (예제 2 의 개선 ) 


import scala.io.Source

object LongLines {

  def processFile(filename : String, width : Int ) {

   def processLine(line: String)  {
     if (line.length > width)
      println(filename + ": " + line.trim)
   }

   val source = Source.fromFile(filename)
   for (line <- source.getLines())
     processLine(filename, width, line)
  }

}



내부의 함수에서 바깥 함수의 인자를 사용한다.



1급  함수

특성 

스칼라는 1급 함수 (first class function) 를 제공한다. 1급이란 말처럼 상위계층 즉 모든게 가능한.. 할당도 가능하고 , 인자로도 넘길 수 있고, 반환도 가능한 애들 말한다. 


이들은 이름 없이 리터럴로 표기해 값 처럼 주고 받을 수 있다.

함수 리터럴은 클래스로 컴파일하는데, 해당 클래스를 실행 시점에 인스턴스화하면 함수 값이 된다.  

모든 함수 값은 FunctionN 트레이트 중 하나를 확장한 클래스로부터 만든 인스턴스이다.

예를들어 Function() 은 인자가 없는 함수고, Function1 은 인자가 1개인 식이다. 

여기에는 함수 호출시 사용하는 apply 메소드가 들어 있다.


함수 리터럴  (간단 예제)  


(x: Int) => x + 1


정수 x 를 x + 1 로 매핑하는 함수이다. 

함수 값은 객체이기 때문에 원하면 변수에 저장 할 수 있다.

동시에 함수 값은 엄연히 함수이기도 하다. ( 괄호를 이용해 실행 할 수 있다는 야그) 


var increase = (x : Int) => x + 1     //  함수를 increase 변수에 저장가능!

increase(10)  // 호출 가능 !  내부적으론 apply 메소드가 사용됨. 



함수 리터럴 ( 둘 이상의 문장 예제) 


increate = (x: Int ) => {

println("...")

println("...")

x + 1

}


둘 이상의 문장이 필요하면 {} 중괄호로 감싸서 블록으로 만든다. 



함수 리터럴 ( 매개변수로 사용) 


val numbers = List (1,2,3,4,5)

numbers .foreach((x:Int) => println(x)) 


다음처럼 함수의 매개변수로 들어가서 사용된다. 

이제는 대부분의 언어에서 상식이 되버린 map, filter 함수에서도 사용된다. 


scala> numbers.map((i: Int) => i * 2)
res0: List[Int] = List(2, 4, 6, 8, 10)
scala> numbers.filter((i: Int) => i % 2 == 0)
res0: List[Int] = List(2, 4)


간단한 형태의 함수 리터럴 

scala> numbers.filter( i => i % 2 == 0)
res0: List[Int] = List(2, 4)


애초에 컬렉션에서 i 는 정수라는 사실을 알기 때문에 굳이 Int 라고 하지 않아도 된다.

타깃 타이핑이라고 하며 () 도 생략 가능하다. 


더 간단한 형태의 함수 리터럴 

scala> numbers.filter( _ > 4)
res0: List[Int] = List(5)

_ 는 함수가 호출 시 전달받은 인자로 하나씩 채워져서 계산될 것이다.  




기타 특성  

반복 파라미터 

def echo (args: String*) = for (arg <-args) println(arg)    //   매개변수에 * 를 표시함으로써 

echo("hello", "world")  // 요렇게 인자의 갯수를 자유롭게 할 수 있다. 


이름 있는 인자 

def speed(distance: Float, time: Flot) : Flot = distance / time 

speed(100,10) // 일반적 사용

speed(time = 10, distance = 100) // 이름을 함께 사용, 순서를 바꾸어서 사용해도 된다.


디폴트 값

def printTime(out: java.io.PrintStream = Console.out) = out.println("time = " + System.currentTimeMillis()) 

printTime() //  이렇게 호출하면 자동적으로 디폴트로 out 은 Console.out 이 된다. 


클로저

 ( 나중에 따로 다루겠습니다 )




def 와 val 의 차이 

def even: Int => Boolean = _ % 2 == 0
val even: Int => Boolean = _ % 2 == 0

even(10) // 둘다 이렇게 호출 가능


위에서 배운 바와 같이 함수는 이렇게 표현 가능한데요. 둘의 차이는 멀까요? 


메소드 def even 은 호출 되었을때 평가되며 새로운 Function1 객체를 매 호출시마다 생성합니다.

반면 val 은 정의 되었을때 평가됩니다. 

val test: () => Int = {  // 이미 평가됨  (리턴값이 항상 같을 경우 사용 할 수 있다) 
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1049057402
test()
// Int = -1049057402 - 동일한 값 

def test: () => Int = {   // 호출할때마다 평가가 달라짐 
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -240885810
test()
// Int = -1002157461 - 값이 달라 졌다. 

* lazy val 로 선언하면 이름에서 느껴지는것 처럼 정의 되었을대 평가되는게 아니라 처음 호출시 평가됩니다.



웹 MVC 컨트롤러에서의 예  (Play for Scala)  


스프링에서 컨트롤러 함수의 예가  다음과 같은 모습을 취하는 반면 

 @RequestMapping(method = RequestMethod.GET)
   public String printHello(ModelMap model) {
      model.addAttribute("message", "Hello Spring MVC Framework!");
      return "hello";
   }

스칼라언어 기반 Play 프레임워크 웹개발에서는 (위의 자바 예와 동일한 내용의 예가 아닙니다) 

def doSomething = Action{
                          Ok.apply(views.html.index("Hi there"))
                  }

이런 모양새로 이루어지는데 여기서 Action 이 무엇일까요? 


위의 예는 다음과 같이 풀어지게 되는데요

def doSomething: Action[AnyContent] = Action.apply({
  Ok.apply(views.html.index("Hi there"))
})

위의 apply 메소드의 정의는  apply(block: => Result) 이라 익명의 함수가 들어가게 됩니다.

인자가 없고 Result 를 리턴하는 함수를 인자로 받아서 사용합니다.


이 함수 말이죠.

{
  Ok.apply(views.html.index("Hi there"))
}

그리고  최종적으로  Action[AnyContent]  클래스의 인스턴스를 리턴합니다.

항상 같은 값을 리턴하기때문에 def 을 빼도 됩니다.  즉 메소드가 아니라 val 이어도 된다는 말.




하마블로그

1
  • 댓글 0

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