web-dev-qa-db-ja.com

LISPの柔軟性の実際的な例?

誰かがLISPを私に売り込もうとしています。これは、これまでにすべてを実行できる超強力な言語であり、それからいくつかの言語です。

実用的 LISPの力のコード例はありますか?
(できれば、正規言語でコーディングされた同等のロジックと一緒に。)

67
Peter Boughton

私はマクロが好きです。

LDAPから人々の属性を詰め込むためのコードは次のとおりです。私はたまたまそのコードが横になっていて、他の人にも役立つだろうと考えました。

マクロの想定される実行時のペナルティについて混乱している人もいるので、最後に物事を明確にする試みを追加しました。

初めに、重複がありました

_(defun ldap-users ()
  (let ((people (make-hash-table :test 'equal)))
    (ldap:dosearch (ent (ldap:search *ldap* "(&(telephonenumber=*) (cn=*))"))
                   (let ((mail  (car (ldap:attr-value ent 'mail)))
                         (uid   (car (ldap:attr-value ent 'uid)))
                         (name  (car (ldap:attr-value ent 'cn)))
                         (phonenumber (car (ldap:attr-value ent 'telephonenumber))))
                      (setf (gethash uid people)
                            (list mail name phonenumber))))
    people))
_

「letbinding」は、LETフォームの外に消えるローカル変数と考えることができます。バインディングの形式に注意してください。これらは非常によく似ており、LDAPエンティティの属性と、値をバインドする名前(「ローカル変数」)のみが異なります。便利ですが、少し冗長で、重複が含まれています。

美の探求について

さて、私たちがそのすべての重複をする必要がなかったら、それは素晴らしいことではないでしょうか?一般的なイディオムはWITH -...マクロです。これは、値を取得できる式に基づいて値をバインドします。そのように機能する独自のマクロWITH-LDAP-ATTRSを導入し、元のコードに置き換えてみましょう。

_(defun ldap-users ()
  (let ((people (make-hash-table :test 'equal))) ; equal so strings compare equal!
    (ldap:dosearch (ent (ldap:search *ldap* "(&(telephonenumber=*) (cn=*))"))
                   (with-ldap-attrs (mail uid name phonenumber) ent
                       (setf (gethash uid people)
                             (list mail name phonenumber))))
    people))
_

たくさんの線が突然消えて、たった1本の線に置き換えられたのを見ましたか?これを行う方法?もちろん、マクロを使用する-コードを書くコード! LISPのマクロは、プリプロセッサを使用してC/C++で見つけることができるものとはまったく異なる動物です。ここでは、realLISPコードを実行できます(他のコードがコンパイルされる前にLISPコードを生成する_#define_ fluff in cpp)。マクロは、実際のLISPコード、つまり通常の関数を使用できます。基本的に制限はありません。

醜いものを取り除く

それで、これがどのように行われたかを見てみましょう。 1つの属性を置き換えるために、関数を定義します。

_(defun ldap-attr (entity attr)
  `(,attr (car (ldap:attr-value ,entity ',attr))))
_

バッククォート構文は少し厄介に見えますが、それが行うことは簡単です。 LDAP-ATTRSを呼び出すと、attr(コンマ)のvalueを含むリストが出力され、その後にcar( "リストの最初の要素"( "リストの最初の要素")が続きます。 consペア、実際には)、実際にはfirstという関数もあります)。これは、_ldap:attr-value_によって返されるリストの最初の値を受け取ります。これはコードをコンパイルするときに実行したいコードではないため(属性値を取得することは、プログラムを実行するときに実行したいことです)、追加しません呼び出しの前のコンマ。

とにかく。マクロの残りの部分に移動します。

_(defmacro with-ldap-attrs (attrs ent &rest body)
  `(let ,(loop for attr in attrs
         collecting `,(ldap-attr ent attr))
     ,@body)) 
_

_,@_-構文は、実際のリストではなく、リストの内容をどこかに配置することです。

結果

これで正しいことがわかることを簡単に確認できます。マクロは多くの場合、このように記述されます。単純にしたいコード(出力)、代わりに記述したいコード(入力)から始めて、入力が正しい出力を提供するまでマクロの成形を開始します。関数_macroexpand-1_は、マクロが正しいかどうかを示します。

_(macroexpand-1 '(with-ldap-attrs (mail phonenumber) ent
                  (format t "~a with ~a" mail phonenumber)))
_

に評価します

_(let ((mail (car (trivial-ldap:attr-value ent 'mail)))
      (phonenumber (car (trivial-ldap:attr-value ent 'phonenumber))))
  (format t "~a with ~a" mail phonenumber))
_

展開されたマクロのLETバインディングを最初のコードと比較すると、同じ形式であることがわかります。

コンパイル時間とランタイム:マクロと関数

マクロは、コンパイル時で実行されるコードであり、任意の通常の関数またはマクロを次のように呼び出すことができるという追加の工夫が加えられています。彼らはお願いします!これは、いくつかの引数を取り、いくつかの変換を適用し、コンパイラーに結果のs-expを供給する、凝ったフィルターにすぎません。

基本的に、言語の低レベルのプリミティブではなく、問題のドメインにある動詞でコードを記述できます。ばかげた例として、次のことを考慮してください(whenがまだ組み込まれていない場合)::

_(defmacro my-when (test &rest body)
  `(if ,test 
     (progn ,@body)))
_

ifは、ブランチでoneフォームのみを実行できる組み込みプリミティブです。複数のフォームが必要な場合は、progn ::を使用する必要があります。

_;; one form
(if (numberp 1)
  (print "yay, a number"))

;; two forms
(if (numberp 1)
  (progn
    (assert-world-is-sane t)
    (print "phew!"))))
_

新しい友達である_my-when_を使用すると、a)偽の分岐がない場合はより適切な動詞を使用し、b)暗黙のシーケンス演算子を追加できます。つまりprogn ::

_(my-when (numberp 1)
  (assert-world-is-sane t)
  (print "phew!"))
_

コンパイルされたコードに_my-when_が含まれることはありませんが、最初のパスではすべてのマクロが展開されるため、実行時のペナルティは発生しません。

_LISP> (macroexpand-1 '(my-when (numberp 1)
                        (print "yay!")))

(if (numberp 1)
  (progn (print "yay!")))
_

_macroexpand-1_は1レベルの拡張のみを行うことに注意してください。拡張がさらに下に続く可能性があります(実際にはおそらく!)。ただし、最終的には、コンパイラ固有の実装の詳細にたどり着きますが、これはあまり面白くないことがよくあります。しかし、結果を拡張し続けると、最終的には詳細が得られるか、入力されたs-expが返されます。

それが物事を明確にすることを願っています。マクロは強力なツールであり、私が気に入っているLISPの機能の1つです。

77
Mikael Jansson

私が考えることができる最も良い例は、広く利用可能である、ポール・グレアムによる本、 LISPについて です。完全なPDFは、先ほど提供したリンクからダウンロードできます。 Practical Common LISP (Webでも完全に入手可能)を試すこともできます。

実用的でない例がたくさんあります。私はかつて、LISPの約40行でプログラムを作成しました。このプログラムは、それ自体を解析し、そのソースをLISPリストとして扱い、リストのツリートラバーサルを実行し、ソースにwaldo識別子が存在するかどうかをWALDOに評価する式を作成します。 waldoが存在しなかった場合はnil。返される式は、解析された元のソースにcar/cdrへの呼び出しを追加することによって作成されました。 40行のコードで他の言語でこれを行う方法がわかりません。おそらく、Perlはさらに少ない行でそれを行うことができます。

18
Jason Dagit

この記事が役立つかもしれません: http://www.defmacro.org/ramblings/LISP.html

とは言うものの、LISPの力の短くて実用的な例を示すことは非常に困難です。なぜなら、それは重要なコードでのみ実際に輝いているからです。プロジェクトが特定のサイズに成長すると、LISPの抽象化機能に感謝し、それらを使用していることを嬉しく思います。一方、適度に短いコードサンプルでは、​​LISPが優れている理由を十分に示すことはできません。これは、他の言語の事前定義された略語が、ドメイン固有の抽象化を管理するLISPの柔軟性よりも小さな例で魅力的に見えるためです。

14

私は Common LISP Object System (CLOS)とmultimethodsが好きです。

すべてではないにしても、ほとんどのオブジェクト指向プログラミング言語には、クラスとメソッドの基本的な概念があります。 Python の次のスニペットは、PeelingToolクラスとVegetableクラス(Visitorパターンに似たもの)を定義しています。

class PeelingTool:
    """I'm used to peel things. Mostly fruit, but anything peelable goes."""
    def peel(self, veggie):
        veggie.get_peeled(self)

class Veggie:
    """I'm a defenseless Veggie. I obey the get_peeled protocol
    used by the PeelingTool"""
    def get_peeled(self, tool):
        pass

class FingerTool(PeelingTool):
  ...

class KnifeTool(PeelingTool):
  ...

class Banana(Veggie):
    def get_peeled(self, tool):
        if type(tool) == FingerTool:
            self.hold_and_peel(tool)
        Elif type(tool) == KnifeTool:
            self.cut_in_half(tool)

peelメソッドをPeelingToolに配置し、バナナに受け入れさせます。ただし、PeelingToolクラスに属している必要があるため、PeelingToolクラスのインスタンスがある場合にのみ使用できます。

Common LISP Object Systemのバージョン:

(defclass peeling-tool () ())
(defclass knife-tool (peeling-tool) ())
(defclass finger-tool (peeling-tool) ())

(defclass veggie () ())
(defclass banana (veggie) ())

(defgeneric peel (veggie tool)
  (:documentation "I peel veggies, or actually anything that wants to be peeled"))

;; It might be possible to peel any object using any tool,
;; but I have no idea how. Left as an exercise for the reader
(defmethod peel (veggie tool)
   ...)

;; Bananas are easy to peel with our fingers!
(defmethod peel ((veggie banana) (tool finger-tool))
  (with-hands (left-hand right-hand) *me*
    (hold-object left-hand banana)
    (peel-with-fingers right-hand tool banana)))

;; Slightly different using a knife
(defmethod peel ((veggie banana) (tool knife-tool))
  (with-hands (left-hand right-hand) *me*
    (hold-object left-hand banana)
    (cut-in-half tool banana)))

チューリング完全である任意の言語で何でも書くことができます。言語間の違いは、同等の結果を得るためにジャンプする必要があるフープの数です。

Common LISP のような強力な言語は、マクロやCLOSなどの機能を備えているため、多くのフープを飛び越えて、標準以下のソリューションに落ち着くか、自分自身がカンガルー。

13
Mikael Jansson

実際、良い実用的な例はLISPLOOPマクロです。

http://www.ai.sri.com/pkarp/loop.html

LOOPマクロは単純にそれです-LISPマクロ。それでも、基本的にはミニループDSL(ドメイン固有言語)を定義しています。

その小さなチュートリアルを閲覧すると、コードのどの部分がループマクロの一部であり、どの部分が「通常の」LISPであるかを知るのが難しいことがわかります(初心者でも)。

そして、それはLispの表現力の重要な要素の1つであり、新しいコードは実際にはシステムと区別できません。

たとえば、Javaを使用している間は、プログラムのどの部分が標準のJavaライブラリと独自のコード、またはサードパーティのライブラリ)からのものであるかを(一目で)知ることができない場合があります。 、コードのどの部分がクラスの単なるメソッド呼び出しではなくJava言語であるかを知っています。確かに、それはすべて「Java言語」ですが、プログラマーとしては、表現のみに制限されています。クラスとメソッド(そして今ではアノテーション)の組み合わせとしてのアプリケーション。LISPでは、文字通りすべてが手に入る。

CommonLISPをSQLに接続するためのCommonSQLインターフェースを検討してください。ここでは、 http://clsql.b9.com/manual/loop-tuples.html 、CLループマクロを拡張してSQLバインディングを「第一級市民」にする方法を示しています。

「[select [first-name] [last-name]:from [employee]:order-by [last-name]]」などの構成要素を確認することもできます。これはCL-SQLパッケージの一部であり、「リーダーマクロ」として実装されています。

LISPでは、データ構造や制御構造などの新しい構造を作成するためのマクロを作成できるだけでなく、リーダーマクロを介して言語の構文を変更することもできます。ここでは、リーダーマクロ(この場合は「[」記号)を使用してSQLモードにドロップし、SQLを他の多くの言語のように単なる生の文字列としてではなく、埋め込みSQLのように機能させています。

アプリケーション開発者としての私たちの仕事は、プロセスと構成をプロセッサが理解できる形式に変換することです。つまり、コンピュータ言語は私たちを「理解していない」ので、必然的にコンピュータ言語に「話しかける」必要があります。

Common LISPは、アプリケーションをトップダウンで構築できるだけでなく、言語と環境を引き上げて途中で会うことができる数少ない環境の1つです。両端でコーディングできます。

心に、これができる限りエレガントで、それは万能薬ではありません。明らかに、言語と環境の選択に影響を与える他の要因があります。しかし、それは確かに学び、遊ぶ価値があります。 LISPを学ぶことは、他の言語であっても、プログラミングを進歩させるための素晴らしい方法だと思います。

13
Will Hartung

LISPにはたくさんのキラー機能がありますが、マクロは私が特に気に入っている機能です。言語が定義するものと私が定義するものとの間に障壁がなくなったからです。たとえば、Common LISPにはwhile構文がありません。歩きながら頭の中で実装したことがあります。それは簡単でクリーンです:

(defmacro while (condition &body body)
  `(if ,condition
       (progn
         ,@body
         (do nil ((not ,condition))
           ,@body))))

Etvoilà! CommonLISP言語を新しい基本構造で拡張しました。これで、次のことができます。

(let ((foo 5))
  (while (not (zerop (decf foo)))
    (format t "still not zero: ~a~%" foo)))

どちらが印刷されますか:

still not zero: 4
still not zero: 3
still not zero: 2
still not zero: 1

LISP以外の言語でそれを行うことは、読者の練習問題として残されています...

12
Nowhere man

この記事は非常に興味深いものでした。

プログラミング言語の比較:LISPとC++

記事の著者であるBrandonCorfmanは、Java、C++、およびLISPのソリューションをプログラミングの問題と比較し、C++で独自のソリューションを作成する研究について書いています。ベンチマークソリューションは、Peter Norvigの45行のLISP(2時間で作成)です。

コーフマンは、自分のソリューションを142行未満のC++/STLに減らすことは難しいと考えています。理由の彼の分析は、興味深い読み物です。

9
Nelson

LISP(および Smalltalk )システムについて私が最も気に入っているのは、それらが生きていると感じることです。 LISPシステムの実行中に、LISPシステムを簡単にプローブおよび変更できます。

これが不思議に聞こえる場合は、 Emacs を開始し、LISPコードを入力してください。タイプC-M-xそしてvoilà! Emacs内からEmacsを変更しただけです。実行中に、すべてのEmacs関数を続行して再定義できます。

もう1つのことは、コード=リストの同等性により、コードとデータの間のフロンティアが非常に薄くなることです。そして、マクロのおかげで、言語を拡張してすばやく作成するのは非常に簡単です DSL

たとえば、コードが生成されたHTML出力に非常に近い基本的なHTMLビルダーをコーディングすることができます。

(html
  (head
    (title "The Title"))
  (body
    (h1 "The Headline" :class "headline")
    (p "Some text here" :id "content")))

=>

<html>
  <head>
    <title>The title</title>
  </head>
  <body>
    <h1 class="headline">The Headline</h1>
    <p id="contents">Some text here</p>
  </body>
</html>

LISPコードでは、自動インデントにより、終了タグがないことを除いて、コードが出力のように見えます。

私はこのマクロの例が好きです http://common-LISP.net/cgi-bin/viewcvs.cgi/cl-Selenium/?root=cl-Selenium これはSeleniumにバインドするCommon LISPです(a Webブラウザテストフレームワーク)、ただし、すべてのメソッドをマッピングする代わりに、コンパイル時にSelenium独自のAPI定義XMLドキュメントを読み取り、マクロを使用してマッピングコードを生成します。生成されたAPIは次の場所で確認できます:common-LISP.net/project/cl-Selenium/api/Selenium-package/index.html

これは基本的に、外部データを使用してマクロを駆動します。この場合はXMLドキュメントですが、データベースまたはネットワークからの読み取りと同じくらい複雑である可能性があります。これは、コンパイル時にLISP環境全体を利用できるようにすることの力です。

7
Lispnik

XMLテンプレートを使用してCommonLISPを拡張する方法をご覧くださいcl-quasi-quote XMLの例プロジェクトページ

(babel:octets-to-string
 (with-output-to-sequence (*html-stream*)
   <div (constantAttribute 42
         someJavaScript `js-inline(print (+ 40 2))
         runtimeAttribute ,(concatenate 'string "&foo" "&bar"))
     <someRandomElement
       <someOther>>>))

 =>

 "<div constantAttribute=\"42\"
       someJavaScript=\"javascript: print((40 + 2))\"
       runtimeAttribute=\"&amp;foo&amp;bar\">
    <someRandomElement>
      <someOther/>
    </someRandomElement>
  </div>"

これは基本的にLISPのバッククォートリーダー(リスト準引用用)と同じですが、XML(特別な<>構文にインストール)、JavaScript( `js-inlineにインストール)などの他のさまざまなものでも機能します。 。

明確にするために、これはユーザーライブラリに実装されています!また、静的XML、JavaScriptなどの部分を TF-8 エンコードされたリテラルバイト配列にコンパイルし、ネットワークストリームに書き込む準備をします。簡単な,(コンマ)LISPに戻り、ランタイムで生成されたデータをリテラルバイト配列にインターリーブできます。

これは気弱な人向けではありませんが、これはライブラリが上記をコンパイルしたものです。

(progn
 (write-sequence
  #(60 100 105 118 32 99 111 110 115 116 97 110 116 65 116 116 114 105 98
    117 116 101 61 34 52 50 34 32 115 111 109 101 74 97 118 97 83 99 114
    105 112 116 61 34 106 97 118 97 115 99 114 105 112 116 58 32 112 114
    105 110 116 40 40 52 48 32 43 32 50 41 41 34 32 114 117 110 116 105
    109 101 65 116 116 114 105 98 117 116 101 61 34)
  *html-stream*)
 (write-quasi-quoted-binary
  (let ((*transformation*
          #<quasi-quoted-string-to-quasi-quoted-binary {1006321441}>))
    (transform-quasi-quoted-string-to-quasi-quoted-binary
      (let ((*transformation*
               #<quasi-quoted-xml-to-quasi-quoted-string {1006326E51}>))
        (locally
            (declare (sb-ext:muffle-conditions sb-ext:compiler-note))
         (let ((it (concatenate 'string "runtime calculated: " "&foo" "&bar")))
           (if it
               (transform-quasi-quoted-xml-to-quasi-quoted-string/attribute-value it)
               nil))))))
  *html-stream*)
 (write-sequence
  #(34 62 10 32 32 60 115 111 109 101 82 97 110 100 111 109 69 108 101 109
    101 110 116 62 10 32 32 32 32 60 115 111 109 101 79 116 104 101 114 47
    62 10 32 32 60 47 115 111 109 101 82 97 110 100 111 109 69 108 101 109
    101 110 116 62 10 60 47 100 105 118 62 10)
  *html-stream*)
 +void+)

参考までに、上記の2つの大きなバイトベクトルは、文字列に変換すると次のようになります。

"<div constantAttribute=\"42\"
      someJavaScript=\"javascript: print((40 + 2))\"
      runtimeAttribute=\""

そして2番目のもの:

"\">
 <someRandomElement>
    <someOther/>
  </someRandomElement>
</div>"

また、マクロや関数などの他のLISP構造とうまく組み合わせることができます。さて、これを JSPs ..と比較してください。

7
Attila Lendvai

私は1970年代にMITでAIの学生でした。他のすべての学生と同じように、言語が最重要だと思いました。それでも、LISPが第一言語でした。これらはまだかなり良いと思ういくつかのことです。にとって:

  • 記号数学。式の記号微分と代数式の簡略化を書くのは簡単で有益です。私はCでそれらを実行しますが、それでもそれらを実行します。

  • 定理証明。時々、挿入ソートが正しいことを証明しようとするように、一時的なAIの大騒ぎをします。そのために私は記号操作をする必要があり、私は通常LISPに頼ります。

  • 小さなドメイン固有言語。 LISPが本当に実用的ではないことは知っていますが、少し試してみたい場合は [〜#〜] dsl [〜#〜 ] 解析などですべてをまとめる必要なしに、LISPマクロはそれを簡単にします。

  • ミニマックスゲームツリー検索のような小さな遊びのアルゴリズムは、3行のように実行できます。

  • 試してみたい ラムダ計算 ? LISPでは簡単です。

主にLISPが私のために行うことは精神的な運動です。そうすれば、それをより実用的な言語に引き継ぐことができます。

P.S.ラムダ計算と言えば、同じAIミリユーで、1970年代に始まったのは、OOがすべての人の脳に侵入し始め、どういうわけか、それが何に興味を持っているかisは、それが何であるかに多くの関心を集めているようです。つまり、機械学習に取り組んでいます。言語、ビジョン、問題解決、あらゆる種類のものが部屋の後ろに行き、クラス、メッセージ、タイプ、多形性などが前に行きました。

7
Mike Dunlavey

私が気に入っているのは、アプリケーションの状態を失うことなく、コードを「ランタイム」にアップグレードできることです。場合によっては便利なことですが、便利な場合は、最初から実装するよりも、すでにそこにある(または開発中の最小限のコストで)方がはるかに安価です。特にこれは「ノーからほとんどノー」のコストで来るので。

6
Vatine

this マクロが強力で柔軟な理由の説明をご覧になりましたか?申し訳ありませんが、他の言語での例はありませんが、マクロで売れる可能性があります。

5
William Keller

@マーク、

あなたが言っていることにはいくらかの真実がありますが、それは必ずしも簡単ではないと思います。

プログラマーや一般の人々は、すべての可能性を評価し、言語を切り替えることを決定するために常に時間をかけるとは限りません。多くの場合、決定するのはマネージャー、または最初の言語を教える学校です...そしてプログラマーは、この言語がその言語よりも多くの時間を節約できると判断できれば、特定のレベルに到達するために十分な時間を費やす必要はありません。

さらに、MicrosoftやSunなどの巨大な営利団体の支援を受けている言語は、そのような支援を受けていない言語と比較して、市場で常に有利であることを認める必要があります。

元の質問に答えるために、Paul Grahamは例を挙げようとします ここ それは必ずしも実用的ではないことを認めますが)私が望むように:-)

4
Pat

私が感銘を受けた1つの特定のことは、含まれているCLOSが気に入らない場合に、独自のオブジェクト指向プログラミング拡張機能を作成できることです。

それらの1つは Garnet にあり、もう1つはPaul Grahamの On LISP にあります。

Screamer というパッケージもあります。これは非決定論的なプログラミングを可能にします(私は評価していません)。

さまざまなプログラミングパラダイムをサポートするように言語を変更できる言語は、柔軟である必要があります。

4
David Thornley

それがマルチパラダイム言語であるという単純な事実は、それを非常に柔軟にします。

2
temp2290

この投稿 by EricNormandが役立つかもしれません。彼は、コードベースが成長するにつれて、LISPがアプリケーションに合わせて言語を構築できるようにする方法について説明します。これは多くの場合、早い段階で余分な労力を要しますが、後で大きな利点が得られます。

2
drewr