#title 메타 프로그래밍 [[TableOfContents]] ==== 개요 ==== R은 LISP이라는 프로그래밍 언어에서 비롯되었다. LISP은 인공지능 연구를 위해 만들어진 언어로서 프로그램을 프로그램할 수 있는 강력한 기능을 가지고 있다. 이것을 메타 프로그래밍이라고 하는데 R도 마찬가지 기능이 있다. LISP은 REPL이라는 방식으로 작동하는데 이것은 R도 마찬가지다. REPL이란 읽고(read)-평가하고(evaluate)-출력하는(print) 과정을 반복(loop)한다는 뜻이다. 예를 들어 살펴보자. 사용자가 1 + 2이라고 입력을 하면 이것은 그냥 문자열일 뿐이다. R은 이것을 먼저 R 문법에 맞춰 읽어들인다. 이 과정을 파싱(parsing)이라고 한다. 파싱을 거치면 1 + 1은 +라는 이름의 함수에 1과 2를 넘겨주어 처리하라는 명령이 된다. 여기까지가 읽는 과정이다. 그 다음에 함수에 1과 2를 넘겨 3이라는 결과를 실제로 얻어내는 것이 평가고 마지막으로 화면에 3을 보여주는 것이 출력이다. ==== substitute와 eval ==== substitute는 프로그램의 일부를 파싱만하고 평가하지 않도록 해준다. 또한 이름이 있으면 그 이름들을 원하는 내용으로 바꿀 수 있다. {{{ > substitute(x + 1) x + 1 > substitute(x + 1, list(x = 3)) 3 + 1 }}} 프로그램의 일부를 파싱만하고 평가하지 않는게 무슨 쓸모가 있을까? 두 가지 경우를 생각할 수 있다. 하나는 프로그램의 일부가 평가되는 시점을 조작하고 싶은 경우, 또 하나는 프로그램의 일부를 데이터로 전환하는 경우다. 첫 번 째 경우를 살펴보자. {{{ > code.injection <- function(x){ a <- 5 ; eval(substitute(x)) } > a <- 10 > code.injection(a + 1) 6 }}} 위의 코드를 이해하려면 먼저 범위(scope)라는 개념을 이해할 필요가 있다. R에서 모든 이름은 자기 범위를 가지고 있다. 위의 프로그램에서 a는 두 번 정의되었는 데 같은 이름처럼 보이지만 범위가 다르다. code.injection 함수 안에 있는 a는 그 함수 내부를 자기 범위로 갖는다. 이런 것을 지역 변수(local variable)이라고 부른다. code.injection 바깥에 있는 a는 모든 곳이 자기 범위가 된다. 이런 것을 전역 변수(global variable)이라고 한다. 만약 전역변수와 지역변수가 공존할 경우에는 R은 지역변수로 해석한다. code.injection(a+1)이라고 하면 a+1이 code.injection 함수 안으로 들어간다. substitute는 바꿔칠 내용을 명시해주지 않으면 현재 영역에 있는 지역변수의 내용으로 대체한다. code.injection 안에 a라는 이름이 5라는 값을 가지도록 되어있으므로 a를 5로 바꿔준다. eval은 평가를 하는 함수이다. substitute에 의해 a + 1이라는 프로그램의 일부가 5 + 1로 바뀌고 이를 평가하여 6이라는 결과가 나오는 것이다. 앞에서 파싱은 문자열을 문법에 맞춰 해독하는 것이라고 했다. deparse 함수는 거꾸로 해독된 내용을 문자열로 바꿔준다. {{{ > pretty.print <- function(x) cat( deparse( substitute(x) ), x, '\n' ) > pretty.print(sqrt(10)) sqrt(10) 3.162278 }}} 위에서 사용한 cat 함수는 뒤 따라오는 내용들을 순서대로 출력해주는 함수다. '\n'은 줄바꿈을 뜻한다. 그러니까 sqrt(10)이라는 명령을 입력하면 substitute(x)에 의해 평가가 중단되고 deparse에 의해 문자열로 변환된다. cat는 이것을 출력한다. 그 다음에는 x의 내용을 평가한 결과를 출력하고 마지막으로 줄 바꿈을 한다. 그래프를 그리거나 결과를 출력하는 함수를 만들 때 사용하면 편리하다. ==== parse ==== deparse라는 함수도 있으므로 당연히 parse라는 함수도 있다. parse는 문자열을 파싱해서 프로그램으로 바꿔준다. 이렇게 생성된 프로그램은 역시 eval 함수로 평가할 수 있다. {{{ > eval(parse(text='1+2')) [1] 3 }}} substitute와 다른 점은 substitue는 단순히 프로그램의 일부가 평가되지 않도록 하는데 반해 parse의 결과는 expression이라는 객체라는 점이 다르다. 이것은 좀 미묘한 차이이므로 일단 그렇게만 알아두자. 아래 예제는 찾아바꾸기 해주는 함수 gsub를 이용해서 %% <<- %% + 1이라는 문자열을 각각 'a <<- a + 1', 'b <<- b + 1','c <<- c + 1'로 바꿔준다음 실행하는 것이다. {{{ > a <- 3 > b <- 4 > c <- 5 > mapply(function(x) eval(parse(text=gsub('%%', x, '%% <<- %% + 1'))), + c('a','b','c')) a b c 4 5 6 > a [1] 4 }}} 프로그램을 짜다보면 비슷비슷한 프로그램을 copy & paste해서 일부만 고쳐 쓰는 경우가 많은데 parse를 이용하면 이런 작업을 자동화시킬 수가 있다. ==== 출처 ==== * http://www.remantu.com/r/tutorial/metaprg