본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
Read-Eval-Print Loop; REPL
사용자가 입력한 식을 읽고, 평가하고, 결과를 출력하는 일을 반복하는 대화형 프로그래밍 환경

REPL(Read-Eval-Print Loop)은 사용자가 코드 한 줄이나 식 하나를 입력하면 곧바로 실행해 결과를 보여 주고 다음 입력을 기다리는 일을 되풀이하는 대화형 프로그래밍 환경이다. 대화형 최상위(interactive toplevel), 언어 셸(language shell), 대화형 인터프리터라고도 한다. 파일 전체를 컴파일해 실행하는 방식과 달리 프로그램을 조각조각 실행하며, 앞에서 정의한 변수와 함수가 세션이 끝날 때까지 남아 있다.

이름은 LISP의 세 기본 함수에서 왔다. read가 입력을 읽어 자료 구조로 바꾸고, eval이 그것을 평가하고, print가 결과를 출력한 뒤, 다시 read로 돌아가는 고리(loop)를 이룬다. 오늘날 Python, Node.js, Ruby, Java(jshell), Haskell(GHCi), Clojure 등 대부분의 언어가 REPL을 제공하며, 학습, 실험, 디버깅, 데이터 분석에 널리 쓰인다. 운영체제의 셸과 헷갈리지 않도록 프로그래밍 언어의 대화형 환경을 특히 REPL이라 부르는 경우가 많지만, 엄밀히 보면 명령어를 읽고 실행하고 결과를 보여 주는 운영체제 셸도 REPL의 한 형태다.

REPL의 네 단계는 다음과 같다.

  • Read: 입력을 받아 구문 분석한다. LISP에서는 (+ 1 2 3)을 읽으면 원소 네 개짜리 리스트가 된다. 입력이 아직 끝나지 않았으면(괄호가 닫히지 않았거나 블록이 이어지면) 실행하지 않고 다음 줄을 더 받는다.
  • Eval: 구문 분석한 결과를 현재 환경에서 평가한다. 위 리스트는 함수 +를 인자 1, 2, 3으로 호출하는 것으로 평가되어 6이 된다.
  • Print: 평가한 값을 사람이 읽을 수 있는 형태로 출력한다. 복잡한 값은 보기 좋게 정리해(pretty-print) 보여 준다.
  • Loop: 다시 입력을 기다린다. 사용자가 끝내라고 할 때까지, 대개 파일 끝(EOF) 신호인 Ctrl+D(윈도우에서는 Ctrl+Z 후 Enter)나 exit 같은 명령을 받을 때까지 무한히 반복한다.

Common Lisp에서 가장 짧은 REPL은 이 네 단계를 그대로 적은 한 줄이다.

(loop (print (eval (read))))

평가값과 표준 출력

편집 원본 편집

REPL이 보여 주는 결과는 평가값이지 프로그램의 표준 출력이 아니다. 둘은 동시에 나타날 수 있다. Ruby의 irb에서 3.times { |n| puts n }를 입력하면 puts가 0, 1, 2를 표준 출력에 찍고, 식 전체의 평가값인 3이 => 뒤에 표시된다.

$ irb
irb(main):001> 10 + 20
=> 30
irb(main):002> 3.times { |n| puts n }
0
1
2
=> 3
irb(main):003> name = "wiki"
=> "wiki"
irb(main):004> puts "hello, #{name}"
hello, wiki
=> nil

마지막 예에서 puts는 문자열을 출력하지만 그 자체의 반환값은 nil이다. 식이 아니라 값을 돌려주지 않는 문장이면 평가값을 표시하지 않는 REPL도 있고, IPython처럼 줄 끝에 세미콜론을 붙이면 결과 표시를 생략하는 REPL도 있다.

REPL은 입력마다 새 환경을 만들지 않고 하나의 실행 환경을 세션 내내 공유한다. 앞 예에서 3번 줄에 정의한 name을 4번 줄에서 쓸 수 있는 것도 이 때문이다. 이를 이용하면 소스 파일을 불러온 뒤(irb -r ./app.rb, python -i app.py) 파일은 고치지 않고 그 안의 함수를 이것저것 불러 보며 디버깅할 수 있다. 반대로 세션이 길어지면 무엇이 정의되어 있는지 기억하기 어려워지고, 파일로 옮겼을 때 동작이 달라지는 원인이 되기도 한다.

구현 측면에서는 입력 단위로 구문 분석을 하되 실행 상태는 누적해야 하므로, 일반적인 한 번 컴파일하고 끝나는 구조와 달리 파서와 실행기를 분리해 설계해야 한다.

많은 REPL이 언어 명세와 별개로 REPL에서만 쓰는 특수 변수와 명령을 둔다.

REPL 직전 결과 특수 명령 예
Python _[1] help(), 3.13부터 exit, quit, help를 괄호 없이 입력
Node.js _, 직전 오류 _error .editor, .load, .save, .break, .help[2]
Common Lisp *, **, ***(직전, 그 전, 그 전전 결과) 구현마다 다른 디버거 명령
Ruby irb _ ls, show_source, help
GHCi it :t(타입 보기), :r(다시 불러오기), :i(정보)
jshell $1, $2처럼 자동 이름이 붙은 변수 /vars, /methods, /edit

그 밖에 입력 기록과 역방향 검색(Ctrl+R), 탭 자동 완성, 구문 강조, 여러 줄 편집, 평가값 미리 보기를 제공하는 구현이 많다. Node.js REPL은 최상위 await를 지원한다.[2] Python 3.13은 PyPy의 코드를 바탕으로 한 새 대화형 인터프리터를 기본으로 넣어 여러 줄 편집, 색상 출력, F1 도움말, F2 기록 탐색, F3 붙여넣기 모드를 추가했다.[3]

Common Lisp 계열 REPL은 오류가 나면 최상위로 돌아가지 않고 오류가 난 문맥에서 한 단계 깊은 REPL(디버거)을 열어, 호출 스택을 살피고 함수를 고쳐 다시 컴파일한 뒤 실행을 이어 가게 해 준다. 또한 print가 read로 다시 읽을 수 있는 형식으로 출력하는 것을 원칙으로 하며, 소켓처럼 다시 읽을 수 없는 객체는 #<...> 같은 별도 표기로 보여 준다. Python의 <__main__.Foo object at 0x...>도 같은 역할이다.

언어 기본 REPL 비고
Python python IPython, bpython, ptpython 같은 확장 REPL이 있다. 프롬프트는 >>>와 이어지는 줄의 ...
JavaScript node, deno, 브라우저 개발자 도구 콘솔 Node.js는 node:repl 모듈로 자체 REPL을 만들 수 있다
Ruby irb 대안으로 Pry. Rails의 rails console도 irb 기반
Java jshell JDK 9에서 추가(JEP 222)[4]. 세미콜론과 클래스 선언 없이 문장과 식을 바로 입력
Kotlin, Scala kotlinc, scala Scala는 초기부터 REPL을 기본 제공
C#, F# csi, dotnet fsi F# Interactive는 .NET SDK에 포함
Haskell GHCi 식의 타입을 바로 확인하는 용도로 많이 쓴다
OCaml, Standard ML ocaml(대안 utop), SML/NJ ML 계열은 "toplevel"이라는 이름을 쓴다
Lisp 계열 SBCL, Racket, Clojure의 clj Clojure는 nREPL로 편집기와 실행 중인 프로세스를 이어 REPL 주도 개발을 한다
Erlang, Elixir erl, iex 실행 중인 노드에 원격 셸로 접속해 상태를 살필 수 있다
Julia julia ?(도움말), ;(셸), ](패키지 관리) 모드 전환
R R 콘솔 RStudio 콘솔도 같은 REPL
Lua, PHP, Perl lua, php -a, perl -de 0 Perl은 디버거를 REPL처럼 쓴다
Swift swift repl LLDB 위에서 동작하며 입력을 컴파일해 실행
C, C++ Cling CERN의 ROOT에서 쓰는 LLVM·Clang 기반 대화형 C++ 인터프리터. 입력을 JIT 컴파일해 실행하며 ROOT 6부터 이전의 CINT를 대체했다[5]
Rust, Go evcxr, gore, yaegi(서드파티) 공식 REPL은 없다

C·C++ 쪽 사례를 조금 더 보면, CINT는 일본의 고토 마사하루가 개발해 ROOT 5까지 쓰인 C/C++ 인터프리터로, ANSI C와 C++11 이전 문법 대부분을 지원했지만 템플릿 일부 등 몇몇 문법을 처리하지 못하고 변수 유효 범위가 모호하다는 비판을 받았다. Cling은 Clang을 그대로 쓰므로 현대 C++ 문법 대부분을 받아들인다. ROOT의 Python 바인딩 PyROOT를 구현하려고 Cling 위에 만든 cppyy는 Python 인터프리터에서 C++ 코드를 평가하고 그 결과(클래스, 함수)를 Python 객체처럼 쓸 수 있게 한다.

REPL은 인터프리터 언어의 전유물이 아니다. Java, C#, Swift, C++의 REPL은 입력을 뒤에서 컴파일하거나 JIT로 번역해 실행하고 겉모습만 인터프리터처럼 보인다. 이런 구조를 두고 Read-Compile-Print Loop라고 바꿔 부르기도 한다.

셸, 노트북, 디버거와 비교

편집 원본 편집
구분 입력 단위 상태 결과 보관 예
REPL 식, 문장 세션 동안 메모리에 유지 화면에만 남음(기록 파일 제외) python, node, ghci
운영체제 셸 명령줄 환경 변수, 현재 디렉터리 정도 화면 Bash, zsh, 파워셸
노트북 셀(여러 줄 코드 묶음) 커널 프로세스가 유지 코드, 출력, 그림, 설명을 한 문서(.ipynb)에 저장 Jupyter, Google Colab
디버거 콘솔 식 멈춘 프로그램의 상태 화면 gdb, pdb, 브라우저 개발자 도구
DBMS 클라이언트 SQL 문 서버 연결과 트랜잭션 화면(표) psql, mysql, sqlite3

Jupyter Notebook은 REPL을 문서 형태로 넓힌 것이다. 브라우저의 셀에 코드를 적고 실행하면 서버를 거쳐 별도 프로세스인 커널(Python이면 IPykernel)이 평가하고, 결과가 셀 아래에 붙는다. 프런트엔드와 커널은 ZeroMQ 소켓 위의 JSON 메시지 프로토콜로 통신하며, 한 커널에 여러 프런트엔드가 동시에 붙을 수도 있다.[6] IPython은 페르난도 페레스가 2001년 만든 향상된 Python REPL로 시작했고, 2011년 노트북이 추가된 뒤 다른 언어 커널이 늘어나자 2014년 언어 독립적인 부분이 Project Jupyter로 분리되었다.[7] 노트북은 셀을 아무 순서로나 다시 실행할 수 있어, 화면에 보이는 코드 순서와 커널의 실제 상태가 어긋나는 숨은 상태(hidden state) 문제가 REPL보다 두드러진다.

디버거도 멈춘 지점에서 식을 평가해 보여 주므로 REPL과 닮았고, 실제로 많은 디버거가 REPL 기능을 품고 있다. DBMS 클라이언트는 입력을 로컬에서 평가하지 않고 서버에 보내 실행한 결과를 표로 보여 준다는 점이 언어 REPL과 다르다.

  • 탐색적 프로그래밍: 라이브러리 함수가 무엇을 돌려주는지, 정규식이 어떻게 맞는지 바로 확인한다. 편집, 컴파일, 실행, 디버깅을 반복하는 고전적인 순환보다 사람이 끼어드는 빈도가 높아 시행착오가 빠르다.
  • 디버깅: 디버그용 출력 코드를 넣고 다시 빌드할 필요 없이 값을 살핀다.
  • 학습: 문법을 하나씩 시험해 보는 교육 도구로 쓰인다.
  • 계산기와 데이터 분석: Python, R, Julia의 REPL과 노트북에서 수치 계산과 시각화를 한다.
  • 실행 중인 시스템 점검: Erlang의 원격 셸이나 Clojure의 nREPL로 운영 중인 프로세스에 붙어 상태를 보고 코드를 바꾼다. Lisp 개발자는 소스 파일은 버전 관리로 관리하면서, 편집기와 연결된 실행 중 Lisp 프로세스에 함수 하나씩 다시 컴파일해 넣는 방식으로 애플리케이션 상태를 잃지 않고 개발한다.

REPL은 임의의 코드를 실행하는 창구이므로 네트워크에 노출하면 원격 코드 실행 취약점과 같다. Node.js의 repl 서버를 TCP 소켓에 붙이거나, nREPL, Erlang 분산 노드, 인증 없는 Jupyter 서버를 외부에 열어 두는 설정은 공격자에게 그대로 셸을 주는 일이다. Jupyter는 기본적으로 토큰 인증을 켠다. 또한 REPL 기록 파일(예: ~/.python_history, ~/.node_repl_history)에 비밀번호나 API 키가 남을 수 있다.

L. 피터 도이치와 에드먼드 버클리는 1964년 PDP-1용 LISP 구현을 설명하면서 "READ-EVAL-PRINT cycle"이라는 표현을 썼다.[8] 도이치는 이 구현을 10대 시절에 만들었다. 이후 1974년 Maclisp 참조 설명서에 "read-eval-print loop"라는 말이 나오고, 1980년대에는 Scheme 쪽에서 REP loop, REPL이라는 약어가 쓰인 것으로 알려져 있다. BASIC의 직접 모드(줄 번호 없이 입력한 명령을 곧바로 실행)도 비슷한 대화형 방식이었다.

온라인 코딩 서비스 Replit의 이름도 REPL에서 따왔다.