-
Notifications
You must be signed in to change notification settings - Fork 36
8장 functions and closures
kingzo edited this page Jun 9, 2015
·
14 revisions
- 프로그램이 커지면 코드를 함수로 분리한다.
- scala는 자바의 method 외에 function 내부의 function, function 리터럴, function value도 제공함.
- 객체의 멤버로 정의된 함수를 메서드라고 부른다.
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)
}
}
- 프로그램을 작은 함수들로 나눈다.
- 복잡한 작업을 유연하게 구성할 수 있다는 장점이 있다.
- 단점은 이러한 헬퍼함수들의 이름이 네임스페이스를 더렵힐 수 있다는 것이다.
- 헬퍼함수는 하나의 개별 단위로는 의미가 없기 때문에 감추는것이 바람직 하다.
- 함수내에 함수를 정의할 수 있고 이를
local function
이라고 부른다.
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)
}
}
- local function은 enclosing function 의 parameter에도 접근할 수 있음.
- 스칼라에는 first-class 함수가 있다.함수를 정의하고 호출하고 이름을 주지 않고 리터럴로 작성해서 값으로 전달할 수 있다.
- function literal은 클래스로 컴파일되고 런타임시에 function value가 된다. function literal 과 function value 의 차이는 소스코드에 존재하는냐, 런타임시에 객체로 존재하느냐의 차이이다.
(x: Int) => x + 1
- 함수값은 객체이므로 변수에 정의할 수 있다.
scala> var increase = (x: Int) => x + 1
increase: (Int) => Int = <function1>
scala> increase(10)
res0: Int = 11
- function value는
scala.FunctoinN
trait를 확장한 클래스의 인스턴스임. 이 trait는apply
메서드를 이용해 펑션을 호출함.
- 다양한 function literal의 축약형을 보여줌
someNumbers.filter((x: Int) => x > 0 )
someNumbers.filter((x) => x > 0 ) // paramter의 type 생략 - target typing
someNumbers.filter( x => x > 0 ) // parameter의 괄호 생략
- target typing: 여기서
x
가 integer라는 것을 표현식의 대상이되는 타입으로 알 수 있다.(여기서는someNumbers.filter()
의 인자)
- 파라미터를 생략하고
_
로 대치할 수 있다. 즉,_ > 0
은x => x > 0
과 같다. -
val f = _ + _
처럼 언더스코어를 여러개 써주려면 파라미터가 한번씩만 나와야 한다. -
_
를 parameter placeholder로 사용하려면 컴파일러가 타입을 인지할 수 있어야 함
val f= (_: Int) + (_: Int)
- 매개변수 목록 전체를
_
로 치환할 수도 있음(함수명과 언더스코어 사이에 공백이 있어야 한다.)
someNumbers.foreach(x => println(x))
someNumbers.foreach(println _) // _ as placeholder for entire parameter list -> partially applied function
- partially applied function: function이 요구하는 모든 인자를 전달하지 않고 일부 혹은 아무 인자도 전달하지 않은 표현식
scala> def sum(a: Int, b: Int, c: Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int
scala> val a = sum _
a: (Int, Int, Int) => Int = <function3>
scala> a(1, 2, 3)
res11: Int = 6
scala> a.apply(1, 2, 3)
res12: Int = 6
-
_
가 전체 parameter list를 대체하는 기능을 이용하면def
를 _function value_로 변형하는 방법도 가능함. -
_
가 전체 parameter list가 아닌 일부 parameter만 치환하게 할 수도 있음
val b = sum(1, _: Int, 3)
b(2) == sum(1,2,3) // b.apply() function call sum(1,2,3)
- 전체 parameter list를 대체하는
_
자체도 생략 가능
someNumbers.foreach(println _)
someNumbers.foreach(println) //function이 들어가야만 하는 위치에서만 가능한 형태
-
(x: Int) => x + more
에서more
는 함수리터럴이 주지 않는 값이므로 _free variable_라고 부르고x
는 함수의 컨텍스트내에 있으므로 _bound variable_라고 부른다. - 이렇게 _free variable_이 포함된 _function value_를 _closure_라고 부름.
- _free variable_이 포함되지 않은 _function literal_은 closed term
- _free variable_이 포함된 _function literal_은 open term
- _open term_인 _function literal_이 _free variable_을 'capture'해서 만들어진 _function value_가 closure
- 클로저가 참조한 변수의 값이 바뀔경우 참조하는 클로저의 값도 변경된다.
- cf) java의 inner class는 final 변수만을 받아들이므로 값이 바뀔 수가 없음
- _closure_가 바라보는 _free variable_의 인스턴스는 _closure_가 생성된 시점의 인스턴스임.
-
Repeated parameters
- java의 가변인자( var args )와 동일한 의미.
- 마지막 파라미터가 여러개일수 있다는 의미로 타입뒤에
*
를 써준다.def echo(args: String*) = body
- _repeated parameter_의 타입은 선언된 타입의 배열이 되지만 배열로 파라미터를 전달할 수는 없다. 배열로 전달하려면
echo(arr: _*)
처럼_*
를 사용해야 한다.
- Named arguments
- Named arguments는 순서에 상관없이 이름으로 파라미터를 전달할 수 있다.
method(a= 100, b = 10)
- Named arguments는 순서에 상관없이 이름으로 파라미터를 전달할 수 있다.
- 파라미터 기본값
- 함수 파라미터에 기본값을 지정해서 호출시 생략할 수 있다.
def a(b=10)
보통 named arguments와 함께 쓰인다.
- 함수 파라미터에 기본값을 지정해서 호출시 생략할 수 있다.
- tail recursion : 재귀함수의 마지막 호출 대상이 자기 자신일 경우. compiler가 꼬리재귀를 인지하여 최적화 수행.
- tail recursion의 경우엔 각 호출마다 새로운 stack이 쌓이지 않고, 단일 frame내에서 처리됨. 최적화를 끄고자 할 때는
-g:notailcalls
옵션을scalac
나scala
에 줌 - JVM 때문에 scala의 tail recursion도 제약을 받음. 무조건 자기 자신을 직접 호출하는 경우에만 최적화가 가능함.