100言語Spedrdrun :エピソード86:Emacs Lisp


編集者戦争は終わった.TextMateスタイルエディタ(崇高なテキスト、原子、VSCode)を獲得した.Jupyter、Android Studio、およびそのような重要な使用のような言語固有のエディタ、そしてどうにか、メモ帳+はそのニッチを見つけました.特に「編集者戦争」の主な役者であるEmacsとVer . Emacsは、viスタイルの編集者(VimとNeovimが最近生きている)をどうにかするために、どうにか十分なvi diehardsがあります.Emacsのスタイルエディタには、以下のような種類がありません.
とにかく、私がここで興味を持っているのは編集者ではない.私はTextMateの初期の採用者であり、決して振り返っていなかった.
Emacs Lispはずっとパワフルで、Emacsの中から実行されたプログラムは全部書き込まれました.当時、それは奇妙に見えました、しかし、現在、あらゆるエディタはそのように働きます.Emacs Lispは基本的にLispの主要な方言でした、そして、私はそれがLipsのどれもひどく人気がないので、まだそうであると思います.Emacsが関連したとき、誰もそれが好きでありませんでした、そして、一般的なLisp、計画または他に何かに変わることについての一定の話がありました.今それはもはや重要ではない、生態系全体が死亡した.
一方vimscriptはEmacs Lispのようにどこにも大きくはありませんでした、そして、Vim World(Neovimによる)の残りのものはとにかくLuAに変わりました.

こんにちは、世界!


適切に#! Emacs Lispスクリプトを端末から実行できます.
#!/usr/bin/env emacs -Q --script

(princ "Hello, World!\n")
$ ./hello.el
Hello, World!
それはコースのlispであるので、至る所で括弧.princ 人間に優しいprint . 多くのEmacs Lisp関数はそのような不可解な名前を持ちます.

フィズバズ


#!/usr/bin/env emacs -Q --script

; FizzBuzz in Emacs Lisp

(defun divisible (n m)
  (= 0 (% n m)))

(defun fizzbuzz (n)
  (cond
    ((divisible n 15) "FizzBuzz")
    ((divisible n 5) "Buzz")
    ((divisible n 3) "Fizz")
    (t (number-to-string n))))

(dotimes (i 100)
  (princ (fizzbuzz (+ i 1)))
  (princ "\n"))
このコードはあまり悪くありませんが、すでにいくつかのマイナーな問題に遭遇します.
  • (defun ...) 関数を定義する
  • (dotimes (i n)) からの反復のみ0 to n-1 , ビルトインがないa to b 反復処理
  • (princ) つの引数だけをとり、改行を出力しません.println ちょうど働くだろう同等の機能princ 複数の引数を取る、それはそのような明白なことです、そしてほとんどの言語はそれだけで罰金をサポートして
  • (cond ...) のようですif/elsif チェーン
  • t 手段true
  • nil 手段false , 空リスト
  • マクロ


    では、Emacs Lispをより良くするように拡張しましょう.追加します(dorange (i a b) ...) 多くの引数(prints ...) .
    #!/usr/bin/env emacs -Q --script
    
    (defun prints (&rest args)
      (if (consp args)
        (progn
          (princ (car args))
          (apply 'prints (cdr args)))))
    
    (defun fib (n)
      (if (<= n 2)
        1
        (+ (fib (- n 1)) (fib (- n 2)))))
    
    (defmacro dorange (i a b &rest body)
      `(let ((,i ,a))
         (while (<= ,i ,b)
           ,@body
           (setq ,i (+ ,i 1)))))
    
    (dorange n 1 30
      (prints "fib(" n ")=" (fib n) "\n"))
    
    $ ./fib.el
    fib(1)=1
    fib(2)=1
    fib(3)=2
    fib(4)=3
    fib(5)=5
    fib(6)=8
    fib(7)=13
    fib(8)=21
    fib(9)=34
    fib(10)=55
    fib(11)=89
    fib(12)=144
    fib(13)=233
    fib(14)=377
    fib(15)=610
    fib(16)=987
    fib(17)=1597
    fib(18)=2584
    fib(19)=4181
    fib(20)=6765
    fib(21)=10946
    fib(22)=17711
    fib(23)=28657
    fib(24)=46368
    fib(25)=75025
    fib(26)=121393
    fib(27)=196418
    fib(28)=317811
    fib(29)=514229
    fib(30)=832040
    
    一歩一歩
  • ネーミングは本当にひどいです.progn , car , cdr , setq 私はこれらが伝統的なLISPの名前であるということを知っています.
  • consp は「空のリスト」です
  • car リストの最初の要素を意味する
  • cdr リストの残り
  • setq 設定変数
  • &rest 関数の残りの引数を意味する
  • なぜ私たちはこのような愚かさを行う必要がありますか(apply 'prints (cdr args)) の代わりに(prints &rest (cdr args)) or (prints . (cdr args)) ?
  • 関数型プログラミング


    OK、いくつかの超基本的な機能プログラミングを試してみましょう.
    #!/usr/bin/env emacs -Q --script
    
    (setq list '(1 2 3 4 5))
    
    (setq add2 (lambda (n) (+ n 2)))
    (print (mapcar add2 list))
    
    (defun addn (n) (lambda (m) (+ n m)))
    (setq add3 (addn 3))
    (print (mapcar add3 list))
    
    作成するadd2 を追加するラムダとして2 である.その後、我々は作成add3 それで3 . きっとそれは正しく働くでしょうか?
    $ ./functional.el
    
    (3 4 5 6 7)
    Symbol’s value as variable is void: n
    
    井戸add2 働いたがadd3 でなかった、ワット?ここではEmacs Lispの主要な問題の一つに遭遇します.いくつかの非常識な理由emacslispすべてのダイナミックなスコープを使用します.これはかなりの機能的プログラミングを使用する任意のアイデアを殺す.

    オプションは壊れません


    Emacsを使用して皆が停止した後に、Emacs Lispは、「壊れないでください」モードを追加しました.
    #!/usr/bin/env emacs -Q --script
    ;; -*- lexical-binding: t -*-
    
    (setq list '(1 2 3 4 5))
    
    (setq add2 (lambda (n) (+ n 2)))
    (print (mapcar add2 list))
    
    (defun addn (n) (lambda (m) (+ n m)))
    (setq add3 (addn 3))
    (print (mapcar add3 list))
    
    $ ./functional2.el
    
    (3 4 5 6 7)
    
    (4 5 6 7 8)
    
    また、これらの余分な改行とは何です(print ...) ? prin1 and princ 改行を表示しないprint 印刷前と1つ後に、WTF?
    一歩一歩
  • (setq lest '(1 2 3 4 5)) - Emacs Lispが関数名をコールしようとせずにリストを関数呼び出しから区別するためにその引用符を必要とします1
  • (lambda (n) ...) 匿名関数n
  • (mapcar f list) is map , ひどいネーミングのもう一つのケース
  • ユニコード


    少なくともUnicodeは働きます.エディタ固有の言語がUnicodeをサポートしていない場合は、当惑します.
    #!/usr/bin/env emacs -Q --script
    
    (defun prints (&rest args)
      (if (consp args)
        (progn
          (princ (car args))
          (princ "\n")
          (apply 'prints (cdr args)))))
    
    (prints
      (length "Hello")
      (length "Żółw")
      (length "💰")
      (downcase "Żółw")
      (upcase "Żółw"))
    
    $ ./unicode.el
    5
    4
    1
    żółw
    ŻÓŁW
    

    悪化する


    よし、ちょっと複雑なことをしましょう.
    #!/usr/bin/env emacs -Q --script
    
    (defun read-file (path)
      (with-temp-buffer
        (insert-file-contents path)
        (buffer-string)))
    (defun read-lines (path)
      (split-string (read-file path) "\n" t))
    (defun random-element (list)
      (nth (random (length list)) list))
    
    (defun report-wordle-blocks (guess word)
      (dotimes (i 5)
        (let ((gi (substring guess i (+ i 1)))
              (wi (substring word i (+ i 1))))
          (princ
            (cond
              ((equal gi wi) "🟩")
              ((string-match-p (regexp-quote gi) word) "🟨")
              (t "🟥")))))
      (princ "\n"))
    
    (defun report-wordle (guess word)
      (if (/= (length guess) 5)
        (princ "Please enter a 5 letter word.\n")
        (report-wordle-blocks guess word)))
    
    (setq word-list (read-lines "wordle-answers-alphabetical.txt"))
    (setq word (random-element word-list))
    (setq guess "")
    
    (while (not (equal guess word))
      (setq guess (read-from-minibuffer "Guess: "))
      (report-wordle guess word))
    
    私の最初の試みは、驚くべきではない:
    $ ./wordle.el
    Guess: raise
    🟥🟩🟥🟥🟩
    Guess: maybe
    🟥🟩🟥🟥🟩
    Guess: dance
    🟥🟩🟥🟥🟩
    Guess: vague
    🟥🟩🟥🟨🟩
    Guess: haute
    🟩🟩🟩🟩🟩
    
    一歩一歩
  • Emacs Lispは「ファイルを読む」、「ランダムな要素」、「文字列を含む」といった多くの明白な機能を欠いている
  • ファイルを読むには、“一時的なバッファ”を作成し、そのファイルにファイルの内容を挿入し、バッファの内容を読み込む必要があります
  • readlineするには、それを行う必要がありますsplit-string そば"\n" - その余分t 空の文字列を無視することを意味します(最後の改行後の最後のもののように)-全体のものは全く正しくないが、十分に閉じます
  • random-element リストからランダムな要素を返す
  • report-wordle-blocks Wordleマッチ用の色付きブロックを出力する
  • (string-match-p (regexp-quote gi) word) 文字列が別の機能を含んでいるかどうかをチェックする最も簡単な方法のようです
  • 全体的にこのコードについての多くの小さなことはちょっと間違っている感じ
  • Emacs Lispを使うべきですか?


    明らかにない.Emacsはエディタをアプリケーションプラットフォームにするパイオニアでした.Emacs Lispはその役割については十分でしたが、EmacsとEmacs Lispの両方は本当に時代遅れです.多分、Emacsはより良い言語、そしてより少ないGUI恐怖症で闘争チャンスを持っていたかもしれません、しかし、歴史はそれです.
    言語自体に関しては、Emacs Lisp言語は奇妙なアーキチックなquirksでいっぱいです、そして、とても多くの基本的な特徴を逃します、そして、現代のLISSは少しよりよくそれをします.Arguably none of the Lisps is all that great , しかし、あなたがLISPにトライを与えたいならば、ラケットとクロジュールはずっと合理的です.
    そして、いくつかのエディタプラグインをコード化したいならば、vscodeはすべてJavaScriptです、それで、あなたはそれを学ばなければなりません.

    コード


    All code examples for the series will be in this repository .
    Code for the Emacs Lisp episode is available here .