8장 functions and closures - codeport/scala GitHub Wiki

kingori

###8.0

scala는 자바의 method 외에 function 내부의 function, function 리터럴, function value도 제공함.

###8.1

method = 객체의 멤버로 정의된 function 그 외 별 내용은 없음

###8.2

local function = function 내부에 정의된 function. 특정 function에서만 필요한 helper method들을 정리하는 용도로 적합해 보임. local function은 enclosing function 의 parameter에도 접근할 수 있음.

###8.3

scala has first-class functions: function의 선언, 호출, function literal, function value가 가능. function literal은 class로 컴파일 되며, runtime에 생성된 이 클래스의 인스턴스를 function value라고 함. function value는 scala.FunctoinN trait를 확장한 클래스의 인스턴스임. 이 trait는 apply 메서드를 이용해 펑션을 호출함.

function literal 선언 예제

increase = (x: Int) => { blah.. blah.. blah.. }

###8.4

다양한 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: 표현식의 targeted usage (argument로 전달되는 function literal을 지칭하는 듯?)이 원래의 표현식의 타입에 영향을 줄 수 있는 경우. (무슨 소린지?) 여기선 (x) => x > 0 의 타입 x가 someNumbers.filter() 에 의해 영향을 받는 다는 소린지? 하여간 쓰는 사람은 그냥 type 떼고 써도 된다는 수준으로 알면 된다고 함-_-

###8.5

공포의 _ 출현. parameter를 생략하고 _ 로 대치할 수 있음

someNumbers.filter( _ > 0 ) // placeholder _ 사용

_ 를 parameter placeholder로 사용하려면 컴파일러가 type을 인지할 수 있어야 함

val f=  (_: Int) + (_: Int)

###8.6

매개변수 목록 전체를 _ 로 치환할 수도 있음

someNumbers.foreach(x => println(x))
someNumbers.foreach(println _) // _ as placeholder for entire parameter list -> partially applied function

partially applied function: function이 요구하는 모든 인자를 전달하지 않고 일부 혹은 아무 인자도 전달하지 않은 표현식 책 예제의 val a = sum _ 이 아닌 val a = sum 을 해보니 다음과 같은 에러가 났음 (8.6 마지막 예제도 동일)

scala> val a = sum
<console>:9: error: missing arguments for method sum in object $iw;
follow this method with `_' if you want to treat it as a partially applied function   
       val a = sum
               ^                                                                  

_ 가 전체 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) //function이 들어가야만 하는 위치에서만 가능한 형태

###8.7

function literal의 variable 중

  • bound variable : 인자로 넘겨받은 variable
  • free variable : literal 자체에서 선언하지 않은 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'.

closure는 variable을 capture하지, variable의 value를 capture하지 않음. 따라서 free variable의 값이 바뀌면 이를 인지함. 또한 closure를 통해서 free variable에 해당하는 외부 변수의 값도 바뀔 수 있음. cf) java의 inner class는 final 변수만을 받아들이므로 값이 바뀔 수가 없음

책의 예제대로라면 someNumbers.foreach( (x: Int) => { sum += x } ) 라는, sum이라는 free variable이 포함된 closure를 통해 sum의 값 자체가 바뀌고 있음.

closure가 바라보는 free variable의 instance는 closure가 생성된 시점의 instance임.

###8.8

특별한 형태의 함수 호출

  • repeated parameters : java의 var args와 동일한 의미. 넘겨진 arg는 Array 타입이 됨. Array 자체를 arg로 넘기면 compiler error가 나는데, 이 때엔 echo(arr: _*) 와 같은 형태를 취하면 됨. arr 를 쪼개서 넘기라는 뜻으로 해석하면 되나?

  • named arguments: argument의 이름을 지정해서 순서를 바꿀 수 있음. 별도의 정의는 필요없는 듯? speed(dist = 100, time = 10)

  • default parameter value: 기본값 지정 가능 def a( x: Int = 10, y: Int ) = x + y

default parameter value는 인자가 1개일 때만 그냥 사용 가능하고, 다른 경우엔 named arguments 형태를 취해야 하는 듯?

scala> def a( x: Int = 10, y: Int ) = x + y
a: (x: Int, y: Int)Int
scala> a(3)
<console>:9: error: not enough arguments for method a: (x: Int, y: Int)Int.
Unspecified value parameter y.
               a(3)
               ^
scala> a(y=3)
res49: Int = 13

###8.9

tail recursion : 재귀함수의 마지막 호출 대상이 자기 자신일 경우. compiler가 tail recursion을 인지하여 최적화 수행. tail recursion의 경우엔 각 호출마다 새로운 stack이 쌓이지 않고, 단일 frame내에서 처리됨. 최적화를 끄고자 할 때는 -g:notailcalls 옵션을 scalac 나 scala에 줌 (굳이 이럴 필요가?)

JVM 때문에 scala의 tail recursion도 제약을 받음. 무조건 자기 자신을 직접 호출하는 경우에만 최적화가 가능함.


Outsider

8.1 Methods

  • 객체의 멤버함수를 메서드라고 부른다.

8.2 Local functions

  • 프로그램은 여러개의 작은 함수로 나눌 수 있는데 대신 네임스페이스 문제가 생긴다.
  • 함수를 함수의 내부에 지역변수처럼 선언할 수 있다.

8.3 First-class functions

  • 스칼라는 first-class 함수를 갖는다.
  • function literal은 클래스로 컴파일되고 런타인시에 function value가 된다.
  • function literal과 function value의 차이점은 소스코드로 존재하느냐 런타임시의 객체로 존재하느냐의 차이이다.

8.4 Short forms of function literals

  • someNumbers.filter((x) => x>0)에서 x의 타입은 적어주지 않아도 된다. 이를 target typing이라고 부른다.
  • 타입이 추론된 파라미터는 괄호를 쓰지 않아도 된다. someNumbers.filter(x => x>0)

8.5 Placeholder syntax

  • _ > 0x => x > 0과 같다.
  • val f = _ + _처럼 언더스코어를 여러개 써주려면 파라미터가 한번씩만 나와야 한다.

8.6 Partially applied functions

  • 전체 파라미터를 언더스코어(_)로 작성할 수 있다. println(_)보다는 println _가 낫다.
    • 이 경우에언더스코어는 하나의 프라미터를 위한 플레이스홀더가 아니라 전체 파라미터 리스트를 위한 플레이스 홀더이다.
  • 이방법으로 언더스코어를 쓸때 partially applied function 작성한 것이다.
  • partially applied function는 전체 파라미터를 다 제공할 필요가 없다.

8.7 Closures

  • (x: Int) => x + more에서 more를 free variable라고 부른고 x는 bound variable라고 부른다.
  • 함수리터럴에서 런타임시에 생성된 function value를 closure라고 부른다.
  • 클로저가 참조한 변수의 값이 바뀔경우 참조하는 클로저의 값도 변경된다.
  • 인스턴스가 실행될 때 클로저도 생성된다.

8.8 Special function call forms

  • Repeated parameters
    • 마지막 파라미터가 여러개일수 있다는 의미로 타입뒤에 를 써준다. `def echo(args: String) = body
    • repeated parameter의 타입은 선언된 타입의 배열이 되지만 배열로 파라미터를 전달할 수는 없다. 배열로 전달하려면 echo(arr: _*)처럼 _*를 사용해야 한다.
  • Named arguments
    • Named arguments는 순서에 상관없이 이름으로 파라미터를 전달할 수 있다. method(a= 100, b = 10)
  • 파라미터 기본값
    • 함수 파라미터에 기본값을 지정해서 호출시 생략할 수 있다. def a(b=10) 보통 named arguments와 함께 쓰인다.

8.9 Tail recursion

  • 꼬리재귀는 각 호출마사 새로운 스택프레임을 만들지 않고 하나의 프레임으로 실행된다.
  • 꼬리재귀 최적화를 하지 않으려면 -g:notailcalls옵션을 컴파일러에 사용한다.

질문

  • p. 196 박스 모르겠음.
  • p. 206의 limits of tail recursion 잘 모르겠음.
⚠️ **GitHub.com Fallback** ⚠️