페미위키:포크 프로젝트/리브레 위키/Lisp

최근 편집: 2021년 11월 1일 (월) 23:04

소개

파일:Xcde 1.png

리습(LISP)는 존 매카시(John McCarthy)가 1956년부터 1958년까지 인공지능을 연구하는 프로젝트를 수행하다 탄생시킨 프로그래밍 언어다. 이 프로젝트에서 매카시는 FORTRAN에서 목록 작업을 수행하는 서브루틴의 패키지로 Fortran List Processing Language(FLPL)을 구현했고, 이것이 모태가 되어 1958년에 처음으로 Lisp[1]이 발표되었다. Lisp이라는 이름은 List Processor[2]에서 유래되었다.

Lisp은 1957년에 발표된 FORTRAN 다음으로 오래된 고급 프로그래밍 언어지만, 새로운 프로그래밍 패러다임이 등장할 때마다 이것을 적극 수용하면서 변화해 왔으며, 이는 21세기인 지금도 현재 진행형이다.

역사가 오래된 만큼, 매우 다양한 변종이 존재하며 이를 모두 일컬어 사투리(Dialects)라고 부른다. 보통 Lisp이라고만 표기하면 Lisp 의 모든 사투리를 통틀어 말하는 표현이 된다.

Lisp 의 영향과 족보

Lisp 은 최초로 함수형 패러다임을 수용한 언어로, 이후에 등장한 많은 프로그래밍 언어에 영향을 미쳤다. 최초의 함수형 언어로서 이후에 등장한 많은 함수형 언어에 영향을 주었고, 최초의 Garbage Collection 을 수행하는 언어로 여러 다른 언어에 영향을 미쳤으며, Macro 라는 강력한 메타 프로그래밍 기능 또한 여러 많은 언어에 영향을 주었다. Closure, Read-Evaluate-Print Loop(REPL) 등 현재 떠올릴 수 있는 많은 프로그래밍의 특성이 Lisp에서 최초로 등장했다.

Lisp 의 사투리 중에 현재까지 널리 쓰이면서 유명한 것들은 다음과 같은 것이 있다.

특징

Atom

초기 Lisp에서 Datatype 은 크게 Atom 과 리스트로 구분되었다. Atom은 상수나 심볼을 뜻하고, 리스트는 이러한 Atom 또는 다른 리스트를 원소로 포함하며 유한한 길이를 가진다. 여기서 두드러지는 특징은, Atom의 값이 불변(immutable)이라는 점이다.

이후 여러 Lisp 사투리가 등장하고 분화되는 과정에서 Atom의 Immutable 특성이 사라지는 경우도 많이 발생했으며, 이는 아마도 실용적인 목적에 의해 이렇게 된 것이 아닐까라고 추측한다.

List

Lisp에는 기본적으로 개수를 셀 수 있고 변경 가능한 리스트가 기본 자료형으로 주어진다. 리스트의 원소는 Atom과 함께 다른 리스트 또한 원소가 될 수 있다.

문법

Lisp 은 표현식 지향 언어이다. 문장이나 식, 데이터의 구분 없이 모든 것은 리스트와 그 안에 내용을 채우는 표현식으로 간주된다. 표현식이 평가(Evaluate)될 때 실제 코드가 실행되며, 평가 후 값이 나온다. 값 또한 코드와 데이터 구분 없이 아무것이나 올 수 있다. 즉 코드 실행의 결과가 값 뿐만이 아닌 함수도 될 수 있다.

또한 기본적으로 전위 표기법(또는 폴란드 표기법, Polsih Notation)을 사용한다. 즉, 통상 프로그래밍 언어에서 1 + 2를 수행하는 코드를 작성하면 다음과 같이 되는데

1 + 2

Lisp에서는 다음과 같이 + 이 제일 앞에 나오고 숫자가 뒤에 표기되는 형식을 취한다. 이는 + 라는 함수에 숫자 1과 2인 인자를 주어 호출함을 뜻한다.

(+ 1 2)

이와 같이 괄호와 Atom을 사용하여 표기하는 방법을 Lisp에서는 S-Expression(Symbolic Expression)이라고 부른다. 이것 때문에 Lisp의 소스 코드는 중첩괄호의 연속이 된다.

M-Expression(Meta Expression)이라는 것이 초기 Lisp 에 존재했는데, 이것은 일반적인 절차형 언어에서 함수를 호출하는 구문을 떠올리면 된다. 나중에 이것을 동등한 S-Expression으로 변환하고, 이를 평가(Evaulate)하면 결국 코드를 실행하는 것과 동일하다는 것이 밝혀졌고, 이후에 등장한 Lisp 사투리에서는 찾아볼 수 없게 되었다.

Homoiconicity

"Code is data, data is code." 로 설명되는 바로 그것이다. Lisp에서는 코드도 일종의 데이터이다. 이는 S-Expression 에 의해 Lisp에서 모든 것은 List 로 표현되기 때문이다. 위의 List 단락에서 예로 들었던 (+ 1 2) 라는 List를 입력하면 코드로 해석이 되어(평가, Evlauation) + 함수에 1 과 2를 인자로 넘겨주고, 1 + 2 가 연산되어 3 을 출력하며, 평가하지 말고 그대로 리스트로 저장하는 ' (quote)를 붙여서 '(+ 1 2)를 입력하면 첫 번째 원소가 + 이고 두 번째 원소가 1, 세 번째 원소가 2 인 리스트로 인식된다. 즉, 이 리스트를 코드로 보고 그대로 실행할 수도 있고, 실행하지 않고 다른 함수에 리스트형 데이터로 넘겨주는 것도 가능하다..

Lambda Function

소위 익명 함수를 뜻하는 Lambda Function을 정의하여 사용할 수 있다. 이 자체만으로는 "표현 가능"하다는 것 외에 의미가 없지만 Lisp 계열 언어에서 주어지는 기본 함수나 API 들이 이를 적극 사용하여 간결하면서도 동시에 편리하게 코딩을 할 수 있도록 도와주는 강력한 요소이다.

예를 들어 Java 7 이하의 API에서 지원하는 컬렉션 정렬 기능을 사용하려면 Comparator 인터페이스를 구현하여 compare 메서드를 구현해야 한다. 즉, 고작 이거 하나 하려고 클래스를 하나 더 만들어야 한다. 하지만 Lisp에서는 그냥 람다 함수로 대소비교를 할 수 있는 익명 함수를 정의해서 파라미터로 넘기면 끝이다. 자바도 8부터 이러한 람다 표현식을 수용하여 보다 편리하게 코딩할 수 있게 되었다.

Macro

문법의 추상화, 메타 프로그래밍을 가능하게 해 주는 매크로(Macro)를 지원한다. 바로 이것에 의해 Lisp 은 다른 프로그래밍 언어 대비 새로운 패러다임을 쉽게 수용할 수 있다.

이 기능은 매우 쉽게 말해서 C언어의 #define과 유사하지만 훨씬 강력한 기능을 제공한다. C의 #define은 소스 코드의 전처리기에 의해 미리 정의된 내용을 소스 코드 수준에서 치환하는 데 그치지만, Lisp의 매크로는 모든 것을 리스트에 들어 있는 내용으로 치환하는 덕에, 실행 도중에 미리 정의된 프로그램으로 직접 치환된다. 즉, 매크로 자체가 입력되는 리스트의 내용을 미리 정의된 리스트의 내용으로 대체하는 매우 강력한 함수이다.

하지만 기능이 강력한 만큼 양날의 검 같은 존재가 바로 이 Macro 다. 나 혼자서만 작업하는 경우면 상관없으나 여러 사람이 작성하는 프로그램에서 사전에 충분한 협의 없이 만들어 쓰는 매크로는 프로그램 전체에 독이 될 가능성이 높다. 먼저 다른 개발자가 설명을 듣거나 해당 매크로의 소스코드를 완전히 이해하기 전에는 코드를 해독하거나 사용하기 힘들고, 여러 명이 공동 작업으로 프로그램을 작성한다는 자체가 프로그램의 덩치가 꽤 크다는 것을 뜻하기 때문에 충분한 사전검토나 협의 없이 만들어진 매크로는 안만드니 못하는 경우도 충분히 있을 수 있기 때문이다.

Lisp 1, Lisp 2

Lisp 1

초기의 Lisp은 보통 말하는 변수와 함수의 이름이 겹칠 수 없었다. 즉, 함수에 사용되는 심볼과 값을 나타내기 위해 사용하는 심볼이 겹칠 수 없었다. 요즘 식으로 표현하면 둘이 같은 네임 스페이스를 사용하고 있기 때문에 이것이 안 된다. 다음은 Lisp 1인 Clojure 예제 코드로, a에 값 4를 할당하고, 이후 2를 곱하는 함수를 정의한 후 a함수에 값 a의 심볼을 넘겨서 호출을 시도하는 코드이다. Clojure는 Lisp 1이기 때문에 당연히 오류가 발생한다.

user=> (def a 4)
#'user/a
;; 아랫줄에서 심볼 a 는 (fn [x] (* x 2)) 함수로 바뀐다.
user=> (defn a [x] (* x 2))
#'user/a
user=> (a a)

ClassCastException user$a cannot be cast to java.lang.Number  clojure.lang.Numbers.multiply (Numbers.java:146) 
;; 함수인 a를 받아서 *를 수행하는 clojure.lang.Number.multiply에 전달했으므로, 자동으로 형변환을 할 수 없다고 오류가 발생한다.

Lisp 2

1966년에 제안된 개념으로, Lisp 1과 달리 함수 심볼과 값 심볼의 네임스페이스를 분리시켰다. 다음 예제 코드는 위의 Clojure 예제와 동일한 내용의 코드로, Common Lisp에서 아무런 문제 없이 실행된다.

[1]> (setq a 4)
4
[2]> (defun a (x) (* x 2))
A
[3]> (a a)
8

하지만 해당 심볼이 함수 심볼임을 나타내는 #'을 붙여서 a를 넘기면 Clojure와 유사한 오류가 발생하는 것을 볼 수 있다.

[4]> (a #'a)

*** - *: #<FUNCTION A (X) (DECLARE (SYSTEM::IN-DEFUN A)) (BLOCK A (* X 2))> is
      not a number
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead.
ABORT          :R2      Abort main loop
Break 1 [5]>

Lisp교

항목 전체 분위기가 너무 딱딱하여 웃자고 쓴 내용이다. 이 단락은 Standard ML을 참고하여 작성되었다. 이를 심각하게 받아들이면 곤란하다.

"Code is data, Data is Code"라는 McCarthy 교주님의 말씀 아래, 모든 코드와 데이터는 평등하며, 괄호와 매크로는 신이 내린 축복임을 믿는 종교다. 사제 그린스펀의 말씀[3]에 따라 C, FORTRAN 같은 언어를 사용하는 자들은 결국 Lisp 로 귀결될 수밖에 없는 것을 거칠고 야만스러운 언어로 만드는 어리석은 것들이라고 믿는다. 그 밖에 C++, C#, Java, Python 같은 언어는 그저 최신 유행에 지나지 않고 돈벌이와 영합한 또 다른 어리석은 언어일 뿐이며, Haskell, Scala 같은 언어는 교주님이 전한 신으로부터의 신성한 말씀을 흉내낸 아류작에 지나지 않는다. 정적 타이핑은 애초에 교주님께서 신의 말씀을 그렇게 전하지 않으셨기 때문에 이단이라며 혐오한다. 피터 노비그와 같은 사제들이 교주님께 Python과 같은 다른 종교의 말씀도 우리 신의 말씀과 다르지 않다는 이의를 제기했으나 곧 "코드와 데이터가 동등한가?" 라는 질문으로 바로 이단으로 심판했다는 일화 등이 유명하다.

한 때 신의 말씀을 하나로 통일하려는 움직임이 있었으나, 교주님께서 싫어하셔서 그러한 일은 일어나지 않았다. 대신 Common Lisp, EuLisp 등 다양한 말씀 판본이 존재한다.

"Code is data, Data is Code" 특성은 Io, Julia, Prolog 등 다른 언어에서도 찾아볼 수 있으나 그런 듣보잡들은 애써 외면한다.

이들은 매크로는 신이 내린 약속의 증표이며, 모든 것을 흡수하여 가능하게 하는 궁극의 도구로 굳게 믿는다. 매크로가 없는 프로그래밍 언어는 제대로 된 프로그래밍 언어 취급을 하지 않으며, 매크로 사용에 따른 부작용은 애초에 언급할 가치도 없다고 생각한다. 매크로가 없는 Lisp 사투리도 꽤나 많은데?

이 종교 내에 세력이 큰 교파로 Common Lisp파, Scheme파가 있으며, 앞에서는 서로를 어리석다고 비난하지만 알고 보면 서로 영향을 강하게 주고받고 있다. 그 밖에 신의 말씀은 거의 이해를 못하지만 생업을 위해 반쪽짜리 신의 말씀을 실천하는 어리석지만 착한 AutoLISP파, Visual Lisp파도 있다.

이 종교를 믿는 사람들은 실용적인 것은 별로 관심이 없으며, 아름답고 보기에는 간결하지만 실제로 이해하고 사용하려면 더럽게 어렵고 복잡한 코드를 좋아한다. 자신들이 전산학, 컴퓨터공학의 전문가라고 믿으며, 특히 엔지니어링적인 것들을 혐오하며 자부심이 넘치는 경향이 있다. 하지만 신의 말씀으로 제대로 된 대형 프로젝트를 진행해 본 사람은 극히 드물다.

  1. 컴퓨터공학, 전산학을 전공한 사람이면 으레 이것을 "리습"이라고 읽는다. 물론, 이렇게 표기하면 "국립국어원의 외래어 표기규정에는 리스프라고 되어 있다"라고 반론이 들어올 수 있다. 2010년에 어떤 사람이 LISP의 한글 표기에 대해 국립국어원 온라인 가나다에 문의한 바가 있긴 하다. 여기서는 학계에서 일반적으로 쓰이는 '리습'으로 적는다.
  2. John McCarthy, "RECURSIVE FUNCTIONS OF SYMBOLIC EXPRESSIONS AND THEIR COMPUTATION BY MACHINE (Part I)", Communications of the ACM CACM Volume 3 Issue 4, 1960, http://www-formal.stanford.edu/jmc/recursive.html 2015-04-26 확인됨.
  3. Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. , [https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule , Greenspun's tenth rule, 2015-04-26 확인됨.