9

Haskell では、次のようなラムダがある場合

(\x -> doStuff x y)

周囲のスコープからどこにyあるのか、それをセクションにして次のように変えることができます

(`doStuff` y)

これはより短く、より簡潔です (Haskell について私が最も気に入っていることの 1 つです)。

Common Lisp では、同等のコードを次のように記述します。

(lambda (x) (do-stuff x y))

そして、これは実際に私が書いている非常に一般的なことですが、ほんの少しのボイラープレートでさえ私をいくらか悩ませていると感じているので、Common Lisp で Haskell スタイルのセクションのようなものを取得する方法があるのだろうか?

4

7 に答える 7

11

あなたがもっと経験を積んでいない限り、Lisp で Haskell を書く方法ではなく、Lisp で Lisp を書く方法を学ぶことをお勧めします。後者は良い考えではありません。Haskell の動作は大きく異なります。

Lisp は「カリー化」(またはシェーンフィンケリング ;-)) を行いません。

次のように記述できます。

CL-USER 5 > (defun curry (fn arg) (lambda (&rest args) (apply fn arg args))) 
CURRY

CL-USER 6 > (mapcar (curry #'expt 2) '(2 3 4 5 6))
(4 8 16 32 64)

ただし、その方法では少し効率が悪くなります。

CL-USER 7 > (mapcar (lambda (base) (expt base 2)) '(2 3 4 5 6))
(4 8 16 32 64)

私は個人的に後者を好みます。なぜなら、私は変数に実際に読みやすい名前を付けているからです。これは、バックトレースが表示されるデバッガーで役立ちます。このようなツールは、おそらく Haskell よりも Lisp の方が重要です。

CL-USER 12 > (mapcar (lambda (base) (expt base 2)) '(2 3 "four" 5 6))

エラー。バックトレースを見てみましょう:

CL-USER 12 : 1 > :bb
...

Condition: In EXPT of ("four" 2) arguments should be of type NUMBER.

Call to SYSTEM::ARGS-TO-BINARY-ARITHMETIC-FN-NOT-OF-TYPE {offset 189}
  SYSTEM::FN-NAME : EXPT
  SYSTEM::ARG1    : "four"
  SYSTEM::ARG2    : 2
  TYPE  {Closing} : NUMBER

Interpreted call to (SUBFUNCTION :ANONYMOUS SYSTEM::ANONYMOUS-LAMBDA):
  BASE : "four"

これで、物に名前があることがわかります。"four"という名前の変数を使用して、文字列を関数に渡していましbaseた。

REPL とデバッグ ツールを使用したインタラクティブな開発が一般的です。この開発スタイルに役立つようにコードを準備してください。Common Lisp は、Haskell のように、広範な型チェックを備えた完全なプログラム コンパイラを提供するように最適化されていません。

Lispの主な問題の 1 つは、コードの一部が実際に何をしているのかを見つけるのが非常に難しいことです。デフォルト (プレフィックス構文を使用した厳密な関数型プログラム) は、比較的理解しやすいものです。しかし、Lisp のコードの意味を変更する可能性はたくさんあります (マクロ、読み取りマクロ、シンボル マクロ、メタ オブジェクト プロトコル、アドバイスなど)。

第 1 のルール: 基本的な Lisp コードを書いている場合は、基本的な構文と意味の可能性に固執してください。防御的に書く。他の誰かがコードを理解する必要があることを期待してください。そのためには、コードは読みやすく、理解しやすく、一般的なイディオムを使用し、デバッグ可能でなければなりません。

Haskell では、数学のバックグラウンドを持つ多くの人が、高度な抽象化を備えた非常にコンパクトな方法でコードを書きたいと考えています。Lispでもできます。しかし、通常のコードについては、私はその道をたどりませんし、より大きなコードの断片については、Lisp は他のメカニズム (マクロによるコード変換など) を使用することがよくあります。

于 2013-03-22T11:41:44.123 に答える
9

このようなフォームに対して、任意の特別な構文を開発できます。複数のバリエーションがあります。たとえば、私は Clojure にインスパイアされたシャープ バッククォート構文を使用しています。これを使用すると、フォームは次のようになります。

#`(do-stuff % y)
于 2013-03-22T11:38:36.087 に答える
5

直接はできないと思いますが…

と同等のことを常に行い、それを(lambda (x) (fun x lexical))より短い方法で表現したいことがわかっている場合は、理論的にはマクロを使用できます。

(lambda (x) (fun x lex))個人的には、そうしないことをお勧めします。多くの入力を必要とせず、コードからあいまいなレイヤーを1つ取り除きます。しかし、それが十分に一般的なパターンであり、特別な処理が必要な場合は、次のようにすることができます。

(defmacro section (function lexical)
   (let ((sym (gensym))
     `(lambda (,sym) (,function ,sym ,lexical))))

これにより、Haskell セクションは次のようになります。

(`doStuff` y)

Common Lisp セクションになります。

(section dostuff y)

そのため、少なくとも簡潔に言えば、それがより読みやすいとは思いませんが、それが私が何度も何度も見たものであった場合、私は実際に考えます (そして、他の何よりも実験目的で行ってきました)高速化するためのマクロ (私はどこかに、 (_ func _2 lexical _1)->のようなことを可能にする中途半端なマクロを持っています*(lambda (a b) (func b lexical a))。これは便利な場合もありますが、実際には読みやすさは向上しません)。

于 2013-03-22T11:10:15.413 に答える
3

Scheme にはcut(SRFI-26 の) マクロがあり、 で手続き呼び出しの穴を指定できます<>。例えば:

(cut doStuff <> y)  ;; same as (lambda (x) (doStuff x y))
(cut - 5 <> 6 <> 7) ;; same as (lambda (x y) (- 5 x 6 y 7))

おそらくCLで同様のものを定義できます。

于 2013-03-25T13:05:10.107 に答える
3

この場合に機能するLet Over Lambdaには、シャープな逆引用符読み取りマクロがあります。

CL-USER>
(print
  '#`,(+ a1 y))

(LAMBDA (A1) (+ A1 Y))
(LAMBDA (A1) (+ A1 Y))

CL-USER>
(let ((y 2))
  (mapcar #`,(+ a1 y)
          (list 1 2 3 4)))
(3 4 5 6)
CL-USER>

このアプローチは、@Vsevolod Dyomkin が言及した手法と非常によく似ています。Hoyte のバージョンには、任意の数の引数を使用してラムダを構築するなど、いくつかの追加機能があります。一方、1 レベル高い表記で表現されているため、解析が少し難しくなります。フォームを評価するには、逆引用符を外す必要があります (この例では「,」を使用)。

于 2013-03-24T07:41:33.823 に答える
2

また、Common Lisp では、Haskell スタイルの関数のカリー化と構成の容易さも見逃していました。その結果、簡潔なカリーと Lisp での構成のためのリーダー マクロを定義する次のパッケージを作成しました (アレクサンドリア関数を使用します)。

http://eschulte.github.io/curry-compose-reader-macros/

このパッケージで(mapcar (compose (curry #'* 2) (curry #'+ 1)) (list 1 2 3 4))となり(mapcar [{* 2} {+ 1}] (list 1 2 3 4))ます。現在、ほぼすべての CL プロジェクトでこれを使用しており、コード サイズが大幅に縮小され、可読性が向上することがわかりました。

于 2013-08-20T23:13:42.800 に答える