Emacs Lisp
≡ 목차 (Table of Contents)
이 글은 더이상 유지보수 되지 않으며 대신 ⏍https://seorenn.github.io/article/emacs-lisp-setq-default.html ⏍https://seorenn.github.io/article/emacs-lisp-single-quote.html ⏍https://seorenn.github.io/article/emacs-lisp-eval-quoted-list-elements.html ⏍https://seorenn.github.io/article/emacs-lisp-plist.html ⏍https://seorenn.github.io/article/emacs-lisp-funcall-and-apply.html 등의 글로 쪼게져서 관리될 예정입니다.
Emacs Lisp은 이름 처럼 Emacs에서 사용할 수 있는 Lisp 언어를 의미한다. 물론 표준 Common Lisp에 Emacs의 다양한 함수가 지원되는 형태라서 Lisp이 아니라고 하기에도 좀 미묘하긴 하다.
개념 메모
샤프(Sharpsign)
#
(Sharpsign) 은 s-expressions 특수 문법이다. 근데 뭔 소린지 하나도 모르겠다. 사실 당연하다. #
은 그 자체만으론 의미가 없고 뒤에 뭐가 오는지에 따라 달라지기 때문이다.
에를 들어 #(1 2 3)
은 벡터(Vector), #'function-name
의 경우는 function abbreviation 으로 불린다.
자세한 내용은 ⏍Sharpsign 글을 참고하자.
단 따옴표(Single Quote)
단 따옴표 '
(혹은 quote 함수)는 식을 그대로 표현할 때 사용한다. 예를 들어 (1 2 3)
이라는 리스트를 메시지로 출력하고 싶어서 아래 코드를 작성했다고 가정해 보자.
(print (1 2 3))
그런데 위 코드는 1
이라는 함수를 찾을 수 없다는 오류가 난다. 이는 (1 2 3)
리스트가 print 함수 호출 시점에 먼저 실행(evaluation)되어 버리기 때문이다. 참고로 Lisp의 리스트의 첫 아이템은 함수가 위치한다.
하지만 이를 아래처럼 고치면 원하는대로 할 수 있다.
(print '(1 2 3))
단 따옴표를 붙인 '(1 2 3)
리스트는 바로 실행되는 게 아니라 인용 처리가 되어서 실행(evaluation)되지 않은 리스트 형태로 넘어가게 된다.
참고로 인용 처리된 리스트는 eval
함수를 이용하면 원래의 리스트(?)대로 만들 수 있다.
(eval '(1 2 3))
위 코드는 처음과 같이 1
이라는 함수가 없다는 오류가 난다.
샤프 단 따옴표(Sharpsign Single Quote)
#'function-name
의 의미는 (function function-name)
을 줄여서 표기한 것이다. 다르게 말해서 함수를 심볼 형태로 넘기기 위한 것이다.
(defun do-what (f) (funcall f "test")) (do-what #'message)
보통은 이렇게 매크로나 C의 함수 포인터(?)를 넘기는 스타일과 비슷하게 쓸 수 있다. 물론 위의 경우 #'
대신 그냥 '
만 써도 문제 없이 동작한다. 다만 #'
의 경우 컴파일러에게 이것은 함수다 라는 것을 명시적으로 알려 주는 의미도 있다고 한다.
apply vs funcall
위에서 본 funcall
과 비슷하게 apply
라는 것도 있다. 사용 방법도 거의 동일하다. 하지만 매개변수를 그대로 받냐 아니면 리스트로 받냐의 차이가 있다.
(funcall #'+ 1 2 3)
위의 것을 apply
를 이용해 구현하면 아래와 같다.
(apply #'+ '(1 2 3))
두 코드 다 6을 반환 한다.
이를 이용해 함수에서 매개변수를 넘기는 방법도 조금 다르다.
(defun do-what-apply (what &rest args) (apply what args)) (defun do-what-funcall (what arg) (funcall what arg)) (do-what-apply #'message "test-apply") (do-what-funcall #'message "test-funcall")
Character
Emacs에서 문자는 ASCII 번호로 표현된다. 즉 숫자다. 만약 특정 ASCII 코드가 기억나지 않는다면 ?
를 붙여서 표현할 수 있다.
(eq ?A 65)
A는 65이기 때문에 위 코드는 참을 반환한다.
setq vs setq-default
Emacs에는 버퍼 로컬 변수라는 개념이 있다. 예를 들자면 tab-width
가 대표적인데, 각 버퍼의 메이저 모드마다 이 변수의 값이 다 다를 수 있다. 버퍼에 속한 로컬 변수이기 때문이다.
setq
명령어는 만약 로컬 변수가 있다면 해당 변수의 값을 지정하고, 로컬 변수가 없다면 전역 변수에 지정하는 방식으로 동작한다.
setq-default
는 버퍼 로컬 변수를 생략하고 무조건 전역 변수의 값을 지정하는 용도로 사용된다.
Comma and Back-Quote
(setq a "a's value" b "b's value" c "c's value") '(a b c) ; => (a b c) `(,a b ,c) ; => ("a's value" b "c's value")
backquote는 quote랑 비슷한데, 인용된 리스트 내부에서 ,
가 붙은 요소는 evaluated 시킨 결과로 치환 시킨다는 점이 다른 것 같다. 간혹 quote로 인용 시킬 때 요소들이 이름이 아닌 값 자체로 들어가길 원할 때가 있는데 이럴 때 쓰면 좋을 것 같다.
Property List (plist)
프로퍼티 리스트는 사전(Dictionary) 타입과 비슷한 자료 구조다.
'(name "Conrad" age 20 phone "82-10-1234-5678" address "Earth, Milkyway, Laniakea Supercluster")
키와 값이 차례로 들어가는 리스트로, 키는 다양한 방식으로도 표현할 수 있는데 예를 들어 :name
처럼 심볼로 넣을 수도 있다.
값을 읽을 때는 plist-get
을 사용한다.
(plist-get plist 'name) ;; -> "Conrad"
값을 쓸 때는 plist-put
을 사용한다.
(plist-put plist 'gender "etc")
코드 예제
각 함수의 설명은 Emacs Lisp Functions 글을 참고하자.
파일 읽고 편집하고 저장하기
(with-temp-buffer (insert-file-contents "/foo/bar/file.md") ;; do what... (write-file "/foo/bar/file-updated.md"))
타입 변환
숫자(정수 및 실수)와 문자열 간의 전환 두 가지 예제다.
(number-to-string 1)
(string-to-number "1")
굳이 설명할 필요는 없을 것 같다.
셸 커맨드
셸 커맨드를 실행시키기 위해 대충 두 가지 함수가 있다.
(shell-command "ls")
작은 버퍼가 열리면서 결과가 표시된다. 해당 함수의 반환 값은 유틸리티가 반환하는 숫자가 돌아온다. 일반적인 UNIX 유틸리티는 성공 시 0을 반환한다는 점을 참고하자.
반환 값이 아닌 출력물을 가로채려면 아래의 방식이 있다.
(shell-command-to-string "ls")
이렇게 하면 결과가 문자열로 반환된다. 파이프를 흉내 내려면 이 함수가 유용할 듯 하다.
비동기 호출
특정 함수를 비동기로 실행시키기 위해서 단순하게는 run-with-idle-timer
를 사용하는 방법이 있다.
;; call my-function after 1 seconds (run-with-idle-timer 1.0 nil #'my-function)
특정 시간 이상 유휴 상태여야 비동기로 동작시키는 커맨드다. run-with-timer
와는 동작이 약간 다르지만 유사하다.
비동기여도 과도하게 CPU를 사용하면 Emacs 자체가 멈춘다. 아무래도 싱글 스레드로 동작해서 그런 것 같다는 느낌이다. 이럴 때는 sleep-for
함수나 sit-for
함수를 사용해서 잠깐씩 화면을 갱신할 틈을 줄 수 있다.
(sit-for 1) ;; wait for 1 seconds and redisplay
sit-for
는 sleep-for
와는 다르게 특정 시간 동안 쉰 뒤 화면을 갱신 시킨다는 차이가 있어서 화면이 얼어 보이는 것이 싫을 때 유용하게 사용할 수 있다.
파일의 내용을 실행시키기
Emacs Lisp 코드를 작성한 파일의 내용을 읽어서 바로 실행시키 위하 아래 함수를 정의해서 사용할 수 있다.
(defun eval-file (file) (eval (ignore-errors (read-from-whole-string (with-temp-buffer (insert-file-contents file) (buffer-string))))))
당연하게도 공용 시스템에선 보안 구멍으로 작용할 수 있으니 가급적 개인 PC에서만 사용하자.
에러 노트
commandp
Wrong type argument: commandp, your-function
commandp
에 대한 문서를 보면 알 수 있지만 이 경우 your-function이라는 함수가 interactive 하지 않아서 발생한 문제일 가능성이 높다. 이 경우 interactive를 넣어주면 해결될 수도 있습니다.
(defun your-function (...) (interactive) ;; <- THIS ...)
안 쓰는 매개변수
예를 들어 아래와 같은 코드에서
(defun somefunc (foo bar) ...)
bar
라는 매개변수를 사용하지 않으면 아래와 같은 경고를 볼 수 있다.
Unused lexical argument 'bar'
이런 경고 메세지를 귀찮다면 매개변수 이름 앞에 밑줄(underscope 혹은 under-line)을 붙이면 경고가 사라진다.
(defun somefunc (foo _bar) ...)
위 처럼 bar
대신 _bar
라고 적으면 안 쓰든 말든 상관하지 않는다.