BIBLIOGRAPHY

Han, Jung. (2025) 2025. “Junghan0611/Emacs-Lisp-Elements.” https://github.com/junghan0611/emacs-lisp-elements.

Stavrou, Protesilaos. 2025. “Emacs Lisp Elements.” Protesilaos Stavrou. April 13, 2025. https://protesilaos.com/emacs/emacs-lisp-elements.

———. (2025) 2025. “Protesilaos/Emacs-Lisp-Elements.” https://github.com/protesilaos/emacs-lisp-elements.

History

  • [2025-04-22 Tue 16:52] 그래 담아. 확인

DONE 매뉴얼 Emacs Lisp Elements

(Stavrou 2025) Stavrou, Protesilaos 2025

This book, written by Protesilaos Stavrou, also known as ‘Prot’, provides a big picture view of the Emacs Lisp programming language.

junghan0611/emacs-lisp-elements

(Han [2025] 2025) Han, Jung 2025 A book that provides a big picture view of the Emacs Lisp programming language.

protesilaos/emacs-lisp-elements

(Stavrou [2025] 2025) Stavrou, Protesilaos 2025

한글 번역

1\. Emacs Lisp 시작하기 - Getting started with Emacs Lisp

이 책의 목적은 Emacs Lisp(Elisp이라고도 함)에 대한 큰 그림을 제공하는 것입니다. 이는 Emacs를 확장하는 데 사용하는 프로그래밍 언어입니다. Emacs는 프로그래밍 가능한 텍스트 편집기입니다. Emacs Lisp을 해석하고 그에 따라 동작합니다. 코드 한 줄 작성 없이도 Emacs를 사용할 수 있습니다. 이미 많은 기능이 내장되어 있기 때문입니다. 하지만 언제든지 직접 작성한 Elisp 코드나 다른 사람에게서 얻은 패키지 형태로 Emacs를 프로그래밍하여 원하는 대로 동작하게 할 수 있습니다.

Programming your own text editor is both useful and fun. You can, for example, streamline a sequence of actions you keep doing by combining them in a single command that you then assign to a key binding: type the key and—bam!—perform all the intermediate tasks in one go. This makes you more efficient while it turns the editor into a comfortable working environment.

자신만의 텍스트 편집기를 프로그래밍하는 것은 유용하면서도 재미있습니다. 예를 들어, 계속 반복하는 일련의 작업을 단일 명령으로 결합한 다음 키 바인딩에 할당하여 간소화할 수 있습니다. 키를 누르면 짠! 한 번에 모든 중간 작업을 수행합니다. 이를 통해 효율성이 향상되고 편집기가 편안한 작업 환경으로 바뀝니다.

The fun part is how you go about writing the code. There are no duties you have to conform with. None! You program for the sake of programming. It is a recreational activity that expands your horizons. Plus, you cultivate your Elisp skills, which can prove helpful in the future, should you choose to modify some behaviour of Emacs.

재미있는 부분은 코드를 작성하는 방식입니다. 따라야 할 의무는 없습니다. 전혀 없습니다! 프로그래밍을 위해 프로그래밍을 합니다. 그것은 당신의 지평을 넓혀주는 여가 활동입니다. 게다가, 미래에 Emacs의 일부 동작을 수정하기로 결정하면 도움이 될 수 있는 Elisp 기술을 연마할 수 있습니다.

Emacs를 가지고 노는 것은 경험의 일부입니다. 그것은 당신의 편집기가 어떻게 작동하는지에 대해 변명 없이 의견을 갖도록 가르쳐줍니다. 핵심은 Elisp를 충분히 알아서 너무 많은 시간을 재미있게 보내거나 사소한 일이 작동하지 않아 좌절하지 않도록 하는 것입니다. 저는 컴퓨터 과학이나 인접 학문 배경이 없는, 직접 이것저것 시도해보는 사람으로서 이것을 쓰고 있습니다. 저는 편집기를 가지고 놀면서 시행착오를 통해 Emacs Lisp를 배웠습니다. 저의 명목상의 목표는 반복적으로 수행하는 특정 마이크로 동작을 개선하는 것이었습니다. 효율성을 추구하다가 훨씬 더 심오한 것을 발견했습니다. 편집기를 확장하는 방법을 배우는 것은 만족스러운 경험이었고 그 결과 생산성이 향상되었습니다. Emacs는 제가 원하는 대로 작동하고 저는 그것에 만족합니다.

각 장은 일반적으로 짧고 핵심을 찌릅니다. 어떤 장은 초보자에게 더 친숙하고, 다른 장은 고급 주제를 더 깊이 다룹니다. 장들 사이에는 링크가 있어, 참고 매뉴얼이 갖춰야 할 모습 그대로입니다. 필요한 내용을 찾기 위해 앞뒤로 자유롭게 이동할 수 있습니다.

여기서 보게 될 텍스트는 산문과 코드의 조합입니다. 코드는 실제 Elisp이거나 기본 패턴을 포착하는 의사 코드일 수 있습니다. 이 책을 Emacs 안에서 읽거나 Emacs를 쉽게 사용할 수 있는 상태에서 읽는 것을 권장합니다. 이렇게 하면 제공되는 함수를 직접 사용해 보면서 그 미묘한 차이를 더 잘 이해할 수 있습니다.

제가 채택하는 “큰 그림 보기” 방식은 Emacs Lisp으로 작업하는 동안 자주 접하는 개념을 다루는 것입니다. 이 책은 Emacs Lisp 참조 매뉴얼을 대체할 수 없으며, 제가 설명하는 Elisp 형식에 대한 진실의 원천으로 간주되어서는 안 됩니다.

행운을 빌며 즐겁게 읽으세요!

2\. Emacs Lisp 평가하기 - Evaluate Emacs Lisp

Everything you do in Emacs calls some function. It evaluates Emacs Lisp code, reading the return values and producing side effects (Side effect and return value).

Emacs에서 수행하는 모든 작업은 어떤 함수를 호출하는 것입니다. Emacs Lisp 코드를 평가하고, 반환 값을 읽고, 부작용을 생성합니다 (부작용 및 반환 값).

You type a key on your keyboard and a character is written to the current buffer. That is a function bound to a key. It actually is an interactive function, because you are calling it via a key binding rather than through some program. Interactive functions are known as “commands”. Though do not let the implementation detail of interactivity distract you from the fact that every single action you perform in Emacs involves the evaluation of Emacs Lisp.

키보드에서 키를 누르면 현재 버퍼에 문자가 기록됩니다. 이것은 키에 바인딩된 함수입니다. 실제로 이것은 대화형 함수입니다. 왜냐하면 어떤 프로그램을 통해서가 아니라 키 바인딩을 통해 호출하기 때문입니다. 대화형 함수는 “명령”이라고 알려져 있습니다. 하지만 Emacs에서 수행하는 모든 단일 작업은 Emacs Lisp의 평가를 포함한다는 사실로부터 대화형이라는 구현 세부 사항이 여러분을 혼란스럽게 하지 않도록 하십시오.

Another common pattern of interaction is with the M-x ( execute-extended-command) key, which by default runs the command execute-extended-command : it produces a minibuffer prompt that asks you to select a command by its name and proceeds to execute it.

또 다른 일반적인 상호 작용 패턴은 기본적으로 execute-extended-command 명령을 실행하는 M-x( execute-extended-command ) 키를 사용하는 것입니다. 이 키는 이름으로 명령을 선택하라는 미니버퍼 프롬프트를 생성하고 명령을 실행합니다.

Emacs can evaluate Elisp code from anywhere. If you have some Elisp in your buffer, you can place the cursor at the end of its closing parenthesis and type C-x C-e ( eval-last-sexp). Similarly, you can use the commands eval-buffer and eval-region to operate on the current buffer or highlighted region, respectively. Emacs는 어디에서든 Elisp 코드를 평가할 수 있습니다. 버퍼에 Elisp 코드가 있는 경우 닫는 괄호 끝에 커서를 놓고 C-x C-e ( eval-last-sexp )를 입력할 수 있습니다. 마찬가지로 eval-buffereval-region 명령을 사용하여 현재 버퍼 또는 강조 표시된 영역에서 각각 작업을 수행할 수 있습니다.

The eval-last-sexp also works on symbols (Symbols, balanced expressions, and quoting). For example, if you place the cursor at the end of the variable buffer-file-name and use C-x C-e ( eval-last-sexp), you will get the value of that variable, which is either nil or the file system path to the file you are editing. eval-last-sexp 은 심볼(심볼, 균형 잡힌 표현식, 인용)에서도 작동합니다. 예를 들어 buffer-file-name 변수 끝에 커서를 놓고 C-x C-e ( eval-last-sexp )를 사용하면 해당 변수의 값을 얻을 수 있습니다. 이 값은 nil 이거나 편집 중인 파일의 파일 시스템 경로입니다.

Sometimes the above are not appropriate for what you are trying to do. Suppose you intend to write a command that copies the file path of the current buffer. To do that, you need your code to test the value of the variable buffer-file-name (Buffers as data structures). But you do not want to type out buffer-file-name in your actual file, then use one of the aforementioned commands for Elisp evaluation, and then undo your edits. That is cumbersome and prone to mistakes! The best way to run Elisp in the current buffer is to type M-: ( eval-expression): it opens the minibuffer and expects you to write the code you want to evaluate. Type RET from there to proceed. The evaluation is done with the last buffer as current (the buffer that was current prior to calling eval-expression ).

때로는 위에서 언급한 방법들이 여러분이 하려는 작업에 적합하지 않을 수 있습니다. 예를 들어 현재 버퍼의 파일 경로를 복사하는 명령을 작성하려고 한다고 가정해 봅시다. 그렇게 하려면 여러분의 코드가 변수 buffer-file-name (데이터 구조로서의 버퍼)의 값을 테스트해야 합니다. 하지만 실제 파일에 buffer-file-name 을 입력한 다음, 앞에서 언급한 Elisp 평가 명령 중 하나를 사용하고, 편집 내용을 되돌리고 싶지는 않을 것입니다. 이는 번거롭고 실수하기 쉽습니다! 현재 버퍼에서 Elisp를 실행하는 가장 좋은 방법은 M-: ( eval-expression )을 입력하는 것입니다. 그러면 미니버퍼가 열리고 평가할 코드를 작성하라는 메시지가 표시됩니다. 거기에서 RET를 입력하여 진행합니다. 평가는 마지막 버퍼( eval-expression 을 호출하기 전에 현재 버퍼였던 버퍼)를 현재 버퍼로 사용하여 수행됩니다.

Here is some Emacs Lisp you may want to try in (i) a buffer that corresponds to a file versus (ii) a buffer that is not associated with any file on disk.

여기에 (i) 파일에 해당하는 버퍼와 (ii) 디스크의 파일과 연결되지 않은 버퍼에서 시도해 볼 수 있는 Emacs Lisp 코드가 있습니다.

;; Use `eval-expression' to evaluate this code in a file-visiting
;; buffer versus a buffer that does not visit any file.
(if buffer-file-name
    (message "The path to this file is `%s'" buffer-file-name)
  (message "Sorry mate, this buffer is not visiting a file"))
 

When you are experimenting with code, you want to test how it behaves. Use the command ielm to open an interactive shell. It puts you at a prompt where you can type any Elisp and hit RET to evaluate it. The return value is printed right below. Alternatively, switch to the *scratch* buffer. If it is using the major mode lisp-interaction-mode, which is the default value of the variable initial-major-mode , then you can move around freely in that buffer and type C-j ( eval-print-last-sexp) at the end of some code to evaluate it. This works almost the same way as eval-last-sexp , with the added effect of putting the return value right below the expression you just evaluated.

코드를 실험할 때 코드가 어떻게 작동하는지 테스트하고 싶을 것입니다. ielm 명령을 사용하여 대화형 셸을 엽니다. Elisp 코드를 입력하고 RET 키를 눌러 평가할 수 있는 프롬프트가 표시됩니다. 반환 값은 바로 아래에 인쇄됩니다. 또는 *scratch* 버퍼로 전환합니다. 주 모드 lisp-interaction-mode 를 사용하는 경우 (변수 initial-major-mode 의 기본값) 해당 버퍼에서 자유롭게 이동하고 일부 코드 끝에서 C-j ( eval-print-last-sexp )를 눌러 평가할 수 있습니다. 이는 eval-last-sexp 와 거의 동일한 방식으로 작동하며, 방금 평가한 표현식 바로 아래에 반환 값을 표시하는 효과가 추가됩니다.

In addition to these, you can rely on the self-documenting nature of Emacs to figure out what the current state is. For example, to learn about the buffer-local value of the variable major-mode , you can do C-h v ( describe-variable), and then search for that variable. The resulting Help buffer will inform you about the current value of major-mode. This help command and many others like describe-function, describe-keymap, describe-key, and describe-symbol , provide insight into what Emacs knows about a given object. The Help buffer will show relevant information, such as the path to the file that defines the given function or whether a variable is declared as buffer-local.

이러한 것 외에도 Emacs의 자체 문서화 기능을 활용하여 현재 상태를 파악할 수 있습니다. 예를 들어, 변수 major-mode 의 버퍼 로컬 값을 알아보려면 C-h v ( describe-variable )를 누른 다음 해당 변수를 검색하면 됩니다. 결과 도움말 버퍼에 major-mode 의 현재 값에 대한 정보가 표시됩니다. 이 도움말 명령과 describe-function , describe-keymap , describe-key , describe-symbol 와 같은 다른 많은 명령은 주어진 객체에 대해 Emacs가 알고 있는 내용에 대한 통찰력을 제공합니다. 도움말 버퍼에는 주어진 함수를 정의하는 파일의 경로 또는 변수가 버퍼 로컬로 선언되었는지 여부와 같은 관련 정보가 표시됩니다.

Emacs is “self-documenting” because it reports on its state. You do not need to explicitly update the Help buffers. This happens automatically by virtue of evaluating the relevant code: Emacs effectively shows you the latest value of whatever it is you are working with. Emacs는 자신의 상태를 알려주기 때문에 “자체 문서화”되어 있습니다. 도움말 버퍼를 명시적으로 업데이트할 필요가 없습니다. 이는 관련 코드를 평가함으로써 자동으로 발생합니다. Emacs는 효과적으로 작업 중인 대상의 최신 값을 보여줍니다.

3\. 부작용 및 반환 값 Side effect and return value

Emacs Lisp has functions. They take inputs and produce outputs. In its purest form, a function is a computation that only returns a value: it does not change anything in its environment. The return value of a function is used as input for another function, in what effectively is a chain of computations. You can thus rely on a function’s return value to express something like “if this works, then also do this other thing, otherwise do something else or even nothing.”

Emacs Lisp에는 함수가 있습니다. 함수는 입력을 받아 출력을 생성합니다. 가장 순수한 형태의 함수는 값을 반환하기만 하는 계산입니다. 환경에 아무것도 변경하지 않습니다. 함수의 반환 값은 다른 함수의 입력으로 사용되며, 이는 사실상 일련의 계산입니다. 따라서 함수의 반환 값에 의존하여 “이것이 작동하면 이 다른 것도 수행하고, 그렇지 않으면 다른 작업을 수행하거나 아무것도 하지 않음”과 같은 것을 표현할 수 있습니다.

Elisp is the language that extends and controls Emacs. This means that it also affects the state of the editor. When you run a function, it can make permanent changes, such as to insert some text at the point of the cursor, delete a buffer, create a new window, and so on. These changes will have an impact on future function calls. For example, if the previous function deleted a certain buffer, the next function which was supposed to write to that same buffer can no longer do its job: the buffer is gone! Elisp는 Emacs를 확장하고 제어하는 언어입니다. 이는 편집기의 상태에도 영향을 미친다는 의미입니다. 함수를 실행하면 커서 위치에 텍스트를 삽입하거나 버퍼를 삭제하거나 새 창을 만드는 등 영구적인 변경을 수행할 수 있습니다. 이러한 변경 사항은 이후 함수 호출에 영향을 미칩니다. 예를 들어 이전 함수가 특정 버퍼를 삭제한 경우, 동일한 버퍼에 쓰려고 했던 다음 함수는 더 이상 작업을 수행할 수 없습니다. 버퍼가 사라졌기 때문입니다!

When you write Elisp, you have to account for both the return value and the side effects. If you are sloppy, you will get unintended results caused by all those ill-considered changes to the environment. But if you use side effects meticulously, you are empowered to take Elisp to its full potential. For instance, imagine you define a function that follows the logic of “create a buffer, go there, write some text, save the buffer to a file at my preferred location, and then come back where I was before I called this function, while leaving the created buffer open.” All these are side effects and they are all useful. Your function may have some meaningful return value as well that you can employ as the input of another function. For example, your function would return the buffer object it generated, so that the next function can do something there like display that buffer in a separate frame and make its text larger. Elisp를 작성할 때 반환 값과 부작용을 모두 고려해야 합니다. 부주의하면 환경에 대한 부적절한 변경으로 인해 의도하지 않은 결과가 발생합니다. 그러나 부작용을 꼼꼼하게 사용하면 Elisp의 잠재력을 최대한 활용할 수 있습니다. 예를 들어 “버퍼를 만들고, 거기로 이동하고, 텍스트를 쓰고, 원하는 위치에 있는 파일에 버퍼를 저장한 다음, 이 함수를 호출하기 전의 위치로 돌아와서 생성된 버퍼를 열어 둡니다.”와 같은 논리를 따르는 함수를 정의한다고 상상해 보세요. 이 모든 것이 부작용이며 모두 유용합니다. 함수는 다른 함수의 입력으로 사용할 수 있는 의미 있는 반환 값을 가질 수도 있습니다. 예를 들어, 함수는 생성된 버퍼 객체를 반환하므로 다음 함수는 별도의 프레임에 해당 버퍼를 표시하고 텍스트를 더 크게 만드는 등의 작업을 수행할 수 있습니다.

The idea is to manipulate the state of the editor, to make Emacs do what you envision. Sometimes this means your code has side effects. At other times, side effects are useless or even run counter to your intended results. You will keep refining your intuition about what needs to be done as you gain more experience and expand the array of your skills (Symbols, balanced expressions, and quoting). No problem; no stress! 핵심은 편집기의 상태를 조작하여 Emacs가 원하는 대로 작동하게 만드는 것입니다. 때로는 이것이 코드에 부작용이 있음을 의미합니다. 다른 경우에는 부작용이 쓸모없거나 의도한 결과에 반하는 경우도 있습니다. 경험을 쌓고 기술 배열을 확장하면서 무엇을 해야 하는지에 대한 직관을 계속 개선해 나갈 것입니다(기호, 균형 잡힌 표현식 및 인용). 문제 없습니다. 스트레스 받지 마세요!

4. Buffers as data structures

4\. 버퍼를 데이터 구조로 사용

A buffer holds data as a sequence of characters. For example, this data is the text you are looking at when you open a file. Each character exists at a given position, which is a number. The function point gives you the position at the point you are on, which typically corresponds to where the cursor is (Evaluate Emacs Lisp). At the beginning of a buffer, point returns the value of 1 (Side effect and return value). There are plenty of functions that return a buffer position, such as point-min, point-max, line-beginning-position, and re-search-forward. Some of those will have side effects, like re-search-forward which moves the cursor to the given match.

버퍼는 데이터를 문자열로 저장합니다. 예를 들어, 이 데이터는 파일을 열 때 보게 되는 텍스트입니다. 각 문자는 주어진 위치(숫자)에 존재합니다. 함수 point 는 현재 위치(일반적으로 커서가 있는 위치)의 위치를 알려줍니다(Emacs Lisp 평가). 버퍼의 시작 부분에서 point1 값을 반환합니다(부작용 및 반환 값). point-min , point-max , line-beginning-positionre-search-forward 과 같이 버퍼 위치를 반환하는 함수가 많이 있습니다. 이러한 함수 중 일부는 re-search-forward 과 같이 부작용을 일으켜 커서를 주어진 일치 항목으로 이동시킵니다.

When you program in Emacs Lisp, you frequently rely on buffers to do some of the following: Emacs Lisp로 프로그래밍할 때 다음 작업을 수행하기 위해 버퍼에 자주 의존합니다.

Extract file contents as a string 파일 내용을 문자열로 추출합니다

Think of the buffer as a large string. You can get the entirety of its contents as one potentially massive string by using the function buffer-string. You may also get a substring between two buffer positions, such as with the buffer-substring function or its buffer-substring-no-properties counterpart (Text has its own properties). Imagine you do this as part of a wider operation that (i) opens a file, (ii) goes to a certain position, (iii) copies the text it found, (iv) switches to another buffer, and (v) writes what it found to this new buffer.

버퍼를 큰 문자열이라고 생각하십시오. 함수 buffer-string 을 사용하여 버퍼 내용 전체를 잠재적으로 거대한 하나의 문자열로 가져올 수 있습니다. 또한 buffer-substring 함수 또는 해당 buffer-substring-no-properties 함수 (텍스트에는 자체 속성이 있음)를 사용하여 두 버퍼 위치 사이의 하위 문자열을 가져올 수도 있습니다. 이것을 (i) 파일을 열고, (ii) 특정 위치로 이동하고, (iii) 찾은 텍스트를 복사하고, (iv) 다른 버퍼로 전환하고, (v) 찾은 내용을 이 새 버퍼에 쓰는 더 광범위한 작업의 일부로 수행한다고 상상해 보십시오.

Present the results of some operation 일부 작업의 결과 제시

You may have a function that shows upcoming holidays. Your code does the computations behind the scenes and ultimately writes some text to a buffer. The end product is on display. Depending on how you go about it, you will want to evaluate the function get-buffer-create or its more strict get-buffer alternative. If you need to clear the contents of an existing buffer, you might use the with-current-buffer macro to temporarily switch to the buffer you are targetting and then either call the function erase-buffer to delete everything or limit the deletion to the range betweeen two buffer positions with delete-region. Finally, the functions display-buffer or pop-to-buffer will place the buffer in an Emacs window.

다가오는 공휴일을 보여주는 함수가 있을 수 있습니다. 코드는 백그라운드에서 계산을 수행하고 궁극적으로 일부 텍스트를 버퍼에 씁니다. 최종 결과물이 표시됩니다. 수행 방식에 따라 함수 get-buffer-create 또는 더 엄격한 get-buffer 대안을 평가하려고 할 것입니다. 기존 버퍼의 내용을 지워야 하는 경우 with-current-buffer 매크로를 사용하여 임시로 대상 버퍼로 전환한 다음 함수 erase-buffer 을 호출하여 모든 항목을 삭제하거나 delete-region 를 사용하여 삭제를 두 버퍼 위치 사이의 범위로 제한할 수 있습니다. 마지막으로 함수 display-buffer 또는 pop-to-buffer 은 버퍼를 Emacs 창에 배치합니다.

Associate variables with a given buffer 특정 버퍼와 변수 연결

In Emacs Lisp, variables can take a buffer-local value which differs from its global counterpart. Some variables are even declared to always be buffer-local, such as the buffer-file-name, fill-column, and default-directory. Suppose you are doing something like returning a list of buffers that visit files in a given directory. You would iterate through the return value of the buffer-list function to filter the results accordingly by testing for a certain value of buffer-file-name (Basic control flow with if, cond , and others). This specific variable is always available, though you can always use the setq-local macro to assign a value to a variable in the current buffer. Emacs Lisp에서 변수는 전역 값과 다른 버퍼 로컬 값을 가질 수 있습니다. 일부 변수는 buffer-file-name , fill-columndefault-directory 와 같이 항상 버퍼 로컬로 선언되기도 합니다. 특정 디렉터리에서 파일을 방문하는 버퍼 목록을 반환하는 것과 같은 작업을 한다고 가정해 보겠습니다. buffer-list 함수의 반환 값을 반복하여 buffer-file-name 의 특정 값을 테스트하여 결과를 적절히 필터링합니다 ( if , cond 등을 사용한 기본 제어 흐름). 이 특정 변수는 항상 사용할 수 있지만, setq-local 매크로를 사용하여 현재 버퍼의 변수에 값을 할당할 수 있습니다.

The latter point is perhaps the most open-ended one. Buffers are like a bundle of variables, which includes their contents, the major mode they are running, and all the buffer-local values they have. In the following code block, I am using the seq-filter function to iterate through the return value of the function buffer-list (Symbols, balanced expressions, and quoting).

후자의 요점은 아마도 가장 개방적인 것일 겁니다. 버퍼는 내용, 실행 중인 메이저 모드, 그리고 가지고 있는 모든 버퍼 로컬 값을 포함하는 변수들의 묶음과 같습니다. 다음 코드 블록에서는 seq-filter 함수를 사용하여 buffer-list 함수의 반환 값을 반복하고 있습니다 (심볼, 균형 잡힌 표현식, 인용).

(seq-filter
 (lambda (buffer)
   "Return BUFFER if it is visible and its major mode derives from `text-mode'."
   (with-current-buffer buffer
     ;; The convention for buffers which are not meant to be seen by
     ;; the user is to start their name with an empty space.  We are
     ;; not interested in those right now.
     (and (not (string-prefix-p " " (buffer-name buffer)))
          (derived-mode-p 'text-mode))))
 (buffer-list))
 

This will return a list of buffer objects that pass the test of (i) being “visible” to the user and (ii) their major mode is either text-mode or derived therefrom. The above may also be written thus (When to use a named function or a lambda function):

이것은 사용자에게 “보이는” (i) 버퍼 객체의 목록과 (ii) 해당 메이저 모드가 text-mode 이거나 그로부터 파생된 목록을 반환합니다. 위는 다음과 같이 작성할 수도 있습니다 (명명된 함수 또는 람다 함수를 사용해야 하는 경우).

(defun my-buffer-visble-and-text-p (buffer)
  "Return BUFFER if it is visible and its major mode derives from `text-mode'."
  (with-current-buffer buffer
    ;; The convention for buffers which are not meant to be seen by
    ;; the user is to start their name with an empty space.  We are
    ;; not interested in those right now.
    (and (not (string-prefix-p " " (buffer-name buffer)))
         (derived-mode-p 'text-mode))))
 
(seq-filter #'my-buffer-visble-and-text-p (buffer-list))
 

As with buffers, Emacs windows and frames have their own parameters. I will not cover those as their utility is more specialised and the concepts are the same. Just know that they are data structures that you may use to your advantage, including by iterating through them (Mapping through a list of elements).

버퍼와 마찬가지로 Emacs 창과 프레임도 자체 매개변수를 가지고 있습니다. 이러한 매개변수의 유용성은 더욱 전문화되어 있고 개념은 동일하므로 다루지 않겠습니다. 반복(요소 목록 매핑)을 포함하여 유리하게 사용할 수 있는 데이터 구조라는 점만 알아두십시오.

5. Text has its own properties

5\. 텍스트는 자체 속성을 가집니다.

Just as with buffers that work like data structures (Buffers as data structures), any text may also have properties associated with it. This is metadata that you inspect using Emacs Lisp. For example, when you see syntax highlighting in some programming buffer, this is the effect of text properties. Some function takes care to “propertise” or to “fontify” the relevant text and decides to apply to it an object known as “face”. Faces are constructs that bundle together typographic and colour attributes, such as the font family and weight, as well as foreground and background hues. To get a Help buffer with information about the text properties at the point of the cursor, type M-x ( execute-extended-command) and then invoke the command describe-char . It will tell you about the character it sees, what font it is rendered with, which code point it is, and what its text properties are.

데이터 구조처럼 작동하는 버퍼(버퍼를 데이터 구조로 사용)와 마찬가지로 모든 텍스트에는 속성이 연결될 수 있습니다. 이는 Emacs Lisp을 사용하여 검사하는 메타데이터입니다. 예를 들어 일부 프로그래밍 버퍼에서 구문 강조 표시를 보면 이는 텍스트 속성의 효과입니다. 일부 함수는 관련 텍스트를 “속성화”하거나 “글꼴화”하고 “face”라고 하는 객체를 적용하기로 결정합니다. Face는 글꼴 패밀리 및 굵기, 전경 및 배경 색조와 같은 타이포그래피 및 색상 속성을 함께 묶는 구조입니다. 커서 위치에 있는 텍스트 속성에 대한 정보가 포함된 도움말 버퍼를 얻으려면 M-x( execute-extended-command )를 입력한 다음 명령 describe-char 을 호출합니다. 그러면 보이는 문자, 렌더링되는 글꼴, 코드 포인트 및 텍스트 속성에 대해 알려줍니다.

Suppose you are writings your own major mode. At the early stage of experimentation, you want to manually add text properties to all instances of the phrase I have properties in a buffer whose major mode is fundamental-mode , so you do something like this (The match data of the last search):

자체 메이저 모드를 작성한다고 가정해 보겠습니다. 초기 실험 단계에서 fundamental-mode 메이저 모드인 버퍼에서 구문 I have properties 의 모든 인스턴스에 텍스트 속성을 수동으로 추가하고 싶다면 다음과 같이 합니다(마지막 검색의 일치 데이터).

(defun my-add-properties ()
  "Add properties to the text \"I have properties\" across the current buffer."
  (goto-char (point-min))
  (while (re-search-forward "I have properties" nil t)
    (add-text-properties (match-beginning 0) (match-end 0) '(face error))))
 

Actually try this. Use C-x b ( switch-to-buffer), type in some random characters that do not match an existing buffer, and then hit RET to visit that new buffer. It runs fundamental-mode, meaning that there is no “fontification” happening and, thus, my-add-properties will work as intented. Now paste the following:

실제로 이것을 시도해 보세요. C-x b ( switch-to-buffer )를 사용하여 기존 버퍼와 일치하지 않는 임의의 문자를 입력한 다음 RET를 눌러 새 버퍼를 방문하세요. 그러면 fundamental-mode 이 실행됩니다. 즉, “폰트화”가 발생하지 않으므로 my-add-properties 가 의도한 대로 작동합니다. 이제 다음을 붙여넣으세요.

This is some sample text. Will the phrase "I have properties" use the `bold' face?
 
What does it even mean for I have properties to be bold?
 

Continue with M-: ( eval-expression) and call the function my-add-properties. Did it work? The face it is applying is called error . Ignore the semantics of that word: I picked it simply because it typically is styled in a fairly intense and obvious way (though your current theme may do things differently). M-: ( eval-expression )를 사용하여 계속하고 함수 my-add-properties 을 호출하세요. 작동했나요? 적용되는 글꼴은 error 라고 합니다. 그 단어의 의미는 무시하세요. 단순히 일반적으로 상당히 강렬하고 눈에 띄는 방식으로 스타일이 지정되기 때문에 선택했습니다 (현재 테마에 따라 다를 수 있음).

There are functions which find the properties at a given buffer position and others which can search forward and backward for a given property. The specifics do not matter right now. All I want you to remember is that the text can be more than just its constituent characters. For more details, type M-x ( execute-extended-command) to call the command shortdoc. It will ask you for a documentation group. Pick text-properties to learn more. Well, use shortdoc for everything listed there. I do it all the time.

주어진 버퍼 위치에서 속성을 찾는 함수와 주어진 속성을 앞으로 또는 뒤로 검색할 수 있는 함수가 있습니다. 구체적인 내용은 지금은 중요하지 않습니다. 텍스트는 구성 문자 그 이상일 수 있다는 것만 기억하면 됩니다. 자세한 내용은 M-x ( execute-extended-command )를 입력하여 shortdoc 명령을 호출하세요. 문서 그룹을 묻습니다. 자세히 알아보려면 text-properties 를 선택하세요. 아니면 거기에 나열된 모든 내용에 대해 shortdoc 을 사용하세요. 저는 항상 그렇게 합니다.

6. Symbols, balanced expressions, and quoting

6\. 심볼, 균형 잡힌 표현식, 그리고 인용

To someone not familiar with Emacs Lisp, it is a language that has so many parentheses! Here is a simple function definition: Emacs Lisp에 익숙하지 않은 사람에게는 괄호가 너무 많은 언어입니다! 다음은 간단한 함수 정의입니다.

(defun my-greet-person (name)
  "Say hello to the person with NAME."
  (message "Hello %s" name))
 

I just defined the function with the name my-greet-person. It has a list of parameters, specifically, a list of one parameter, called name. Then is the optional documentation string, which is for users to make sense of the code and/or understand the intent of the function. my-greet-person takes name and passes it to the function message as an argument to ultimately print a greeting. The message function logs the text in the *Messages* buffer, which you can visit directly with C-h e ( view-echo-area-messages). At any rate, this is how you call my-greet-person with the one argument it expects:

저는 방금 my-greet-person 이라는 이름으로 함수를 정의했습니다. 여기에는 매개변수 목록, 특히 name 이라는 하나의 매개변수 목록이 있습니다. 다음은 사용자가 코드를 이해하거나 함수의 의도를 이해하는 데 도움이 되는 선택적 문서 문자열입니다. my-greet-personname 을 가져와서 message 함수에 인수로 전달하여 궁극적으로 인사말을 출력합니다. message 함수는 C-h e ( view-echo-area-messages )로 직접 방문할 수 있는 *Messages* 버퍼에 텍스트를 기록합니다. 어쨌든 이것이 예상되는 하나의 인수로 my-greet-person 을 호출하는 방법입니다.

(my-greet-person "Protesilaos")
 

Now do the same with more than one parameters:

이제 둘 이상의 매개변수로 동일하게 수행하십시오.

(defun my-greet-person-from-country (name country)
  "Say hello to the person with NAME who lives in COUNTRY."
  (message "Hello %s of %s" name country))
 

And call it thus:

다음과 같이 호출합니다:

(my-greet-person-from-country "Protesilaos" "Cyprus")
 

Even for the most basic tasks, you have lots of parentheses. But fear not! These actually make it simpler to have a structural understanding of your code. If it does not feel this way right now, it is because you are not used to it yet. Once you do, there is no going back.

가장 기본적인 작업조차도 괄호가 많이 필요합니다. 하지만 두려워하지 마세요! 이것들은 실제로 코드의 구조적 이해를 더 간단하게 만들어줍니다. 지금 당장은 그렇게 느껴지지 않더라도 아직 익숙하지 않기 때문입니다. 일단 익숙해지면 되돌아갈 수 없습니다.

The basic idea of any dialect of Lisp, Emacs Lisp being one of them, is that you have parentheses which delimit lists. A list consists of elements. Lists are either evaluated to produce the results of some computation or returned as they are for use in some other evaluation (Side effect and return value): Lisp의 모든 방언(Emacs Lisp도 그 중 하나)의 기본 아이디어는 괄호로 목록을 구분한다는 것입니다. 목록은 요소로 구성됩니다. 목록은 일부 계산 결과를 생성하기 위해 평가되거나 다른 평가에 사용하기 위해 그대로 반환됩니다 (부작용 및 반환 값).

The list as a function call 함수 호출로서의 목록

When a list is evaluated, the first element is the name of the function and the remaining elements are the arguments passed to it. You already saw this play out above with how I called my-greet-person with "Protesilaos" as its argument. Same principle for my-greet-person-from-country, with "Protesilaos" and "Cyprus" as its arguments.

목록이 평가될 때 첫 번째 요소는 함수의 이름이고 나머지 요소는 함수에 전달되는 인수입니다. 위에서 my-greet-person"Protesilaos" 을 인수로 사용하여 호출한 방법으로 이미 이것이 어떻게 작동하는지 확인했습니다. my-greet-person-from-country 의 경우도 마찬가지로 "Protesilaos""Cyprus" 를 인수로 사용합니다.

The list as data데이터를 나타내는 리스트

When a list is not evaluated, then none of its elements has any special meaning at the outset. They are all returned as a list without further changes. When you do not want your list to be evaluated, you prefix it with a single quote character. For example, '("Protesilaos" "Prot" "Cyprus") is a list of three elements that should be returned as-is.

리스트가 평가되지 않으면 해당 요소 중 어떤 것도 처음부터 특별한 의미를 갖지 않습니다. 요소는 추가 변경 없이 리스트로 반환됩니다. 리스트가 평가되는 것을 원하지 않으면 작은따옴표 문자를 접두사로 붙입니다. 예를 들어, '("Protesilaos" "Prot" "Cyprus") 은 있는 그대로 반환되어야 하는 세 개의 요소로 구성된 리스트입니다.

Consider the latter case, which you have not seen yet. You have a list of elements and you want to get some data out of it. At the most basic level, the functions car and cdr return the first element and the list of all remaining elements, respectively:

아직 보지 못한 후자의 경우를 고려해 보십시오. 여러분은 요소 목록을 가지고 있고 그로부터 일부 데이터를 얻고 싶어합니다. 가장 기본적인 수준에서, 함수 carcdr 은 각각 첫 번째 요소와 나머지 모든 요소의 목록을 반환합니다.

(car '("Protesilaos" "Prot" "Cyprus"))
;; => "Protesilaos"
 
(cdr '("Protesilaos" "Prot" "Cyprus"))
;; => ("Prot" "Cyprus")
 

The single quote here is critical, because it instructs Emacs to not evaluate the list. Otherwise, the evaluation of this list would treat the first element, namely "Protesilaos" , as the name of a function and the remainder of the list as the arguments to that function. As you do not have the definition of such a function, you get an error.

여기서 작은따옴표는 Emacs에게 목록을 평가하지 않도록 지시하기 때문에 매우 중요합니다. 그렇지 않으면 이 목록의 평가는 첫 번째 요소인 "Protesilaos" 을 함수의 이름으로, 목록의 나머지를 해당 함수의 인수로 취급합니다. 이러한 함수의 정의가 없으므로 오류가 발생합니다.

Certain data types in Emacs Lisp are “self-evaluating”. This means that if you evaluate them, their return value is what you are already seeing. For example, the return value of the string of characters "Protesilaos" is "Protesilaos". This is true for strings, numbers, keywords, symbols, and the special nil or t. Here is a list with a sample of each of these, which you construct by calling the function list : Emacs Lisp의 특정 데이터 유형은 “자체 평가”됩니다. 이는 해당 데이터 유형을 평가하면 반환 값이 이미 보고 있는 것과 같다는 의미입니다. 예를 들어 문자열 "Protesilaos" 의 반환 값은 "Protesilaos" 입니다. 이는 문자열, 숫자, 키워드, 심볼 및 특수 nil 또는 t 에 해당됩니다. 다음은 함수 list 를 호출하여 구성하는 각 항목의 샘플 목록입니다.

(list "Protesilaos" 1 :hello 'my-greet-person-from-country nil t)
;; => ("Protesilaos" 1 :hello 'my-greet-person-from-country nil t)
 

The list function evaluates the arguments passed to it, unless they are quoted. The reason you get the return value without any apparent changes is because of self-evaluation. Notice that my-greet-person-from-country is quoted the same way we quote a list we do not want to evaluate. Without it, my-greet-person-from-country would be evaluated, which would return an error unless that was also defined as a variable. list 함수는 인용되지 않은 경우 전달된 인수를 평가합니다. 눈에 띄는 변경 없이 반환 값을 얻는 이유는 자체 평가 때문입니다. my-greet-person-from-country 은 평가하고 싶지 않은 목록을 인용하는 것과 같은 방식으로 인용됩니다. 이것이 없으면 my-greet-person-from-country 가 평가되고, 이는 변수로 정의되지 않은 경우 오류를 반환합니다.

Think of the single quote as an unambiguous instruction: “do not evaluate the following.” More specifically, it is an instruction to not perform evaluation if it would have normally happened in that context (Partial evaluation inside of a list). In other words, you do not want to quote something inside of a quoted list, because that is the same as quoting it twice:

작은 따옴표를 명확한 지시 사항으로 생각하십시오. “다음을 평가하지 마십시오.” 더 구체적으로 말하면, 해당 컨텍스트에서 일반적으로 발생했을 평가를 수행하지 않도록 지시하는 것입니다 (목록 내부의 부분 평가). 즉, 인용된 목록 안에 있는 것을 인용하고 싶지 않은데, 이는 두 번 인용하는 것과 같기 때문입니다.

;; This is the correct way:
'(1 :hello my-greet-person-from-country)
 
;; It is wrong to quote `my-greet-person-from-country' because the
;; entire list would not have been evaluated anyway.  The mistake here
;; is that you are quoting what is already quoted, like doing
;; ''my-greet-person-from-country.
'(1 :hello 'my-greet-person-from-country)
 

Now you may be wondering why did we quote my-greet-person-from-country but nothing else? The reason is that everything else you saw there is effectively “self-quoting”, i.e. the flip-side of self-evaluation. Whereas my-greet-person-from-country is a symbol. A “symbol” is a reference to something other than itself: it either represents some computation—a function—or the value of a variable. If you write a symbol without quoting it, you are effectively telling Emacs “give me the value this symbol represents.” In the case of my-greet-person-from-country , you will get an error if you try that because this symbol is not a variable and thus trying to get a value out of it is not going to work.

이제 왜 my-greet-person-from-country 만 인용했고 다른 것은 인용하지 않았는지 궁금할 수 있습니다. 그 이유는 다른 모든 것은 사실상 “자체 인용”이기 때문입니다. 즉, 자체 평가의 반대입니다. 반면에 my-greet-person-from-country 은 기호입니다. “기호”는 자기 자신이 아닌 다른 것을 참조하는 것입니다. 즉, 어떤 계산(함수) 또는 변수 값을 나타냅니다. 기호를 인용하지 않고 작성하면 사실상 Emacs에게 “이 기호가 나타내는 값을 주세요”라고 말하는 것입니다. my-greet-person-from-country 의 경우, 이 기호는 변수가 아니므로 값을 얻으려고 하면 오류가 발생합니다.

Keep in mind that Emacs Lisp has a concept of “macro”, which basically is a templating system to write code that actually expands into some other code which is then evaluated. Inside of a macro, you control how quoting is done, meaning that the aforementioned may not apply to calls that involve the macro, even if they are still used inside of the macro’s expanded form (Evaluation inside of a macro or special form). Emacs Lisp에는 “매크로”라는 개념이 있는데, 이는 기본적으로 일부 코드로 확장되어 평가되는 코드를 작성하는 템플릿 시스템입니다. 매크로 내부에서는 인용 방식(quoting)을 제어하므로, 언급된 내용이 매크로를 포함하는 호출에는 적용되지 않을 수 있습니다. 심지어 매크로의 확장된 형태 내부에서 사용되더라도 마찬가지입니다 (매크로 또는 특수 형식 내부의 평가).

As you expose yourself to more Emacs Lisp code, you will encounter quotes that are preceded by the hash sign, like #'some-symbol. This “sharp quote”, as it is called, is the same as the regular quote with the added semantics of referring to a function in particular. The programmer can thus better articulate the intent of a given expression, while the byte compiler may internally perform the requisite checks and optimisations. In this light, read about the functions quote and function which correspond to the quote and sharp quote, respectively. Emacs Lisp 코드를 더 많이 접하다 보면, #'some-symbol 와 같이 해시 기호가 앞에 붙은 인용 부호들을 보게 될 것입니다. 이 “sharp quote”라고 불리는 것은 일반적인 인용 부호와 동일하지만, 특히 함수를 참조한다는 의미가 추가되었습니다. 따라서 프로그래머는 주어진 표현의 의도를 더 명확하게 표현할 수 있으며, 바이트 컴파일러는 내부적으로 필요한 검사와 최적화를 수행할 수 있습니다. 이러한 관점에서 quotefunction 함수에 대해 읽어보십시오. 이 함수들은 각각 일반 인용 부호와 sharp quote에 해당합니다.

7. Evaluate some elements inside of a list

7\. 리스트 내부의 일부 요소 평가하기

You already have an idea of how Emacs Lisp code looks like (Symbols, balanced expressions, and quoting). You have a list that is either evaluated or taken as-is. There is another case where a list should be partially evaluated or, more specifically, where it should be treated as data instead of a function call with some elements inside of it still subject to evaluation. Emacs Lisp 코드의 모양 (심볼, 균형 잡힌 표현식, 인용)에 대한 아이디어가 이미 있습니다. 리스트는 평가되거나 있는 그대로 받아들여집니다. 리스트가 부분적으로 평가되어야 하거나, 더 구체적으로는 내부의 일부 요소가 여전히 평가 대상인 함수 호출 대신 데이터로 처리되어야 하는 또 다른 경우가 있습니다.

In the following code block, I am defining a variable called my-greeting-in-greek, which is a common phrase in Greek that literally means “health to you” and is pronounced as “yah sou”. Why Greek? Well, you got the lambda that engendered this whole business with Lisp, so you might as well get all the rest (When to use a named function or a lambda function)! 다음 코드 블록에서 저는 my-greeting-in-greek 이라는 변수를 정의하고 있습니다. 이는 그리스어로 “건강하세요”라는 뜻으로 “야 수”라고 발음되는 일반적인 문구입니다. 왜 그리스어냐고요? 음, Lisp에 관한 이 모든 비즈니스를 야기한 lambda 이 있으니 나머지 전부를 얻는 것도 나쁘지 않겠죠(이름 있는 함수 또는 람다 함수를 언제 사용해야 할까요!)!

(defvar my-greeting-in-greek "Γεια σου"
  "Basic greeting in Greek to wish health to somebody.")
 

Now I want to experiment with the message function to better understand how evaluation works. Let me start with the scenario of quoting the list, thus taking it as-is:

이제 평가가 어떻게 작동하는지 더 잘 이해하기 위해 message 함수를 실험해보고 싶습니다. 먼저 목록을 있는 그대로 가져오는 인용 시나리오부터 시작하겠습니다.

(message "%S" '(one two my-greeting-in-greek four))
;;=> "(one two my-greeting-in-greek four)"
 

You will notice that the variable my-greeting-in-greek is not evaluated. I get the symbol, the actual my-greeting-in-greek , but not the value it represents. This is the expected result, because the entire list is quoted and, ipso facto, everything inside of it is not evaluated.

변수 my-greeting-in-greek 이 평가되지 않는 것을 알 수 있습니다. 기호, 실제 my-greeting-in-greek 은 얻지만, 그것이 나타내는 값은 얻지 못합니다. 이것은 예상된 결과입니다. 왜냐하면 전체 목록이 인용되었고, 따라서 그 안의 모든 것이 평가되지 않기 때문입니다.

Now check the next code block to understand how I can tell Emacs that I want the entire list to still be quoted but for my-greeting-in-greek in particular to be evaluated, so it is replaced by its value:

이제 다음 코드 블록을 확인하여 전체 목록은 여전히 인용되기를 원하지만, 특히 my-greeting-in-greek 은 평가되기를 원하도록 Emacs에게 지시하여 해당 값으로 대체되도록 할 수 있는 방법을 이해하십시오.

(message "%S" `(one two ,my-greeting-in-greek four))
;; => "(one two \"Γεια σου\" four)"
 

Pay close attention to the syntax here. Instead of a single quote, I am using the backtick or back quote, which is also known as a “quasi quote” in our case. This behaves like the single quote except for anything that is preceded by a comma. The comma is an instruction to “evaluate the thing that follows” and only works inside of a quasi-quoted list. The “thing” that follows is either a symbol or a list. The list can, of course, be a function call. Let me then use concat to greet a certain person all while returning everything as a list:

여기서 구문에 주의하십시오. 작은따옴표 대신, 백틱 또는 역 따옴표를 사용하고 있는데, 이는 우리 경우에 “준 인용(quasi quote)“이라고도 합니다. 이것은 작은따옴표처럼 작동하지만, 쉼표가 앞에 붙은 것은 예외입니다. 쉼표는 “다음에 오는 것을 평가하라”는 지시이며, 준 인용된 목록 내에서만 작동합니다. “다음에 오는 것”은 기호 또는 목록입니다. 목록은 물론 함수 호출일 수도 있습니다. 그러면 특정 사람에게 인사하면서 모든 것을 목록으로 반환하기 위해 concat 을 사용하겠습니다.

(message "%S" `(one two ,(concat my-greeting-in-greek " " "Πρωτεσίλαε") four))
;; => "(one two \"Γεια σου Πρωτεσίλαε\" four)"
 

Bear in mind that you would get an error if you were not quoting this list at all, because the first element one would be treated as the symbol a function, which would be called with all other elements as its arguments. Chances are that one is not defined as a function in your current Emacs session or those arguments are not meaningful to it, anyway. Plus, two and four would then be treated as variables, since they are not quoted, in which case those would have to be defined as well, else more errors would ensue.

이 목록을 전혀 인용하지 않으면 오류가 발생할 수 있습니다. 왜냐하면 첫 번째 요소 one 이 함수 기호로 취급되어 다른 모든 요소를 인수로 사용하여 호출되기 때문입니다. 현재 Emacs 세션에서 one 이 함수로 정의되지 않았거나 해당 인수가 의미가 없을 가능성이 높습니다. 또한 twofour 은 인용되지 않았으므로 변수로 취급되며, 이 경우에도 정의되어야 추가 오류가 발생하지 않습니다.

Other than the comma operator, there is the ,@ (how is this even pronounced? “comma at”, perhaps?), which is notation for “splicing”. This is jargon in lieu of saying “the return value is a list and I want you to remove the outermost parentheses of it.” In effect, the code that would normally return '(one two three) now returns one two three . This difference may not make much sense in a vacuum, though it does once you consider those elements as expressions that should work in their own right, rather than simply be elements of a quoted list. I will not elaborate on an example here, as I think this is best covered in the context of defining macros (Evaluation inside of a macro or special form).

쉼표 연산자 외에 ,@ (“콤마 앳”이라고 발음하는 건가요?)이 있는데, 이는 “스플라이싱”을 위한 표기법입니다. 이는 “반환 값은 목록이며 가장 바깥쪽 괄호를 제거하고 싶다”는 의미의 전문 용어입니다. 결과적으로 일반적으로 '(one two three) 을 반환하는 코드는 이제 one two three 를 반환합니다. 이러한 차이는 독립적으로는 큰 의미가 없을 수 있지만, 해당 요소를 단순히 인용된 목록의 요소가 아닌 자체적으로 작동해야 하는 표현식으로 간주하면 이해가 됩니다. 여기서는 예제를 자세히 설명하지 않겠습니다. 이는 매크로 정의(매크로 또는 특수 형식 내부의 평가)의 맥락에서 가장 잘 다루어지기 때문입니다.

Chances are you will not need to use the knowledge of partial evaluation. It is more common in macros, though can be applied anywhere. Be aware of it regardless, as there are scenaria where you will, at the very least, want to understand what some code you depend on is doing.

부분 평가에 대한 지식을 사용할 필요는 없을 것입니다. 매크로에서 더 흔하지만 어디든 적용할 수 있습니다. 그럼에도 불구하고 일부 코드가 무엇을 하는지 이해하고 싶을 최소한의 시나리오가 있으므로 알아두십시오.

Lastly, since I introduced you to some Greek words, I am now considering you my friend. Here is a joke from when I was a kid. I was trying to explain some event to my English instructor. As I lacked the vocabulary to express myself, I started using Greek words. My instructor had a strict policy of only responding to English, so she said “It is all Greek to me.” Not knowing that her answer is an idiom for “I do not understand you”, I blithely replied, “Yes, Greek madame; me no speak England very best.” I was not actually a beginner at the time, though I would not pass on the opportunity to make fun of the situation. Just how you should remember to enjoy the time spent tinkering with Emacs. But enough of that! Back to reading this book.

마지막으로, 몇 가지 그리스 단어를 소개했으니 이제 당신을 제 친구로 여기겠습니다. 어렸을 때 들었던 농담이 있습니다. 저는 영어 강사에게 어떤 사건을 설명하려고 했습니다. 어휘력이 부족해서 그리스 단어를 사용하기 시작했습니다. 제 강사는 영어에만 반응하는 엄격한 정책을 가지고 있어서 “It is all Greek to me.”라고 말했습니다. 그녀의 대답이 “나는 당신을 이해하지 못한다”는 의미의 관용구인지 몰랐기 때문에 저는 태연하게 “Yes, Greek madame; me no speak England very best.”라고 대답했습니다. 당시 초보자는 아니었지만 그 상황을 재미있게 만들 기회를 놓치지 않았을 겁니다. Emacs를 가지고 노는 시간을 즐기는 것을 잊지 말아야 합니다. 하지만 그만! 다시 이 책을 읽으러 돌아가자.

8. Evaluation inside of a macro or special form

8\. 매크로 또는 특수 형식 내부의 평가

In the most basic case of Emacs Lisp code, you have lists that are either evaluated or not (Symbols, balanced expressions, and quoting). If you get a little more fancy, you have lists that are only partially evaluated (Partial evaluation inside of a list). Sometimes though, you look at a piece of code and cannot understand why the normal rules of quoting and evaluation do not apply. Before you see this in action, inspect a typical function call that also involves the evaluation of a variable:

가장 기본적인 Emacs Lisp 코드의 경우, 평가되거나 평가되지 않는 목록이 있습니다 (심볼, 균형 잡힌 표현식, 인용). 좀 더 복잡하게 들어가면 부분적으로만 평가되는 목록이 있습니다 (목록 내부의 부분 평가). 하지만 때로는 코드를 보고 인용 및 평가의 일반적인 규칙이 왜 적용되지 않는지 이해할 수 없을 때가 있습니다. 실제로 이것을 보기 전에 변수 평가를 포함하는 일반적인 함수 호출을 살펴보겠습니다.

(concat my-greeting-in-greek " " "Πρωτεσίλαε")
 

You encountered this code in the section about partial evaluation. What you have here is a call to the function concat, followed by three arguments. One of these arguments is a variable, the my-greeting-in-greek. When this list is evaluated, what Emacs actually does is to first evaluate the arguments, including my-greeting-in-greek, in order to get their respective values and only then to call concat with those values. You can think of the entire operation as follows:

부분 평가에 대한 섹션에서 이 코드를 접했습니다. 여기서 여러분은 함수 concat 호출과 세 개의 인수를 가지고 있습니다. 이 인수 중 하나는 변수인 my-greeting-in-greek 입니다. 이 목록이 평가될 때 Emacs가 실제로 하는 일은 먼저 my-greeting-in-greek 를 포함한 인수를 평가하여 각 값을 얻은 다음 해당 값으로 concat 을 호출하는 것입니다. 전체 작업을 다음과 같이 생각할 수 있습니다.

  • Here is a list. 여기에 목록이 있습니다.
  • It is not quoted. 인용되지 않았습니다.
  • So you should evaluate it. 따라서 평가해야 합니다.
  • The first element is the name of the function. 첫 번째 요소는 함수 이름입니다.
  • The remaining elements are arguments passed to that function. 나머지 요소는 해당 함수에 전달되는 인수입니다.
  • Check what the arguments are. 인수가 무엇인지 확인하십시오.
  • Evaluate each of the arguments to resolve it to its actual value. 각 인수를 평가하여 실제 값으로 확인합니다.
  • Strings are self-evaluating, while the my-greeting-in-greek is a variable. 문자열은 자체 평가되는 반면, my-greeting-in-greek 은 변수입니다.
  • You now have the value of each of the arguments, including the value of the symbol my-greeting-in-greek . 이제 기호 my-greeting-in-greek 의 값을 포함하여 각 인수의 값을 갖게 되었습니다.
  • Call concat with all the values you got. 얻은 모든 값을 사용하여 concat 을(를) 호출합니다.

In other words, the following two yield the same results (assuming a constant my-greeting-in-greek ):

달리 말하면, 다음 두 코드는 동일한 결과를 생성합니다 ( my-greeting-in-greek 이(가) 상수라고 가정).

(concat my-greeting-in-greek " " "Πρωτεσίλαε")
 
(concat "Γεια σου" " " "Πρωτεσίλαε")
 

This is predictable. It follows the basic logic of the single quote: if it is quoted, do not evaluate it and return it as-is, otherwise evaluate it and return its value. But you will find plenty of cases where this expected pattern is seemingly not followed. Consider this common case of using setq to bind a symbol to the given value:

이것은 예측 가능합니다. 이는 작은따옴표의 기본 논리를 따릅니다. 인용되면 평가하지 않고 있는 그대로 반환하고, 그렇지 않으면 평가하여 값을 반환합니다. 그러나 예상되는 이 패턴이 따르지 않는 경우가 많이 있습니다. setq 을 사용하여 심볼을 주어진 값에 바인딩하는 일반적인 경우를 생각해 보십시오.

(setq my-test-symbol "Protesilaos of Cyprus")
 

The above expression looks like a function call, meaning that (i) the list is not quoted, (ii) the first element is the name of a function, and (iii) the remaining elements are arguments passed to that function. In a way, this is all true. Though you would then expect the my-test-symbol to be treated as a variable, which would be evaluated in place to return its result which would, in turn, be the actual argument passed to the function. However, this is not how setq works. The reason is that it is a special case that internally does this:

위 표현식은 함수 호출처럼 보입니다. 즉, (i) 목록이 인용되지 않았고, (ii) 첫 번째 요소는 함수 이름이고, (iii) 나머지 요소는 해당 함수에 전달되는 인수입니다. 어떤 면에서는 이 모든 것이 사실입니다. 그러나 my-test-symbol 이 변수로 취급되어 제자리에서 평가되어 결과를 반환하고, 이는 결국 함수에 전달되는 실제 인수가 될 것이라고 예상할 수 있습니다. 하지만 setq 은 이렇게 작동하지 않습니다. 그 이유는 이것이 내부적으로 다음과 같이 작동하는 특별한 경우이기 때문입니다.

(set 'my-test-symbol "Protesilaos of Cyprus")
 

This is where things are as expected. There is no magic happening behind the scenes. The setq, then, is a convenience for the user to not quote the symbol each time. Yes, this makes it a bit more difficult to reason about it, though you get used to it and eventually it all makes sense. Hopefully, you will get used to such special forms, as you find them with setq but also with defun , among many others. Here is a function you have already seen:

이것은 예상대로 작동하는 부분입니다. 이면에서 마법 같은 일은 일어나지 않습니다. 따라서 setq 은 사용자가 매번 심볼을 인용하지 않도록 편의를 제공하는 것입니다. 네, 이 때문에 추론하기가 약간 더 어렵지만 익숙해지면 결국 모든 것이 이해가 됩니다. 바라건대, setq 뿐만 아니라 defun 등에서도 이러한 특별한 형태에 익숙해지실 수 있을 겁니다. 다음은 이미 보신 함수입니다.

(defun my-greet-person-from-country (name country)
  "Say hello to the person with NAME who lives in COUNTRY."
  (message "Hello %s of %s" name country))
 

If the normal rules of evaluation applied, then the list of parametes should be quoted. Otherwise, you would expect (name country) to be interpreted as a function call with name as the symbol of the function and country as its argument which would also be a variable. But this is not what is happening because defun will internally treat that list of parameters as if it was quoted.

일반적인 평가 규칙이 적용된다면, 매개변수 목록은 인용되어야 합니다. 그렇지 않으면 (name country)name 을 함수의 심볼로, country 를 변수이기도 한 인수로 사용하는 함수 호출로 해석될 것이라고 예상할 수 있습니다. 그러나 defun 은 해당 매개변수 목록을 마치 인용된 것처럼 내부적으로 처리하기 때문에 이런 일은 발생하지 않습니다.

Another common scenario is with let (Control flow with if-let* and friends). Its general form is as follows:

또 다른 일반적인 시나리오는 let 을(를) 사용하는 것입니다 ( if-let* 등과 함께 제어 흐름). 일반적인 형식은 다음과 같습니다.

;; This is pseudo-code
(let LIST-OF-LISTS-AS-VARIABLE-BINDINGS
  BODY-OF-THE-FUNCTION)
 

The LIST-OF-LISTS-AS-VARIABLE-BINDINGS is a list in which each element is a list of the form (SYMBOL VALUE) . Here is some actual code: LIST-OF-LISTS-AS-VARIABLE-BINDINGS 은(는) 각 요소가 (SYMBOL VALUE) 형식의 목록인 목록입니다. 실제 코드는 다음과 같습니다.

(let ((name "Protesilaos")
      (country "Cyprus"))
  (message "Hello %s of %s" name country))
 

Continuing with the theme of special forms, if let was a typical function call, the LIST-OF-LISTS-AS-VARIABLE-BINDINGS would have to be quoted. Otherwise, it would be evaluated, in which case the first element would be the name of the function. But that would return an error, as the name of the function would correspond to another list, the (name "Protesilaos"), rather than a symbol. Things work fine with let because it internally does the quoting of its LIST-OF-LISTS-AS-VARIABLE-BINDINGS .

특수 형식이라는 주제를 계속해서 이어가자면, 만약 let 이 일반적인 함수 호출이었다면, LIST-OF-LISTS-AS-VARIABLE-BINDINGS 은 따옴표로 묶여야 했을 것입니다. 그렇지 않으면 평가될 것이고, 이 경우 첫 번째 요소는 함수의 이름이 될 것입니다. 그러나 함수의 이름이 심볼이 아닌 다른 리스트, 즉 (name "Protesilaos") 에 해당하므로 오류가 발생할 것입니다. let 은 내부적으로 해당 LIST-OF-LISTS-AS-VARIABLE-BINDINGS 을 따옴표로 묶기 때문에 문제가 없습니다.

Expect similar behaviour with many special forms as well as with macros such as the popular use-package , which is used to configure packages inside of your Emacs initialisation file. How each of those macros works depends on the way it is designed. I will not delve into the technicalities here, as I want the book to be useful long-term, focusing on the principles rather than the implementation details that might change over time.

널리 사용되는 use-package 과 같은 매크로뿐만 아니라 많은 특수 형식에서도 유사한 동작을 기대할 수 있습니다. use-package 은 Emacs 초기화 파일 내에서 패키지를 구성하는 데 사용됩니다. 이러한 각 매크로의 작동 방식은 설계 방식에 따라 다릅니다. 저는 이 책이 장기적으로 유용하도록 원칙에 초점을 맞추고 시간이 지남에 따라 변경될 수 있는 구현 세부 사항에 대해서는 자세히 다루지 않겠습니다.

To learn what a given macro actually expands to, place the cursor at the end of its closing parenthesis and call the command pp-macroexpand-last-sexp . It will produce a new buffer showing the expanded Emacs Lisp code. This is what is actually evaluated in the macro’s stead.

주어진 매크로가 실제로 어떻게 확장되는지 알아보려면 커서를 닫는 괄호의 끝에 놓고 명령 pp-macroexpand-last-sexp 을 호출하십시오. 그러면 확장된 Emacs Lisp 코드를 보여주는 새 버퍼가 생성됩니다. 이것이 매크로 대신 실제로 평가되는 내용입니다.

With those granted, it is time to write a macro. This is like a template, which empowers you to not repeat yourself. Syntactically, a macro will most probably depend on the use of the quasi-quote, the comma operator, and the mechanics of splicing (Partial evaluation inside of a list). Here is a simple scenario where we want to run some code in a temporary buffer while setting the default-directory to the user’s home directory.

이제 매크로를 작성할 시간입니다. 이것은 템플릿과 같아서 반복 작업을 줄일 수 있습니다. 구문상으로 매크로는 준 인용, 쉼표 연산자 및 스플라이싱(목록 내 부분 평가) 메커니즘에 의존할 가능성이 높습니다. 다음은 default-directory 을 사용자 홈 디렉토리로 설정하면서 임시 버퍼에서 일부 코드를 실행하려는 간단한 시나리오입니다.

(defmacro my-work-in-temp-buffer-from-home (&rest expressions)
  "Evaluate EXPRESSIONS in a temporary buffer with `default-directory' set to the user's home."
  `(let ((default-directory ,(expand-file-name "~/")))
     (with-temp-buffer
       (message "Running all expression from the `%s' directory" default-directory)
       ,@expressions)))
 

In this definition, the &rest makes the following parameter a list. So you can pass an arbitrary number of arguments to it, all of which will be collected into a single list called EXPRESSIONS . The judicious use of partial evaluation ensures that the macro will not be evaluated right now but only when it is called. The arguments passed to it will be placed where you have specified. Here is a call that uses this macro:

이 정의에서 &rest 은 다음 매개변수를 목록으로 만듭니다. 따라서 임의의 수의 인수를 전달할 수 있으며, 이 모든 인수는 EXPRESSIONS 이라는 단일 목록에 수집됩니다. 부분 평가를 신중하게 사용하면 매크로가 지금 당장이 아니라 호출될 때만 평가됩니다. 전달된 인수는 지정한 위치에 배치됩니다. 다음은 이 매크로를 사용하는 호출입니다.

(progn
  (message "Now we are doing something unrelated to the macro")
  (my-work-in-temp-buffer-from-home
   (message "We do stuff inside the macro")
   (+ 1 1)
   (list "Protesilaos" "Cyprus")))
 

If you place the cursor at the closing parenthesis of my-work-in-temp-buffer-from-home , you will be able to confirm what it expands to by typing M-x ( execute-extended-command) and then invoking the command pp-macroexpand-last-sexp . This is what I get: my-work-in-temp-buffer-from-home 의 닫는 괄호에 커서를 놓으면 M-x ( execute-extended-command )을 입력한 다음 pp-macroexpand-last-sexp 명령을 호출하여 확장된 내용을 확인할 수 있습니다. 제가 얻은 결과는 다음과 같습니다.

(let ((default-directory "/home/prot/"))
  (with-temp-buffer
    (message "Running all expression from the `%s' directory" default-directory)
    (message "We do stuff inside the macro")
    (+ 1 1)
    (list "Protesilaos" "Cyprus")))
 

Piecing it together with the rest of the code in its context, I arrive at this:

문맥상 나머지 코드와 함께 조합해 보면 다음과 같습니다.

(progn
  (message "Now we are doing something unrelated to the macro")
  (let ((default-directory "/home/prot/"))
    (with-temp-buffer
      (message "Running all expression from the `%s' directory" default-directory)
      (message "We do stuff inside the macro")
      (+ 1 1)
      (list "Protesilaos" "Cyprus"))))
 

With this example in mind, consider Elisp macros to be a way of saying “this little thing here helps me express this larger procedure more succinctly, while the actual code that runs is still that of the latter.” 이 예제를 염두에 두고 Elisp 매크로를 “여기 이 작은 것이 더 큰 절차를 더 간결하게 표현하는 데 도움이 되지만 실제로 실행되는 코드는 후자의 코드입니다.”라고 말하는 방법으로 생각하십시오.

The above macro I wrote has its body start with a quasi-quote, so you do not get to appreciate the nuances of evaluation within it. Let me show you this other approach, instead, where I write a macro that lets me define several almost identical interactive functions (Make your interactive function also work from Lisp calls).

제가 작성한 위의 매크로는 quasi-quote로 본문이 시작되므로, 그 안에서의 평가의 뉘앙스를 제대로 이해할 수 없습니다. 대신, 거의 동일한 여러 대화형 함수를 정의할 수 있게 해주는 매크로를 작성하는 다른 접근 방식을 보여드리겠습니다(대화형 함수가 Lisp 호출에서도 작동하도록 만드세요).

(defmacro my-define-command (name &rest expressions)
  "Define command with specifier NAME that evaluates EXPRESSIONS."
  (declare (indent 1))
  (unless (symbolp name)
    (error "I want NAME to be a symbol"))
  (let ((modifined-name (format "modified-version-of-%s" name)))
    `(defun ,(intern modifined-name) ()
       (interactive)
       ,(message "The difference between `%s' and `%s'" modifined-name name)
       ,@expressions)))
 

The my-define-command can be broadly divided into two parts: (i) what gets evaluated outright and (ii) what gets expanded for further evaluation. The latter part starts with the quasi-quote. This distinction is important when we call the macro, because the former part will be executed right away so if we hit the error, it will never expand and then run the EXPRESSIONS. Try pp-macroexpand-last-sexp with the following to see what I mean. For your convenience, I include the macro expansions right below each case. my-define-command 은 크게 두 부분으로 나눌 수 있습니다. (i) 즉시 평가되는 부분과 (ii) 추가 평가를 위해 확장되는 부분입니다. 후자는 quasi-quote로 시작합니다. 이 구분은 매크로를 호출할 때 중요합니다. 전자는 즉시 실행되기 때문에 오류가 발생하면 확장되지 않고 EXPRESSIONS 이 실행되지 않습니다. 제가 의미하는 바를 보려면 다음을 사용하여 pp-macroexpand-last-sexp 을 시도해 보세요. 편의를 위해 각 경우 바로 아래에 매크로 확장을 포함했습니다.

(my-define-command first-demo
  (message "This is what my function does")
  (+ 1 10)
  (message "And this"))
;; =>
;;
;; (defun modified-version-of-first-demo nil
;;   (interactive)
;;   "The difference between ‘modified-version-of-first-demo’ and ‘first-demo’"
;;   (message "This is what my function does")
;;   (+ 1 10)
;;   (message "And this"))
 
 
(my-define-command second-demo
  (list "Protesilaos" "Cyprus")
  (+ 1 1)
  (message "Arbitrary expressions here"))
;; =>
;;
;; (defun modified-version-of-second-demo nil
;;   (interactive)
;;   "The difference between ‘modified-version-of-second-demo’ and ‘second-demo’"
;;   (list "Protesilaos" "Cyprus")
;;   (+ 1 1)
;;   (message "Arbitrary expressions here"))
 
 
(my-define-command "error scenario"
  (list "Will" "Not" "Reach" "This")
  (/ 2 0))
;; => ERROR...
 

Do you need macros? Not always, though there will be cases where a well-defined macro makes your code more elegant. What matters is that you have a sense of how evaluation works so that you do not get confused by all those parentheses. Otherwise you might expect something different to happen than what you actually get.

매크로가 필요하신가요? 항상 그런 것은 아니지만, 잘 정의된 매크로가 코드를 더 우아하게 만드는 경우가 있습니다. 중요한 것은 평가가 어떻게 작동하는지 이해하여 괄호 때문에 혼란스러워하지 않도록 하는 것입니다. 그렇지 않으면 실제로 얻는 것과 다른 일이 일어날 것이라고 예상할 수 있습니다.

9. Mapping through a list of elements

9\. 요소 목록을 통한 매핑

A common routine in programming is to work through a list of items and perform some computation on each of them. Emacs Lisp has the generic while loop, as well as a whole range of more specialised functions to map over a list of elements, such as mapcar, mapc, dolist, seq-filter, seq-remove , and many more. Depending on what you are doing, you map through elements with the intent to produce some side effect and/or to test for a return value (Side effect and return value). I will show you some examples and let you decide which is the most appropriate tool for the task at hand.

프로그래밍에서 흔히 볼 수 있는 루틴은 항목 목록을 탐색하면서 각 항목에 대해 어떤 계산을 수행하는 것입니다. Emacs Lisp에는 일반적인 while 루프뿐만 아니라 mapcar , mapc , dolist , seq-filter , seq-remove 등과 같이 요소 목록을 매핑하는 데 특화된 다양한 함수가 있습니다. 수행하려는 작업에 따라 부작용을 생성하거나 반환 값을 테스트하기 위해 요소를 매핑합니다(부작용 및 반환 값). 몇 가지 예를 보여드리고 어떤 도구가 현재 작업에 가장 적합한지 결정하도록 하겠습니다.

Starting with mapcar, it applies a function to each element of a list. It then takes the return value at each iteration and collects it into a new list. This is the return value of mapcar as a whole. In the following code block, I use mapcar over a list of numbers to increment them by 10 and return a new list of the incremented numbers. mapcar 부터 시작하여 목록의 각 요소에 함수를 적용합니다. 그런 다음 각 반복에서 반환 값을 가져와 새 목록으로 수집합니다. 이것이 mapcar 전체의 반환 값입니다. 다음 코드 블록에서는 숫자 목록에 대해 mapcar 를 사용하여 10 씩 증가시키고 증가된 숫자의 새 목록을 반환합니다.

(mapcar
 (lambda (number)
   (+ 10 number))
 '(1 2 3 4 5))
;; => (11 12 13 14 15)
 

In the code block above, I am using a lambda , else an anonymous function (When to use a named function or a lambda function). Here is the same code, but with an eponymous function, i.e. a named function:

위의 코드 블록에서는 lambda 또는 익명 함수(명명된 함수 또는 람다 함수를 사용해야 하는 경우)를 사용하고 있습니다. 다음은 동일한 코드이지만 이름을 가진 함수, 즉 명명된 함수를 사용한 것입니다.

(defun my-increment-by-ten (number)
  "Add 10 to NUMBER."
  (+ 10 number))
 
(mapcar #'my-increment-by-ten '(1 2 3 4 5))
;; => (11 12 13 14 15)
 

Notice that here we quote the eponymous function (Symbols, balanced expressions, and quoting).

여기서 이름을 가진 함수를 인용한다는 점에 유의하십시오(기호, 균형 잡힌 표현식 및 인용).

The mapcar collects the return values into a new list. Sometimes this is useless. Suppose you want to evaluate a function that saves all unsaved buffers which visit a file. In this scenario, you do not care about accumulating the results: you just want the side effect of saving the buffer outright. To this end, you may use mapc , which always returns the list it operated on: mapcar 은 반환 값을 모아 새로운 목록으로 만듭니다. 때로는 이것이 쓸모없을 수도 있습니다. 파일을 방문하는 저장되지 않은 모든 버퍼를 저장하는 함수를 평가한다고 가정해 봅시다. 이 시나리오에서는 결과 축적에 신경 쓰지 않습니다. 버퍼를 바로 저장하는 부작용만 원할 뿐입니다. 이를 위해 mapc 을 사용할 수 있습니다. 이 함수는 항상 작동한 목록을 반환합니다.

(mapc
 (lambda (buffer)
   (when (and (buffer-file-name buffer)
              (buffer-modified-p buffer))
     (save-buffer)))
 (buffer-list))
 

An alternative to the above is dolist, which is used for side effects but always returns nil :

위와 다른 방법으로 dolist 을 사용할 수 있습니다. 이는 부작용에 사용되지만 항상 nil 을 반환합니다.

(dolist (buffer (buffer-list))
  (when (and (buffer-file-name buffer)
             (buffer-modified-p buffer))
    (save-buffer)))
 

You will notice that the dolist is a macro, so some parts of it seem to behave differently than with basic lists and the evaluation rules that apply to them (Evaluation inside of a macro or special form). This is a matter of getting used to how the code is expressed. dolist 은 매크로이므로 일부 부분이 기본 목록 및 해당 목록에 적용되는 평가 규칙과 다르게 작동하는 것처럼 보일 것입니다(매크로 또는 특수 형식 내부의 평가). 이는 코드 표현 방식에 익숙해지는 문제입니다.

When to use a dolist as opposed to a mapc is a matter of style. If you are using a named function, a mapc looks cleaner to my eyes. Otherwise a dolist is easier to read. Here is my approach with some pseudo-code: mapc 대신 dolist 을 언제 사용해야 하는지는 스타일 문제입니다. 명명된 함수를 사용하는 경우 mapc 이 보기에 더 깔끔해 보입니다. 그렇지 않으면 dolist 이 읽기 쉽습니다. 다음은 몇 가지 의사 코드를 사용한 제 접근 방식입니다.

;; I like this:
(mapc #'NAMED-FUNCTION LIST)
 
;; I also like a `dolist' instead of a `mapc' with a `lambda':
(dolist (element LIST)
  (OPERATE-ON element))
 
;; I do not like this:
(mapc
 (lambda (element)
   (OPERATE-ON element))
 LIST)
 

While dolist and mapc are for side effects, you can still employ them in the service of accumulating results, with the help of let and related forms (Control flow with if-let* and friends). Depending on the specifics, this approach may make more sense than relying on a mapcar . Here is an annotated sketch: dolistmapc 은 부작용을 위한 것이지만, let 및 관련 형식(제어 흐름과 if-let* 등)을 사용하여 결과를 축적하는 데 여전히 사용할 수 있습니다. 특정 상황에 따라 이 접근 방식이 mapcar 에 의존하는 것보다 더 합리적일 수 있습니다. 다음은 주석이 달린 스케치입니다.

;; Start with an empty list of `found-strings'.
(let ((found-strings nil))
  ;; Use `dolist' to test each element of the list '("Protesilaos" 1 2 3 "Cyprus").
  (dolist (element '("Protesilaos" 1 2 3 "Cyprus"))
    ;; If the element is a string, then `push' it to the `found-strings', else skip it.
    (when (stringp element)
      (push element found-strings)))
  ;; Now that we are done with the `dolist', return the new value of `found-strings'.
  found-strings)
;; => ("Cyprus" "Protesilaos")
 
 
;; As above but reverse the return value, which makes more sense:
(let ((found-strings nil))
  (dolist (element '("Protesilaos" 1 2 3 "Cyprus"))
    (when (stringp element)
      (push element found-strings)))
  (nreverse found-strings))
;; => ("Protesilaos" "Cyprus")
 

For completeness, the previous example would have to be done as follows with the use of mapcar :

완전성을 위해, 이전 예제는 mapcar 을 사용하여 다음과 같이 수행해야 합니다.

(mapcar
 (lambda (element)
   (when (stringp element)
     element))
 '("Protesilaos" 1 2 3 "Cyprus"))
;; => ("Protesilaos" nil nil nil "Cyprus")
 
 
(delq nil
      (mapcar
       (lambda (element)
         (when (stringp element)
           element))
       '("Protesilaos" 1 2 3 "Cyprus")))
;; => ("Protesilaos" "Cyprus")
 

Because mapcar happily accumulates all the return values, it returns a list that includes nil. If you wanted that, you would probably not even bother with the when clause there. The delq is thus applied to the return value of the mapcar to delete all the instances of nil. Now compare this busy work to seq-filter : mapcar 은 모든 반환 값을 문제없이 축적하므로 nil 을 포함하는 목록을 반환합니다. 그것을 원했다면 아마도 when 절을 사용하지 않았을 것입니다. 따라서 delqmapcar 의 반환 값에 적용되어 nil 의 모든 인스턴스를 삭제합니다. 이제 이 번거로운 작업을 seq-filter 과 비교해 보십시오.

(seq-filter #'stringp '("Protesilaos" 1 2 3 "Cyprus"))
;; => ("Protesilaos" "Cyprus")
 

The seq-filter is the best tool when all you need is to test if the element satisfies a predicate function and then return that element. But you cannot return something else. Whereas mapcar will take any return value without complaints, such as the following: seq-filter 은 요소가 술어 함수를 만족하는지 테스트한 다음 해당 요소를 반환하는 데 필요한 전부일 때 가장 좋은 도구입니다. 하지만 다른 것을 반환할 수는 없습니다. 반면에 mapcar 은 다음과 같이 불만 없이 모든 반환 값을 취합니다.

(delq nil
      (mapcar
       (lambda (element)
         (when (stringp element)
           ;; `mapcar' accumulates any return value, so we can change
           ;; the element to generate the results we need.
           (upcase element)))
       '("Protesilaos" 1 2 3 "Cyprus")))
;; => ("PROTESILAOS" "CYPRUS")
 
(seq-filter
 (lambda (element)
   (when (stringp element)
     ;; `seq-filter' only returns elements that have a non-nil return
     ;; value here, but it returns the elements, not what we return
     ;; here.  In other words, this `lambda' does unnecessary work.
     (upcase element)))
 '("Protesilaos" 1 2 3 "Cyprus"))
;; => ("Protesilaos" "Cyprus")
 

How you go about mapping over a list of elements will depend on what you are trying to do. There is no one single function that does everything for you. Understand the nuances and you are good to go. Oh, and do look into the built-in seq library (use M-x ( execute-extended-command), invoke find-library, and then search for seq). You are now looking at the source code of seq.el: it defines plenty of functions like seq-take, seq-find, seq-union. Another way is to invoke the command shortdoc and read about the documentation groups list as well as sequence .

리스트의 요소들을 매핑하는 방법은 무엇을 하려고 하는지에 따라 달라집니다. 모든 것을 한 번에 처리해주는 단일 함수는 없습니다. 뉘앙스를 이해하면 됩니다. 아, 그리고 내장된 seq 라이브러리를 살펴보세요 (M-x ( execute-extended-command )을 사용하고, find-library 를 호출한 다음, seq 을 검색하세요). 이제 seq.el 의 소스 코드를 보고 있습니다. 여기에는 seq-take , seq-find , seq-union 과 같은 많은 함수가 정의되어 있습니다. 또 다른 방법은 shortdoc 명령을 호출하고 listsequence 문서 그룹에 대해 읽어보는 것입니다.

10\. 마지막 검색의 매치 데이터

As you work with Emacs Lisp, you will encounter the concept of “match data” and the concomitant functions match-data, match-beginning, match-string, and so on. These refer to the results of the last search, which is typically performed by the functions re-search-forward, looking-at, string-match , and related. Each time you perform a search, the match data gets updated. Be mindful of this common side effect (Side effect and return value). If you forget about it, chances are your code will not do the right thing. Emacs Lisp을 사용하다 보면 “매치 데이터”라는 개념과 그에 따른 함수인 match-data , match-beginning , match-string 등을 접하게 됩니다. 이것들은 일반적으로 re-search-forward , looking-at , string-match 및 관련 함수에 의해 수행되는 마지막 검색의 결과를 나타냅니다. 검색을 수행할 때마다 매치 데이터가 업데이트됩니다. 이 일반적인 부작용(부작용 및 반환 값)을 명심하십시오. 잊어버리면 코드가 제대로 작동하지 않을 가능성이 큽니다.

In the following code block, I define a function that performs a search in the current buffer and returns a list of match data without text properties, where relevant (Text has its own properties).

다음 코드 블록에서는 현재 버퍼에서 검색을 수행하고 관련 있는 경우 텍스트 속성이 없는 매치 데이터 목록을 반환하는 함수를 정의합니다(텍스트 자체에 속성이 있음).

(defun my-get-match-data (regexp)
  "Search forward for REGEXP and return its match data, else nil."
  (when (re-search-forward regexp nil t)
    (list
     :beginning (match-beginning 0)
     :end (match-end 0)
     :string (match-string-no-properties 0))))
 

You may then call it with a string argument, representing an Emacs Lisp regular expression:

그런 다음 Emacs Lisp 정규식을 나타내는 문자열 인수를 사용하여 호출할 수 있습니다.

(my-get-match-data "Protesilaos.*Cyprus")
 

If the regular expression matches, then you get the match data. Here is some sample text:

정규 표현식이 일치하면 일치 데이터를 얻을 수 있습니다. 다음은 샘플 텍스트입니다.

Protesilaos lives in the mountains of Cyprus.
 

Place the cursor before that text and use M-: ( eval-expression) to evaluate my-get-match-data with the regexp I show above. You will get a return value, as intended.

해당 텍스트 앞에 커서를 놓고 M-: ( eval-expression )를 사용하여 위에 표시된 정규식으로 my-get-match-data 을 평가합니다. 의도한 대로 반환 값을 얻게 됩니다.

The way my-get-match-data is written, it does two things: (i) it has the side effect of moving the cursor to the end of the text it found and (ii) it returns a list with the match data I specified. There are many scenaria where you do not want the aforementioned side effect: the cursor should stay where it is. As such, you can wrap your code in a save-excursion (Switching to another buffer, window, or narrowed state): it will do what it must and finally restore the point (Run some code or fall back to some other code): my-get-match-data 가 작성된 방식은 두 가지 작업을 수행합니다. (i) 찾은 텍스트의 끝으로 커서를 이동시키는 부작용이 있고 (ii) 지정한 일치 데이터가 있는 목록을 반환합니다. 위에서 언급한 부작용을 원하지 않는 시나리오가 많이 있습니다. 즉, 커서가 있던 위치에 유지되어야 합니다. 따라서 코드를 save-excursion (다른 버퍼, 창 또는 좁혀진 상태로 전환)로 래핑할 수 있습니다. 그러면 수행해야 할 작업을 수행하고 최종적으로 point (일부 코드를 실행하거나 다른 코드로 대체)를 복원합니다.

(defun my-get-match-data (regexp)
  "Search forward for REGEXP and return its match data, else nil."
  (save-excursion ; we wrap our code in a `save-excursion' to inhibit the side effect
    (when (re-search-forward regexp nil t)
      (list
       :beginning (match-beginning 0)
       :end (match-end 0)
       :string (match-string-no-properties 0)))))
 

If you evaluate this version of my-get-match-data and then retry the function call I had above, you will notice how you get the expected return value without the side effect of the cursor moving to the end of the matching text. In practice, this is a useful tool that may be combined with save-match-data . Imagine you want to do a search forward inside of another search you are performing, such as to merely test if there is a match for a regular expression in the context, but need to inhibit the modification of the match data you planned to operate on. As such:

만약 이 버전의 my-get-match-data 을 평가한 다음 위에서 했던 함수 호출을 다시 시도하면 커서가 일치하는 텍스트의 끝으로 이동하는 부작용 없이 예상되는 반환 값을 얻을 수 있습니다. 실제로 이것은 save-match-data 과 함께 사용할 수 있는 유용한 도구입니다. 예를 들어 컨텍스트에서 정규 표현식과 일치하는지 여부를 테스트하기 위해 수행 중인 다른 검색 내에서 앞으로 검색을 수행하고 싶지만 작업할 계획이었던 일치 데이터의 수정을 억제해야 한다고 상상해 보십시오. 이와 같이:

(defun my-get-match-data-with-extra-check (regexp)
  "Search forward for REGEXP followed by no spaces and return its match data, else nil."
  (save-excursion
    (when (and (re-search-forward regexp nil t)
               (save-match-data (not (looking-at "[\s\t]+"))))
      ;; Return the match data of the first search.  The second one
      ;; which tests for spaces or tabs is just an extra check, but we
      ;; do not want to use its match data, hence the `save-match-data'
      ;; around it.
      (list
       :beginning (match-beginning 0)
       :end (match-end 0)
       :string (match-string-no-properties 0)))))
 

Evaluate the function my-get-match-data-with-extra-check and then call with M-: ( eval-expression ) to test that it returns a non- nil value with the second example below, but not the first one. This is the expected outcome.

함수 my-get-match-data-with-extra-check 을 평가한 다음 M-: ( eval-expression )로 호출하여 아래의 두 번째 예제에서는 nil 가 아닌 값을 반환하지만 첫 번째 예제에서는 그렇지 않은지 테스트하십시오. 이것이 예상되는 결과입니다.

(my-get-match-data-with-extra-check "Protesilaos.*Cyprus")
;; => nil
 
 
;; Protesilaos, also known as "Prot", lives in the mountains of Cyprus   .
 
(my-get-match-data-with-extra-check "Protesilaos.*Cyprus")
;; => (:beginning 41988 :end 42032 :string "Protesilaos lives in the mountains of Cyprus")
 
 
;; Protesilaos lives in the mountains of Cyprus.
 

11. Switching to another buffer, window, or narrowed state

11\. 다른 버퍼, 창 또는 좁혀진 상태로 전환

As you use Emacs Lisp to do things programmatically, you encounter cases where you need to move away from where you are. You may have to switch to another buffer, change to the window of a given buffer, or even modify what is visible in the buffer you are editing. At all times, this involves one or more side effects which, most probably, should be undone when your function finishes its job (Side effect and return value). Emacs Lisp을 사용하여 프로그래밍 방식으로 작업을 수행할 때 현재 위치에서 벗어나야 하는 경우가 발생합니다. 다른 버퍼로 전환하거나, 주어진 버퍼의 창으로 변경하거나, 편집 중인 버퍼에서 보이는 내용을 수정해야 할 수도 있습니다. 항상 이것은 하나 이상의 부작용을 수반하며, 대부분의 경우 함수가 작업을 완료하면 취소해야 합니다 (부작용 및 반환 값).

Perhaps the most common case is to restore the point. You have some code that moves back or forth in the buffer to perform a match for a given piece of text. But then, you need to leave the cursor where it originally was, otherwise the user will lose their orientation. Wrap your code in a save-excursion and you are good to go, as I show elsewhere (The match data of the last search):

가장 흔한 경우는 point 을 복원하는 것입니다. 특정 텍스트와 일치하는 항목을 찾기 위해 버퍼에서 앞뒤로 이동하는 코드가 있다고 가정합니다. 하지만 커서가 원래 위치에 있지 않으면 사용자가 방향 감각을 잃게 됩니다. 코드를 save-excursion 로 래핑하면 다른 곳에서 보여주듯이 문제 없습니다 (마지막 검색의 일치 데이터):

(save-excursion ; restore the `point' after you are done
  MOVE-AROUND-IN-THIS-BUFFER)
 

Same principle for save-window-excursion, which allows you to select another window, such as with select-window , move around in its buffer, and then restore the windows as they were: save-window-excursion 도 동일한 원리가 적용됩니다. 예를 들어 select-window 와 같이 다른 창을 선택하고 해당 버퍼를 탐색한 다음 창을 원래대로 복원할 수 있습니다.

(save-window-excursion
  (select-window SOME-WINDOW)
  MOVE-AROUND-IN-THIS-BUFFER)
 

The save-restriction allows you to restore the current narrowing state of the buffer. You may then choose to either widen or narrow-to-region (and related commands like org-narrow-to-subtree ), do what you must, and then restore the buffer to its original state. save-restriction 을 사용하면 버퍼의 현재 좁히기 상태를 복원할 수 있습니다. 그런 다음 widen 또는 narrow-to-region (및 org-narrow-to-subtree 와 같은 관련 명령)를 선택하여 필요한 작업을 수행한 다음 버퍼를 원래 상태로 복원할 수 있습니다.

;; Here we assume that we start in a widened state.  Then we narrow to
;; the current Org heading to get all of its contents as one massive
;; string.  Then we widen again, courtesy of `save-restriction'.
(save-restriction
  (org-narrow-to-subtree)
  (buffer-string))
 

Depending on the specifics, you will want to combine the aforementioned. Beware that the documentation of save-restriction tells you to use save-excursion as the outermost call. Other than that, you will also find cases that require a different approach to perform some conditional behaviour (Run some code or fall back to some other code).

구체적인 내용에 따라 위에 언급한 내용을 결합하고 싶을 것입니다. save-restriction 의 설명서에는 save-excursion 을 가장 바깥쪽 호출로 사용하라고 나와 있으니 주의하십시오. 그 외에도 일부 조건부 동작을 수행하기 위해 다른 접근 방식이 필요한 경우도 있습니다 (일부 코드를 실행하거나 다른 코드로 대체).

12. Basic control flow with if, cond , and others

12\. if , cond 등을 사용한 기본 제어 흐름

You do not need any conditional logic to perform basic operations. For example, if you write a command that moves 15 lines down, it will naturally stop at the end of the buffer when it cannot move past the number you specified. Using defun, you write an interactive function (i.e. a “command”) to unconditionally move down 15 lines using forward-line internally (call it with a negative number to move in the opposite direction):

기본 작업을 수행하는 데 조건부 논리가 필요하지 않습니다. 예를 들어 15줄 아래로 이동하는 명령을 작성하면 지정한 숫자를 지나서 이동할 수 없을 때 버퍼 끝에서 자연스럽게 멈춥니다. defun 을 사용하면 forward-line 을 내부적으로 사용하여 15줄 아래로 무조건 이동하는 대화형 함수(“명령”)를 작성합니다(반대 방향으로 이동하려면 음수를 사용).

(defun my-15-lines-down ()
  "Move at most 15 lines down."
  (interactive)
  (forward-line 15))
 

The my-15-lines-down is about as simple as it gets: it wraps around a basic function and passes to it a fixed argument, in this case the number 15 . Use M-x ( execute-extended-command) and then call this command by its name. It works! Things get more involved as soon as you decide to perform certain actions only once a given condition is met. This “control flow” between different branches of a logical sequence is expressed with if, when, unless, and cond, among others. Depending on the specifics of the case, and as well as or may suffice. my-15-lines-down 은 기본 함수를 감싸고 고정된 인수인 15 을 전달하는 가장 간단한 형태입니다. M-x( execute-extended-command )를 사용하여 이 명령을 이름으로 호출합니다. 잘 작동합니다! 특정 조건이 충족될 때만 특정 작업을 수행하기로 결정하는 즉시 상황이 더 복잡해집니다. 논리적 순서의 다른 분기 간의 이러한 “제어 흐름”은 if , when , unlesscond 등으로 표현됩니다. 경우에 따라 andor 로도 충분할 수 있습니다.

How about you make your my-15-lines-down a bit smarter? When it is at the absolute end of the buffer, have it move 15 lines up. Why? Because this is a demonstration, so why not? The predicate function that tests if the point is at the end of the buffer is eobp . A “predicate” is a function that returns true, technically non- nil, when its condition is met, else it returns nil (Side effect and return value). As for the weird name, the convention in Emacs Lisp is to end predicate functions with the p suffix: if the name of the function consists of multiple words, typically separated by dashes, then the predicate function is named NAME-p, like string-match-p, otherwise it is NAMEp, like stringp . my-15-lines-down 을 좀 더 똑똑하게 만들어 보는 건 어떨까요? 버퍼의 맨 끝에 있을 때 15줄 위로 이동하도록 합니다. 왜냐구요? 이것은 시연이니까요, 왜 안 되겠어요? 포인트가 버퍼의 끝에 있는지 테스트하는 술어 함수는 eobp 입니다. “술어”는 조건이 충족되면 true(기술적으로는 nil 이 아님)를 반환하고 그렇지 않으면 nil 을 반환하는 함수입니다(부작용 및 반환 값). 이상한 이름에 대해 말하자면, Emacs Lisp의 규칙은 술어 함수를 p 접미사로 끝내는 것입니다. 함수 이름이 여러 단어로 구성되어 있고 일반적으로 대시로 구분되는 경우 술어 함수는 NAME-p 와 같이 string-match-p 로 이름이 지정되고 그렇지 않으면 NAMEp 과 같이 stringp 로 이름이 지정됩니다.

(defun my-15-lines-down-or-up ()
  "Move at most 15 lines down or go back if `eobp' is non-nil."
  (interactive)
  (if (eobp)
      (forward-line -15)
    (forward-line 15)))
 

Evaluate this function, then type M-x ( execute-extended-command) and invoke my-15-lines-down-or-up to get a feel for it. Below is a similar idea, which throws and error and exits what it was doing if eobp returns non- nil :

이 함수를 평가한 다음 M-x ( execute-extended-command )를 입력하고 my-15-lines-down-or-up 을 호출하여 감을 잡아보세요. 다음은 유사한 아이디어로, eobpnil 이 아닌 값을 반환하면 오류를 발생시키고 하던 작업을 종료합니다.

(defun my-15-lines-down-or-error ()
  "Throw an error if `eobp' returns non-nil, else move 15 lines down."
  (interactive)
  (if (eobp)
      (error "Already at the end; will not move further")
    (forward-line 15)))
 

A quirk of Emacs Lisp, which may be a feature all along, is how indentation is done. Just mark the code you have written and type TAB: Emacs will take care to indent it the way it should be done. In the case of the if statement, the “then” part is further in than the “else” part of the logic. There is no special meaning to this indentation: you could write everything on a single line like (if COND THIS ELSE) , which looks like your typical list, by the way (Symbols, balanced expressions, and quoting). What the indentation does is help you identify imbalances in your parentheses. If the different expressions all line up in a way that looks odd, then you are most probably missing a parentheses or have too many of them. Generally, expressions at the same level will all line up the same way. Those deeper in will have more indentation, and so on. Experience will allow you to spot mistakes with mismatching parentheses. But even if you do not identify them, you will get an error eventually. Rest assured! Emacs Lisp의 특이한 점은 (어쩌면 처음부터 기능이었을 수도 있지만) 들여쓰기가 수행되는 방식입니다. 작성한 코드를 선택하고 TAB 키를 누르면 Emacs가 올바르게 들여쓰기를 처리합니다. if 구문의 경우 “then” 부분은 논리의 “else” 부분보다 더 안쪽에 있습니다. 이 들여쓰기에는 특별한 의미가 없습니다. (if COND THIS ELSE) 처럼 모든 것을 한 줄에 쓸 수도 있습니다. 그런데 이것은 일반적인 목록처럼 보입니다(기호, 균형 잡힌 표현식, 인용). 들여쓰기가 하는 역할은 괄호의 불균형을 식별하는 데 도움이 된다는 것입니다. 서로 다른 표현식이 이상하게 정렬되어 보이면 괄호가 누락되었거나 너무 많을 가능성이 큽니다. 일반적으로 같은 수준의 표현식은 모두 같은 방식으로 정렬됩니다. 더 깊이 들어갈수록 들여쓰기가 더 많아집니다. 경험을 통해 일치하지 않는 괄호의 실수를 발견할 수 있습니다. 하지만 발견하지 못하더라도 결국에는 오류가 발생할 것입니다. 안심하세요!

The way if is written is like a function that takes two or more arguments. The “or more” all counts as part of the “else” logic. As such, (if COND THIS) has no “else” consequence, while (if COND THIS ELSE1 ELSE2 ELSE3) will run ELSE1, ELSE2, and ELSE3 in order as part of the “else” branch. Here is how this looks once you factor in proper indentation: if 이 작성되는 방식은 두 개 이상의 인수를 취하는 함수와 같습니다. “or more”는 모두 “else” 논리의 일부로 간주됩니다. 따라서 (if COND THIS) 은 “else” 결과가 없는 반면, (if COND THIS ELSE1 ELSE2 ELSE3) 는 “else” 분기의 일부로 ELSE1 , ELSE2 , ELSE3 를 순서대로 실행합니다. 적절한 들여쓰기를 고려하면 다음과 같이 보입니다.

(if COND
    THIS
  ELSE1
  ELSE2
  ELSE3)
 

Now what if the THIS part needs to be more than one function call? Elisp has the progn form, which you can use to wrap function calls and pass them as a single argument. Putting it all together, your code will now look this like:

이제 THIS 부분이 둘 이상의 함수 호출이 필요한 경우 어떻게 해야 할까요? Elisp에는 함수 호출을 래핑하고 단일 인수로 전달하는 데 사용할 수 있는 progn 형식이 있습니다. 모두 합치면 코드는 다음과 같이 보일 것입니다.

(if COND
    (progn
      THIS1
      THIS2
      THIS3)
  ELSE1
  ELSE2
  ELSE3)
 

If you do not need the “else” part, use when to express your intention. Internally, this is a macro which actually stands for (if COND (progn EXPRESSIONS)), where EXPRESSIONS is one or more expressions. A when looks like this: “else” 부분이 필요하지 않으면 when 을 사용하여 의도를 표현하십시오. 내부적으로 이것은 실제로 (if COND (progn EXPRESSIONS)) 을 나타내는 매크로이며, 여기서 EXPRESSIONS 는 하나 이상의 표현식입니다. when 은 다음과 같습니다.

(when COND
  THIS1
  THIS2
  THIS3)
 

Similarly, the unless has the meaning of (when (not COND) EXPRESSIONS). It, too, is a macro that expands to an if statement:

마찬가지로, unless(when (not COND) EXPRESSIONS) 의 의미를 갖습니다. 이것 또한 if 문으로 확장되는 매크로입니다.

(unless COND
  THIS1
  THIS2
  THIS3)
 

When the condition you are testing for has multiple parts, you can rely on and as well as or :

테스트하는 조건에 여러 부분이 있는 경우 and 뿐만 아니라 or 에도 의존할 수 있습니다.

(when (or THIS THAT)
  EXPRESSIONS)
 
(when (and THIS THAT)
  EXPRESSIONS)
 
(when (or (and THIS THAT) OTHER)
  EXPRESSIONS)
 

Depending on the specifics of the case, the combination of multiple if, when, or, and will look awkward. You can break down the logic to distinct conditions, which are tested in order from top to bottom, using cond. The way cond is written is as a list of lists, which do not need quoting (Evaluation inside of a macro or special form). In abstract, it looks like this:

경우에 따라 여러 if , when , or , and 의 조합이 어색해 보일 수 있습니다. cond 를 사용하여 위에서 아래로 순서대로 테스트되는 고유한 조건으로 논리를 나눌 수 있습니다. cond 가 작성되는 방식은 인용 부호가 필요 없는 목록의 목록입니다(매크로 또는 특수 형식 내부의 평가). 추상적으로는 다음과 같습니다.

(cond
 (CONDITION1
  CONSEQUENCES1)
 (CONDITION2
  CONSEQUENCES2)
 (CONDITION3
  CONSEQUENCES3)
 (t
  CONSEQUENCES-FALLBACK))
 

Each of the consequences can be any number of expressions, like you saw above with when. This is a toy function to show how cond behaves:

각각의 결과는 위에서 when 에서 본 것처럼 여러 개의 표현식이 될 수 있습니다. 다음은 cond 의 동작 방식을 보여주는 간단한 함수입니다.

(defun my-toy-cond (argument)
  "Return a response depending on the type of ARGUMENT."
  (cond
   ((and (stringp argument)
         (string-blank-p argument))
    (message "You just gave me a blank string; try harder!"))
   ((stringp argument)
    (message "I see you can do non-blanks string; I call that progress."))
   ((null argument)
    (message "Yes, the nil is an empty list like (), but do not worry about it"))
   ((listp argument)
    (message "Oh, I see you are in the flow of using lists!"))
   ((symbolp argument)
    (message "What's up with the symbols, mate?"))
   ((natnump argument)
    (message "I fancy those natural numbers!"))
   ((numberp argument)
    (message "You might as well be a math prodigy!"))
   (t
    (message "I have no idea what type of thing your argument `%s' is" argument))))
 

I want you to evaluate it and pass it different arguments to test what it does (Evaluate Emacs Lisp). Here are two examples:

이 함수를 평가하고 다양한 인수를 전달하여 무엇을 하는지 테스트해 보세요(Emacs Lisp 평가). 다음은 두 가지 예입니다.

(my-toy-cond "")
;; => "You just gave me a blank string; try harder!"
 
(my-toy-cond '(1 2 3))
;; => "Oh, I see you are in the flow of using lists!"
 

All of the above are common in Emacs Lisp. Another powerful macro is pcase , which we will consider separately due to its particularities (Pattern match with pcase and related).

위의 모든 것은 Emacs Lisp에서 흔히 사용됩니다. 또 다른 강력한 매크로는 pcase 이며, 그 특수성으로 인해 별도로 고려할 것입니다(패턴 일치와 pcase 및 관련 항목).

13. Control flow with if-let* and friends

13\. if-let* 등을 사용한 제어 흐름

The let and let* declare variables that are available only within the current scope, else the BODY of the let . As such: letlet* 은 현재 범위 내에서만 사용할 수 있는 변수를 선언합니다. 그렇지 않으면 letBODY 입니다. 다음과 같습니다:

(let BINDINGS
  BODY)
 
(let ((variable1 value1)
      (variable2 value2))
  BODY)
 

The BINDINGS is a list of lists, which does not need to be quoted (Evaluation inside of a macro or special form). While BODY consists of one or more expressions, which I have also named EXPRESSIONS elsewhere in this book. The difference between let and let* (pronounced “let star”) is that the latter makes earlier bindings available to later bindings. Like this: BINDINGS 은 리스트의 리스트이며, 인용할 필요가 없습니다 (매크로 또는 특수 형식 내부의 평가). 반면 BODY 은 하나 이상의 표현식으로 구성되어 있으며, 이 책의 다른 곳에서 EXPRESSIONS 라고도 명명했습니다. letlet* (“let star”라고 발음)의 차이점은 후자가 이전 바인딩을 이후 바인딩에서 사용할 수 있게 한다는 것입니다. 다음과 같습니다:

;; This works because `greeting' can access `name' and `country',
;; courtesy of `let*':
(let* ((name "Protesilaos")
       (country "Cyprus")
       (greeting (format "Hello %s of %s" name country)))
  (DO-STUFF-WITH greeting))
 
;; But this fails...
(let ((name "Protesilaos")
      (country "Cyprus")
      (greeting (format "Hello %s of %s" name country)))
  (DO-STUFF-WITH greeting))
 

Sometimes what you want to do is create those bindings if—and only if—they are all non- nil. If their value is nil , then they are useless to you, in which case you do something else (Basic control flow with if, cond , and others). Values may or may not be nil when you are creating a binding with the return value of a function call or some other variable. You could always write code like this:

때로는 해당 바인딩이 모두 non- nil 인 경우에만 해당 바인딩을 생성하고 싶을 수 있습니다. 해당 값이 nil 이면 쓸모가 없으므로 다른 작업을 수행합니다 ( if , cond 등을 사용한 기본 제어 흐름). 함수 호출의 반환 값 또는 다른 변수로 바인딩을 생성할 때 값이 nil 일 수도 있고 아닐 수도 있습니다. 항상 다음과 같은 코드를 작성할 수 있습니다:

(let ((variable1 (SOME-FUNCTION SOME-ARGUMENT))
      (variable2 (OTHER-FUNCTION OTHER-ARGUMENT)))
  (if (and variable1 variable2) ; simply test both for non-nil
      THIS
    ELSE))
 

But you can do the same with if-let*, where the THIS part runs only if all the bindings are non- nil :

그러나 if-let* 을 사용하여 동일하게 수행할 수 있습니다. 여기서 THIS 부분은 모든 바인딩이 non- nil 인 경우에만 실행됩니다:

(if-let* ((variable1 (SOME-FUNCTION SOME-ARGUMENT))
          (variable2 (OTHER-FUNCTION OTHER-ARGUMENT)))
    THIS
  ELSE)
 

In the ELSE part, the bindings variable1 and variable2 do not exist: they only exist for the THIS part of the code. ELSE 부분에서는 바인딩 variable1variable2 가 존재하지 않습니다. 해당 바인딩은 코드의 THIS 부분에만 존재합니다.

The when-let* is the same as when, meaning that it has no “else” logic. If one of its bindings is nil, then the whole when-let* returns nil . No need to belabour that point. when-let*when 과 동일합니다. 즉, “else” 논리가 없습니다. 바인딩 중 하나가 nil 이면 전체 when-let*nil 를 반환합니다. 더 자세히 설명할 필요는 없습니다.

As you dig dipper into the Emacs Lisp ecosystem, you will come across uses of if-let* that (i) create multiple bindings like let or let* but (ii) also call a predicate function to test if they should continue with the THIS part of their logic. Remember that if-let* goes straight to ELSE if one of its bindings returns nil . Consider this example: Emacs Lisp 생태계를 더 깊이 파고들면 if-let* 을 사용하는 경우를 보게 될 것입니다. (i) let 또는 let* 와 같이 여러 바인딩을 생성하지만 (ii) 또한 술어 함수를 호출하여 해당 로직의 THIS 부분을 계속해야 하는지 테스트합니다. 바인딩 중 하나가 nil 을 반환하면 if-let* 는 바로 ELSE 로 이동합니다. 다음 예를 고려하십시오.

(if-let* ((variable1 (SOME-FUNCTION SOME-ARGUMENT))
          ;; The _ signifies intent: "do not bind this; I only care
          ;; about the return value being non-nil".  What we are doing
          ;; here is test if `variable1' is a string: if it is, we
          ;; continue with the bindings, otherwise we move to the ELSE
          ;; part of the code.
          (_ (stringp variable1))
          (variable2 (OTHER-FUNCTION OTHER-ARGUMENT)))
    THIS
  ELSE)
 

There is no inherently superior way of doing things. It is a matter of using the right tool for the task at hand. Sometimes you want the bindings to be created, even if their value is nil . Choose what makes sense.

본질적으로 더 우월한 방법은 없습니다. 주어진 작업에 적합한 도구를 사용하는 문제입니다. 때로는 값이 nil 이더라도 바인딩을 생성하고 싶을 수 있습니다. 합리적인 것을 선택하십시오.

14\. pcase 및 관련 항목을 사용한 패턴 매칭

Once you get in the flow of expressing your thoughts with Emacs Lisp, you will be fluent in the use of if, cond , and the like (Basic control flow with if, cond , and others). You might even get more fancy if if-let* (Control flow with if-let* and friends). However you go about it, there are some cases that arguably benefit from more succinct expressions. This is where pcase comes in. At its more basic formulation, it is like cond, in that it tests the return value of a given expression against a list of conditions. Here is an example that compared the buffer-local value of the variable major-mode for equality against a couple of known symbols: Emacs Lisp로 생각을 표현하는 흐름을 타게 되면 if , cond 등 ( if , cond 등을 사용한 기본 제어 흐름)을 유창하게 사용할 수 있습니다. 심지어 if-let* ( if-let* 등을 사용한 제어 흐름)를 사용하면 더 멋있어질 수도 있습니다. 어떤 방법을 사용하든 간에 더 간결한 표현으로 이점을 얻을 수 있는 경우가 있습니다. 여기서 pcase 이 등장합니다. 가장 기본적인 형태로 보면 주어진 표현식의 반환 값을 조건 목록과 비교한다는 점에서 cond 과 유사합니다. 다음은 변수 major-mode 의 버퍼 로컬 값을 몇 가지 알려진 심볼과 비교하여 동일한지 확인하는 예제입니다.

(pcase major-mode
  ('org-mode (message "You are in Org"))
  ('emacs-lisp-mode (message "You are in Emacs Lisp"))
  (_ (message "You are somewhere else")))
 

The above is the same idea as this cond :

위의 내용은 다음 cond 와 같은 아이디어입니다.

(cond
 ((eq major-mode 'org-mode)
  (message "You are in Org"))
 ((eq major-mode 'emacs-lisp-mode)
  (message "You are in Emacs Lisp"))
 (t
  (message "You are somewhere else")))
 

Some programmers may argue that pcase is more elegant. I think it is true in this specific example, though I remain flexible and practical: I will use whatever makes more sense for the code I am writing. While on the topic of elegance, I should inform you that practically all of the conditional logic can be done in a way that may seem unexpected. Consider how my examples in this book make repetitive use of message , when in reality the only part that changes is the actual string/argument passed to that function. This will work just as well:

일부 프로그래머는 pcase 이 더 우아하다고 주장할 수 있습니다. 이 특정 예에서는 사실이라고 생각하지만 유연하고 실용적인 태도를 유지합니다. 작성하는 코드에 더 적합한 것을 사용할 것입니다. 우아함에 대해 이야기하는 김에 조건부 논리의 거의 모든 부분을 예상치 못한 방식으로 수행할 수 있다는 것을 알려드려야겠습니다. 이 책의 예제에서 message 을 반복적으로 사용하는 방법을 살펴보십시오. 실제로는 해당 함수에 전달되는 실제 문자열/인수만 변경됩니다. 이것도 똑같이 잘 작동합니다.

(message
 (pcase major-mode
   ('org-mode "You are in Org")
   ('emacs-lisp-mode "You are in Emacs Lisp")
   (_ "You are somewhere else")))
 

Same idea for if, when , and the rest. if , when 및 나머지에 대해서도 동일한 아이디어가 적용됩니다.

Back to the topic of what pcase does differently. If you read its documentation, you will realise that it has its own mini language, or “domain-specific language” (DSL). This is common for macros (Evaluation inside of a macro or special form). They define how evaluation is done and what sort of expressions are treated specially. Let me then gift you this toy function that illustrates some of the main features of the DSL now under consideration: pcase 이 무엇을 다르게 하는지에 대한 주제로 돌아가겠습니다. 해당 문서를 읽어보면 자체 미니 언어 또는 “도메인 특정 언어”(DSL)를 가지고 있다는 것을 알게 될 것입니다. 이는 매크로에서 흔히 볼 수 있습니다(매크로 또는 특수 형식 내부의 평가). 그들은 평가가 어떻게 수행되는지, 어떤 종류의 표현식이 특별하게 취급되는지 정의합니다. 이제 고려 중인 DSL의 주요 기능을 보여주는 이 장난감 함수를 선물로 드리겠습니다.

(defun my-toy-pcase (argument)
  "Use `pcase' to return an appropriate response for ARGUMENT."
  (pcase argument
    (`(,one ,_ ,three)
     (message "List where first element is `%s', second is ignored, third is `%s'" one three))
    (`(,one . ,two)
     (message "Cons cell where first element is `%s' and second is `%s'" one two))
    ((pred stringp)
     (message "The argument is a string of some sort"))
    ('hello
     (message "The argument is equal to the symbol `hello'"))
    (_ (message "This is the fallback"))))
 

Go ahead and evaluate that function and then try it out (Evaluate Emacs Lisp). Below are a couple of examples:

해당 함수를 평가한 다음 사용해 보십시오(Emacs Lisp 평가). 아래에 몇 가지 예가 있습니다.

(my-toy-pcase '("Protesilaos" "of" "Cyprus"))
;; => "List where first element is ‘Protesilaos’, second is ignored, third is ‘Cyprus’"
 
(my-toy-pcase '("Protesilaos" . "Cyprus"))
;; => "Cons cell where first element is ‘Protesilaos’ and second is ‘Cyprus’"
 

Some of those clauses are a different way to express cond. Arguably better, but not a clear winner in my opinion. What is impressive and a true paradigm shift is the concept of “destructuring”, else the pattern matching done to the expression that effectively let binds elements of a list or cons cell to their corresponding index. The syntax used for this destructuring is arcane, until you relate it to the quasi-quote and the comma which are used for partial evaluation (Partial evaluation inside of a list). With this in mind, consider pcase-let, pcase-let*, pcase-lambda, and pcase-dolist, as variations of the plain let, let*, lambda, and dolist with the added feature of supporting destructuring. They are not doing any of the extras of pcase though—just destructuring on top of their familiar behaviour! This is especially useful when you are working with the return value of a function which comes as a list. I will not elaborate at length, as this is an advanced use-case. If you are already at that level, you do not need me to tell you what to write. For the rest of us who, like me, typically work with simpler code, the pcase-let serves as a sufficient illustration of the principle:

이러한 절 중 일부는 cond 을 표현하는 또 다른 방법입니다. 논쟁의 여지는 있지만 더 나을 수도 있지만, 제 생각에는 명확한 승자는 아닙니다. 인상적이고 진정한 패러다임 전환은 “구조 분해(destructuring)“의 개념입니다. 즉, 효과적으로 let 리스트 또는 cons 셀의 요소를 해당 인덱스에 바인딩하는 표현식에 대한 패턴 매칭입니다. 이 구조 분해에 사용되는 구문은 준-인용(quasi-quote) 및 부분 평가에 사용되는 쉼표와 관련될 때까지는 이해하기 어렵습니다(리스트 내부의 부분 평가). 이를 염두에 두고 pcase-let , pcase-let* , pcase-lambdapcase-dolist 를 일반 let , let* , lambdadolist 의 변형으로 간주하십시오. 이러한 변형은 구조 분해를 지원하는 추가 기능이 있습니다. 그러나 pcase 의 추가 기능은 수행하지 않고 익숙한 동작 위에 구조 분해만 수행합니다! 이것은 함수의 반환 값이 리스트로 제공될 때 특히 유용합니다. 이것은 고급 사용 사례이므로 자세히 설명하지 않겠습니다. 이미 그 수준에 도달했다면 무엇을 작성해야 하는지 말할 필요가 없습니다. 저와 같이 더 간단한 코드를 사용하는 나머지 사람들에게는 pcase-let 이 원리를 충분히 설명해 줍니다.

(defun my-split-string-at-space (string)
  "Split STRING at the space, returning a list of strings."
  (split-string string "\s"))
 
(pcase-let ((`(,one ,_ ,three) (my-split-string-at-space "Protesilaos of Cyprus")))
  (message "This is like `let', but we got `%s' and `%s' via destructuring" one three))
;; => "This is like ‘let’, but we got ‘Protesilaos’ and ‘Cyprus’ via destructuring"
 

Whether you use pcase and destructuring in general is up to you. You do not require them to write high quality code. Though you might agree with those who consider them inherently more elegant and opt to use them for this very reason to have code that is succinct yet highly expressive. pcase 과 일반적인 구조 분해를 사용할지 여부는 여러분에게 달려 있습니다. 고품질 코드를 작성하는 데 필요하지 않습니다. 그러나 본질적으로 더 우아하다고 생각하고 간결하면서도 표현력이 뛰어난 코드를 갖기 위해 이러한 이유로 사용하는 사람들과 동의할 수도 있습니다.

15. Run some code or fall back to some other code

15\. 일부 코드를 실행하거나 다른 코드로 대체합니다.

Your typical code will rely on if, cond , and the like for control flow (Basic control flow with if, cond , and others). Depending on your specific needs or stylistic considerations, it may even include pcase (Pattern match with pcase and related) as well as if-let* (Control flow with if-let* and friends). There are some cases, nonetheless, that make it imperative you run additional code after your primary operation concludes or exits. The idea is to clean up whatever intermediate state you created. The logic is “do this with all the necessary side effects, then whatever happens to it do that now to, inter alia, undo the side effects.” This is the concept of “unwinding”, which is implemented via unwind-protect .

일반적인 코드는 흐름 제어를 위해 if , cond 등( if , cond 등을 이용한 기본 흐름 제어)에 의존합니다. 특정 요구 사항이나 스타일 고려 사항에 따라 pcase ( pcase 및 관련 항목을 이용한 패턴 매칭) 및 if-let* ( if-let* 등을 이용한 흐름 제어)도 포함될 수 있습니다. 그럼에도 불구하고 기본 작업이 완료되거나 종료된 후 추가 코드를 실행해야 하는 경우가 있습니다. 아이디어는 생성한 중간 상태를 정리하는 것입니다. 논리는 “필요한 모든 부작용을 통해 이것을 수행한 다음, 어떤 일이 발생하든 이제 그것을 수행하여 무엇보다도 부작용을 취소합니다.” 이것이 unwind-protect 을 통해 구현되는 “언와인딩”의 개념입니다.

In the following code block, I define a function which produces a minibuffer prompt asking you to provide a y or n answer, which is shorthand notation for “yes” or “no”. It tests the return value of y-or-n-p to determine what it needs to do. While the prompt is open, the function highlights all instances of the regular expression (defun in the current buffer. Those highlights must go away after you are done with the minibuffer and its consequences.

다음 코드 블록에서 y 또는 n 답변을 요청하는 미니버퍼 프롬프트를 생성하는 함수를 정의합니다. 이는 “예” 또는 “아니오”의 약식 표기법입니다. 이 함수는 y-or-n-p 의 반환 값을 테스트하여 수행해야 할 작업을 결정합니다. 프롬프트가 열려 있는 동안 이 함수는 현재 버퍼에서 정규 표현식 (defun 의 모든 인스턴스를 강조 표시합니다. 이러한 강조 표시는 미니버퍼와 그 결과가 끝나면 사라져야 합니다.

(defun my-prompt-with-temporary-highlight ()
  "Ask for confirmation and highlight all instances of a regexp while waiting."
  (let ((regexp "(defun"))
    (unwind-protect
        (progn
          (highlight-regexp regexp)
          (if (y-or-n-p "Should we proceed or not? ")
              (message "You have decided to proceed")
            (message "You prefer not to continue")))
      (unhighlight-regexp regexp))))
 

Try the above in your Emacs to get a feel for it. While the “yes or no” prompt is active, also do C-g ( keyboard-quit ) or C-] ( abort-recursive-edit ) to confirm that the highlights are removed even though the code never gets past the prompting phase. You may even modify the function to produce an error: it will create a backtrace, which will still have the effect of unwinding after you do q ( debugger-quit) from the *Backtrace* window.

위 코드를 Emacs에서 실행하여 감을 잡아보십시오. “예 또는 아니오” 프롬프트가 활성 상태인 동안 C-g ( keyboard-quit ) 또는 C-] ( abort-recursive-edit )를 눌러 코드가 프롬프트 단계를 벗어나지 않더라도 강조 표시가 제거되는지 확인하십시오. 함수를 수정하여 오류를 발생시킬 수도 있습니다. 오류가 발생하면 백트레이스가 생성되는데, 이 백트레이스를 *Backtrace* 창에서 q ( debugger-quit )를 눌러 닫으면 언와인딩 효과가 나타납니다.

(defun my-prompt-with-temporary-highlight-try-with-error ()
  "Ask for confirmation and highlight all instances of a regexp while waiting."
  (let ((regexp "(defun"))
    (unwind-protect
        (progn
          (highlight-regexp regexp)
          (error "This error makes no sense here; close the backtrace to test the unwinding")
          (if (y-or-n-p "Should we proceed or not? ")
              (message "You have decided to proceed")
            (message "You prefer not to continue")))
      (unhighlight-regexp regexp))))
 

Taking a step back, you will figure out how unwind-protect is a more general form of specialists like save-excursion and save-restriction (Switching to another buffer, window, or narrowed state), while it underpins the save-match-data (The match data of the last search) among many other functions/macros, such as with-temp-buffer and save-window-excursion. What unwind-protect does not do is respond specially to signals, such as those coming from the error function: it will allow the error to happen, meaning that a backtrace will be displayed and your code will exit right there (but the unwinding will still work, as I already explained, once you dismiss the backtrace). To make your code treat signals in a more controlled fashion, you must rely on condition-case .

한 걸음 물러서서 생각해 보면 unwind-protectsave-excursionsave-restriction (다른 버퍼, 창 또는 좁혀진 상태로 전환)와 같은 전문가의 더 일반적인 형태이며, save-match-data (마지막 검색의 일치 데이터), with-temp-buffersave-window-excursion 와 같은 다른 많은 함수/매크로의 기반이 된다는 것을 알 수 있습니다. unwind-protecterror 함수에서 오는 신호와 같은 신호에 특별히 반응하지 않습니다. 오류가 발생하도록 허용합니다. 즉, 백트레이스가 표시되고 코드가 바로 종료됩니다 (그러나 백트레이스를 닫으면 이미 설명했듯이 언와인딩은 여전히 작동합니다). 코드가 신호를 보다 제어된 방식으로 처리하도록 하려면 condition-case 에 의존해야 합니다.

With condition-case you assume full control over the behaviour of your code, including how it should deal with errors. Put differently, your Elisp will express the intent of “I want to do this, but if I get an error I want to do that instead.” There are many signals to consider, all of which come from the signal function. These include the symbols error, user-error, args-out-of-range, wrong-type-argument, wrong-length-argument, and quit, in addition to anything else the programmer may consider necessary. In the following code blocks, I show you how condition-case looks like. Remember that sometimes you do not do quoting the usual way because of how the underlying form is implemented (Evaluation inside of a macro or special form). The example I am using is the same I had for unwind-protect .

condition-case 을 사용하면 오류 처리 방법을 포함하여 코드의 동작을 완전히 제어할 수 있습니다. 달리 말하면, Elisp는 “이 작업을 수행하고 싶지만 오류가 발생하면 대신 저 작업을 수행하고 싶습니다.”라는 의도를 표현합니다. 고려해야 할 많은 신호가 있으며, 모두 signal 함수에서 비롯됩니다. 여기에는 프로그래머가 필요하다고 생각할 수 있는 것 외에도 error , user-error , args-out-of-range , wrong-type-argument , wrong-length-argumentquit 기호가 포함됩니다. 다음 코드 블록에서 condition-case 의 모양을 보여 드리겠습니다. 기본 형식이 구현되는 방식 때문에 평소와 같이 인용 부호를 사용하지 않는 경우가 있습니다 (매크로 또는 특수 형식 내부의 평가). 제가 사용하는 예는 unwind-protect 에 사용했던 것과 동일합니다.

(defun my-prompt-with-temporary-highlight-and-signal-checks ()
  "Ask for confirmation and highlight all instances of a regexp while waiting."
  (let ((regexp "(defun"))
    (condition-case nil
        (progn
          (highlight-regexp regexp)
          (if (y-or-n-p "Should we proceed or not? ")
              (user-error "You have decided to proceed; but we need to return a `user-error'")
            (error "You prefer not to continue; but we need to return an `error'")))
      (:success
       (unhighlight-regexp regexp)
       (message "No errors, but still need to unwind what we did, plus whatever else we want here"))
      (quit
       (unhighlight-regexp regexp)
       (message "This is our response to the user aborting the prompt"))
      (user-error
       (unhighlight-regexp regexp)
       (message "This is our response to the `user-error' signal"))
      (error
       (unhighlight-regexp regexp)
       (message "This is our response to the `error' signal")))))
 

The above function illustrates both the aforementioned concept of unwinding and the mechanics of handling signals. The abstract structure of condition-case looks to me like an amalgamation of let, unwind-protect, and cond. These conditions may include the special handler of :success, as I show there. Granted, the code I wrote will never lead to that specific success case, though you can modify what happens after the prompt to, say, call message instead of the user-error function, which will then count as a successful conclusion. Otherwise, I think the expressions I wrote tell you exactly how this program responds to the signals it receives.

위의 함수는 앞서 언급한 풀기 개념과 신호 처리 메커니즘을 모두 보여줍니다. condition-case 의 추상적인 구조는 let , unwind-protect , cond 의 융합처럼 보입니다. 이러한 조건에는 :success 의 특수 핸들러가 포함될 수 있으며, 이는 제가 보여주는 바와 같습니다. 제가 작성한 코드는 특정 성공 사례로 이어지지는 않겠지만, 프롬프트 이후에 발생하는 작업을 수정하여 user-error 함수 대신 message 를 호출하도록 할 수 있으며, 이는 성공적인 결론으로 간주됩니다. 그렇지 않으면 제가 작성한 표현식이 이 프로그램이 수신하는 신호에 어떻게 반응하는지 정확히 알려준다고 생각합니다.

What I have not covered yet, is the aspect of condition-case that is like the let, namely, how it binds the error data to a variable within this scope. In my implementation above, it is the nil you see there, meaning that I choose not to perform such a binding, as I have no use for its data. Below I decide to use it, just for the sake of demonstration.

아직 다루지 않은 것은 condition-caselet 과 같은 측면, 즉 오류 데이터를 이 범위 내의 변수에 바인딩하는 방법입니다. 위의 내 구현에서 이는 nil 이며, 이는 해당 데이터가 필요하지 않으므로 이러한 바인딩을 수행하지 않기로 선택했음을 의미합니다. 아래에서는 데모를 위해 사용하기로 결정했습니다.

(defun my-prompt-with-temporary-highlight-and-signal-checks-with-error-report ()
  "Ask for confirmation and highlight all instances of a regexp while waiting."
  (let ((regexp "(defun"))
    (condition-case error-data-i-got
        (progn
          (highlight-regexp regexp)
          (if (y-or-n-p "Should we proceed or not? ")
              (user-error "You have decided to proceed; but we need to return a `user-error'")
            (error "You prefer not to continue; but we need to return an `error'")))
      (:success
       (unhighlight-regexp regexp)
       (message "No errors, but still need to unwind what we did, plus whatever else we want here")
       (message "The error is `%s' and its data is `%S'" (car error-data-i-got) (cdr error-data-i-got)))
      (quit
       (unhighlight-regexp regexp)
       (message "This is our response to the user aborting the prompt")
       (message "The error is `%s' and its data is `%S'" (car error-data-i-got) (cdr error-data-i-got)))
      (user-error
       (unhighlight-regexp regexp)
       (message "This is our response to the `user-error' signal")
       (message "The error is `%s' and its data is `%S'" (car error-data-i-got) (cdr error-data-i-got)))
      (error
       (unhighlight-regexp regexp)
       (message "This is our response to the `error' signal")
       (message "The error is `%s' and its data is `%S'" (car error-data-i-got) (cdr error-data-i-got))))))
 

There will be times when unwind-protect and condition-case are the right tools for the job. My hope is that these examples have given you the big picture view and you are now ready to write your own programs in Emacs Lisp.

때로는 unwind-protectcondition-case 이 적절한 도구가 될 수 있습니다. 이 예제들이 여러분에게 큰 그림을 보여주었고 이제 Emacs Lisp로 여러분만의 프로그램을 작성할 준비가 되었기를 바랍니다.

16. When to use a named function or a lambda function

16\. 이름 있는 함수 또는 람다 함수를 사용해야 할 경우

The lambda is an anonymous function. It stands in juxtaposition to defun, which defines a function with a given name. When to use one or the other is largely a matter of style. Though there are some cases where a certain approach is more appropriate. The rule of thumb is this: if you need to use the function more than once, then give it a name and then call it by its name. Otherwise, you will effectively be redefining it each time, which makes it hard for you to rewrite your program. By contrast, if the function is only relevant ad-hoc, then a lambda is fine.

lambda 은 익명 함수입니다. 이것은 주어진 이름으로 함수를 정의하는 defun 과 대조됩니다. 어느 것을 사용할지는 주로 스타일의 문제입니다. 특정 접근 방식이 더 적절한 경우가 있긴 하지만요. 경험 법칙은 다음과 같습니다. 함수를 두 번 이상 사용해야 하는 경우 이름을 지정한 다음 해당 이름으로 호출하십시오. 그렇지 않으면 매번 효과적으로 재정의하게 되어 프로그램을 다시 작성하기가 어려워집니다. 반대로 함수가 임시적으로만 관련이 있는 경우 lambda 가 좋습니다.

In some cases, you will have a named function that employs a lambda internally. To modify one of the examples you will find in this book (Mapping through a list of elements):

경우에 따라 내부적으로 lambda 을 사용하는 이름 있는 함수가 있을 수 있습니다. 이 책에서 찾을 수 있는 예제 중 하나를 수정하려면 (요소 목록 매핑):

(defun my-increment-numbers-by-ten (numbers)
  "Add 10 to each number in NUMBERS and return the new list."
  (mapcar
   (lambda (number)
     (+ 10 number))
   numbers))
 
(my-increment-numbers-by-ten '(1 2 3))
;; => (11 12 13)
 

A lambda inside of a named function may also be used to do something over and over again, with the help of let. You may, for instance, have a function that needs to greet a list of people as a side effect with mapc and you do not want to define the same function more than once:

명명된 함수 내부의 lambdalet 의 도움을 받아 무언가를 반복적으로 수행하는 데에도 사용될 수 있습니다. 예를 들어, mapc 를 사용하여 여러 사람에게 인사해야 하는 함수가 있고 동일한 함수를 두 번 이상 정의하고 싶지 않을 수 있습니다.

(defun my-greet-teams (&rest teams)
  "Say hello to each person in TEAMS and return list with all persons per team.
Each member of TEAMS is a list of strings."
  (let* ((greet-name (lambda (name)
                       (message "Hello %s" name)))
         (greet-team-and-names (lambda (team)
                                 (message "Greeting the team of `%s'..." team)
                                 (mapc greet-name team))))
    (mapcar greet-team-and-names teams)))
 
(my-greet-teams
 '("Pelé" "Ronaldo")
 '("Maradona" "Messi")
 '("Beckenbauer" "Neuer")
 '("Platini" "Zidane")
 '("Baresi" "Maldini")
 '("Eusebio" "Cristiano Ronaldo")
 '("Xavi" "Iniesta")
 '("Charlton" "Shearer")
 '("Puskas" "Kubala")
 '("All of the Greece Euro 2004 squad ;)"))
;; => (("Pelé" "Ronaldo") ("Maradona" "Messi") ...)
 

The greetings are a side effect in this case and are available in the *Messages* buffer. You can quickly access that buffer with C-h e ( view-echo-area-messages). It does not really matter what my-greet-teams is doing. Focus on the combination of a named function and anonymous functions inside of it.

이 경우 인사는 부작용이며 *Messages* 버퍼에서 사용할 수 있습니다. C-h e ( view-echo-area-messages )를 사용하여 해당 버퍼에 빠르게 액세스할 수 있습니다. my-greet-teams 가 무엇을 하고 있는지는 중요하지 않습니다. 명명된 함수와 그 안의 익명 함수의 조합에 집중하세요.

17. Make your interactive function also work from Lisp calls

17\. 대화형 함수가 Lisp 호출에서도 작동하도록 만드세요.

Functions can be used interactively when they are declared with the interactive specification. This turns them into “commands”. They can be called via their name by first doing M-x ( execute-extended-command) and then finding the command. They may also be assigned to a key and invoked directly by pressing that key. In its simplest form, the interactive specification is an unquoted list like (interactive). Here is a trivial example that calls read-string to produce a minibuffer prompt which accepts user input and returns it as a string:

함수는 interactive 사양으로 선언되면 대화형으로 사용할 수 있습니다. 이렇게 하면 함수가 “명령”으로 바뀝니다. M-x ( execute-extended-command )를 먼저 수행한 다음 명령을 찾아 이름으로 호출할 수 있습니다. 키에 할당하여 해당 키를 눌러 직접 호출할 수도 있습니다. 가장 간단한 형태로 interactive 사양은 (interactive) 과 같은 인용 부호가 없는 목록입니다. 다음은 사용자 입력을 받아 문자열로 반환하는 미니버퍼 프롬프트를 생성하기 위해 read-string 를 호출하는 간단한 예입니다.

(defun my-greet-person ()
  (interactive)
  (message "Hello %s" (read-string "Whom to greet? ")))
 

The problem with the above implementation is that it is only useful in interactive use. If you want to issue such a greeting non-interactively through a program, you need to write another function that does practically the same thing except that it takes a NAME argument. Like this:

위 구현 방식의 문제는 대화형 환경에서만 유용하다는 것입니다. 프로그램을 통해 비대화형으로 인사를 보내려면 NAME 인수를 받는다는 점만 제외하면 거의 동일한 작업을 수행하는 다른 함수를 작성해야 합니다. 예를 들어 다음과 같습니다.

(defun my-greet-person-with-name (name)
  "Greet person with NAME."
  (message "Hello %s" name))
 

You do not need to write two separate functions which practically do the same thing. Instead, you can have one function, with its parameters, which decides how to get the values of the arguments passed to it depending on if it is called interactively or programmatically. Consider this scenario:

실제로 동일한 작업을 수행하는 두 개의 별도 함수를 작성할 필요가 없습니다. 대신, 하나의 함수와 해당 매개변수를 사용하여 대화형으로 호출되는지 프로그래밍 방식으로 호출되는지에 따라 전달된 인수의 값을 가져오는 방법을 결정할 수 있습니다. 다음 시나리오를 고려하십시오.

(defun my-greet-interactive-and-non-interactive (name)
  "Greet person with NAME.
When called interactively, produce a minibuffer prompt asking for NAME.
 
When called from Lisp, NAME is a string."
  (interactive (list (read-string "Whom to greet? ")))
  (message "Hello %s" name))
 

The documentation I wrote there tells you exactly what is happening. Though let me explain interactive in further detail: it takes an argument, which is a list that corresponds to the argument list of the current defun. In this case, the defun has a list of arguments that includes a single element, the NAME. Thus, interactive also has a list with one element, whose value corresponds to NAME. If the parameters were more than one, then the interactive would have to be written accordingly: each of its elements would correspond to the parameter at the same index on the list.

제가 작성한 문서는 정확히 무슨 일이 일어나고 있는지 알려줍니다. 좀 더 자세히 설명하자면 interactive 은 현재 defun 의 인수 목록에 해당하는 목록인 인수를 취합니다. 이 경우 defun 에는 단일 요소인 NAME 를 포함하는 인수 목록이 있습니다. 따라서 interactiveNAME 에 해당하는 값을 가진 요소가 하나인 목록을 갖습니다. 매개변수가 둘 이상인 경우 interactive 은 그에 따라 작성해야 합니다. 각 요소는 목록에서 동일한 인덱스에 있는 매개변수에 해당합니다.

This list of expressions you pass to interactive essentially is the preparatory work that binds values to the parameters. When you call the above function interactively, you practically tell Emacs that in this case NAME is the return value of the call to read-string . For more parameters, you get the same principle but I write it down just to be clear:

interactive 에 전달하는 이 표현식 목록은 기본적으로 매개변수에 값을 바인딩하는 준비 작업입니다. 위 함수를 대화형으로 호출하면 실제로 Emacs에 이 경우 NAMEread-string 호출의 반환 값이라고 알려주는 것입니다. 매개변수가 더 많은 경우에도 동일한 원리가 적용되지만 명확하게 하기 위해 적어 두겠습니다.

(defun my-greet-with-two-parameters (name country)
  "Greet person with NAME from COUNTRY.
When called interactively, produce a minibuffer prompt asking for NAME
and then another prompt for COUNTRY.
 
When called from Lisp, NAME and COUNTRY are strings."
  (interactive
   (list
    (read-string "Whom to greet? ")
    (read-string "Where from? ")))
  (message "Hello %s of %s" name country))
 
(my-greet-with-two-parameters "Protesilaos" "Cyprus")
;; => "Hello Protesilaos of Cyprus"
 

Write interactive specifications with care and you will end up with a rich corpus of code that is economical and flexible.

interactive 사양을 주의해서 작성하면 경제적이고 유연한 풍부한 코드 모음을 얻을 수 있습니다.