06 | 값에 의한 전달
R에서는 모든 것이 객체다. 또, 객체는 함수 호출 시 일반적으로 값으로 전달된다. 이를 변수에 대한 참조Reference에 의한 방식에 대비해 값에 의한 전달Pass By Value이라고 한다. 값으로 전달된다는 말은 객체가 복사되어 함수로 전달된다는 의미다.
여기에는 예외(환경Environment, 심볼Symbol, 스페셜Special, 빌트인Builtin 등)도 있다. 그러나 이들에 대한 내용은 이 책의 범위를 벗어나므로 관심 있는 독자는 참고자료 [2]를 확인하기 바란다. 이 책에서 다루는 범위 안에서는 객체가 값으로 넘어간다고 간주해도 좋다.
값으로 데이터가 넘어가는 예를 살펴보자. 예를 들어, 다음과 같이 데이터 프레임을 함수에 인자로 주었을 때 함수 내부에서 수행한 변경은 원래 객체에 반영되지 않는다. 왜냐하면 함수 f( ) 안에서 df2란 f( )를 호출한 쪽에서 넘긴 객체를 가리키는 참조가 아니라 넘겨받은 df를 복사한 새로운 데이터 프레임이기 때문이다.
> f <- function(df2) { + df2$a <- c(1, 2, 3) + } > df <- data.frame(a=c(4, 5, 6)) > f(df) > df a 1 4 2 5 3 6
만약 인자로 받은 df를 수정하고 이를 함수를 호출한 쪽에 반영하려면 함수 f에서 수정된 값을 반환하고 함수를 호출한 쪽에서 원래 변수에 할당해야 한다.
> f <- function(df) { + df$a <- c(1, 2, 3) + return(df) + } > df <- data.frame(a=c(4, 5, 6)) > df <- f(df) # df의 값을 덮어쓴다. > df a 1 1 2 2 3 3
결과적으로 특별한 객체(예를 들면, 네트워크 접속connection이나 파일 입출력 등)를 제외하고는 객체의 상태가 함수에 의해 직접 수정되지 않는다. 이런 이유로 어떤 함수를 호출하더라도 인자로 넘긴 객체가 수정되지 않음을 보장받는다. 이 주제에 더 관심 있는 독자는 <Mutable objects in R>[3]을 참고하기 바란다.
여기까지 읽은 독자는 ‘그렇다면 객체가 함수에 넘겨질 때마다 새로운 객체를 만들어야 하니 메모리 사용량이 너무 많아지지 않을까?’ 또는 ‘매번 새로운 객체를 만들려면 속도가 느려지지 않을까?’ 의심을 품게 될 것이다. 실제로 매번 복사가 일어나야 한다면 이러한 부담이 있는 것이 사실이다.
그러나 R을 포함한 현대적 언어는 함수 호출 시 곧바로 객체를 복사하기보다는 객체의 값을 바꿀 필요가 있을 때만 복사를 수행하는 ‘쓰기 시 복사(Copy On Write)’ 기법을 사용한다. 쓰기 시 복사에서는 데이터 프레임 등의 객체를 어떤 함수에 넘겼을 때 해당 함수가 객체 내부 값을 바꾸는 시점에 이르러서야 값이 수정된 새로운 복사본을 만든다. 따라서 읽기 작업만 수행할 때는 복사에 따른 오버헤드를 염려하지 않아도 된다.