3

LISP でポインターを使用して C の動作を模倣する方法があるかどうかを知りたいです。C では、ポインタが指している変数の値を変更すると、グローバルな効果があります (つまり、値は関数の外でも変更されます)。

もし私が持っていたら

(defun mutate ( a ) 
   (some-magic-function a 5)
)

a は mutate を呼び出した後、以前の値に関係なく 5 に変わります。

リストを持つ要素でそれが可能であることは知っています (ほとんどの副作用として) common-lisp では、元のリストを変更せずに関数内からリスト パラメーターの一部を変更するにはどうすればよいですか? しかし、リスト全体に対してそれを行う方法を知りたいです。

4

3 に答える 3

8

Cコードの議論

sds の回答は質問の要点に対処していますが、エミュレートしている C コードで何が起こるかについて少し混乱しているように見えます。

LISP でポインターを使用して C の動作を模倣する方法があるかどうかを知りたいです。C では、ポインターが指している変数の値を変更すると、グローバルな効果があります (つまり、値は関数の外でも変更されます)。

あなたが提供したLispコードに最もよく似ていると私が思う次のことを考えてみてください:

#include<stdio.h>

int a = 3;

int mutate( int a ) {
  return a = 5;
}

int main() { 
  mutate( a );         /* or mutate( 8 ) or anything other argument */
  printf( "%d\n", a ); /* prints 3 */
  return 0;
}

ainmutateは 内にのみ存在する変数であるため、コードは 3 を出力しますmutate。グローバルと名前を共有しているからといって、a一方を変更しても他方が変更されるわけではありません。このコードでmutateの変数の値を変更できる唯一の場所aは ですmutate。「[a] ポインターが指している変数の [the] 値を変更する」というオプションはありません。できることは、ポインターを変数の値に渡し、そのポインターを介して値を変更し、後で値の結果を観察することです。これは、次の C コードに対応します。

#include<stdio.h>

int a = 3;

int mutate( int *a ) {
  return (*a = 5);
}

int main() { 
  mutate( &a );
  printf( "%d\n", a ); /* prints 5 */
  return 0;
}

構造による間接化

Common Lisp でも、好きな種類の間接化を使用して、このようなことを行うことができます。たとえば、 が であるセルを作成した場合、aそれconsを渡し、その の値を変更できます。car3conscar

CL-USER> (defparameter *a* (cons 3 nil))
*A*
CL-USER> (defun mutate (cons)
           (setf (car cons) 5))
MUTATE
CL-USER> (mutate *a*)
5
CL-USER> (car *a*)
5

ただし、Lisp には address-of 演算子がないため、C コードとまったく同じように行うことはできず、このアプローチを使用する場合は、常に何らかの方法で値を「ラップ」する必要があります。 . コンスセル、ベクトル、その他見つけられるものなど、Common Lisp 内の既存の構造を使用できます。

一般化された参照

C スタイルのポインターはありませんが、Common Lisp は、 Generalized Referenceと呼ばれる、読み書き用のメモリ位置を参照する非常に広範な方法を定義しています。

5.1.1 場所の概要と一般化された参照

一般化された参照は、読み書き可能な変数であるかのように、場所と呼ばれることもあるフォームの使用です。場所の値は、場所フォームが評価するオブジェクトです。場所の値は、setf を使用して変更できます。場所を束縛するという概念は Common Lisp では定義されていませんが、この概念を定義することによって言語を拡張する実装が許可されています。

Common Lisp では、 を使用して場所に割り当てることができますsetfsds が提供した提案は、グローバル変数シンボルを の場所として使用するかsetf、または とともに使用することで、グローバル変数の値を変更できるという共通点を共有していますsymbol-value。つまり、 (defparameter *a* 3)両方のような定義の後、*a*(symbol-value '*a*)はの新しい値を格納できる場所*a*です。placeその結果、変数名とを使用してマクロを作成したいと思います。これにより、value任意の場所を引数として使用できることが明確になります。

(defmacro mutate (place value)
  `(setf ,place ,value))

レキシカル クロージャを使用して変数への C スタイルのポインタをシミュレートする

レキシカル変数も場所であるため、まだ考慮されていない別のオプションがあります。レキシカル クロージャを使用して、C スタイルのポインタと同じ種類の機能を提供する関数を作成できます。

(defmacro make-pointer (place)
  `(lambda (op &optional value)
     (ecase op
       ((read)  ,place)
       ((write) (setf ,place value)))))

(let* ((x 3)
       (xp (make-pointer x)))
  (funcall xp 'write 5)             ; write a new value to x
  (list (funcall xp 'read)          ; read the value from x through xp
        x))                         ; read the value from x directly
;=> (5 5)

このコードでmake-pointerは、1 つまたは 2 つの引数で呼び出すことができる関数を返します。read最初の引数は、または のいずれかのシンボルである必要があり、最初の引数が の場合に指定する必要があるwrite2 番目の引数はwrite、その場所に格納する新しい値です。で呼び出すとread、場所の値が返されます。で呼び出されるとwrite、新しい値が格納されて返されます。

ただし、ここでの複数評価にはいくつかの問題があります。たとえば、次のことを行う場合(print 2)、値を返すことを思い出して2ください。

(make-pointer (aref some-array (print 2)))

ポインターを使用して読み取りまたは書き込みを行う2 たびに印刷することになりますが、これはおそらく望ましくありません。この質問でこれに対処する必要があるかどうかはわかりませんが、これを回避するためのいくつかの可能な方法について読み続けてください.

同様の質問 ( How to mutate global variable passed to and mutated inside function? ) に関するいくつかの調査の後、Lisp マシン (Common Lisp ではなく、Lisp Machine Lisp を実行した) が C ポインターに似た概念を持っていたことは注目に値しますCommon Lispへの回答で簡単に言及されている locatives 、 value および actual value への参照検索する用語がわかれば、 Chapter 13. locatives from the Lisp Machine Manual や Common Lisp のさまざまな再実装(で終わる長いコメントで始まるAlan Crowe のものを含む) を含む、locative についての詳細を簡単に見つけることができます。 ) 簡潔な要約:

;;; The basic idea is to use closures

後で(ソースは非常にうまく読みます)、次のようになります。

;;; It looks as though we are done
;;; now we can translate C code
;;; &x = (addr x), *x = (data x)

しかし、注意点があります

;;; The trouble is, we have a multiple evaluation bug.

Crowe はget-setf-expansion、場所にアクセスする方法を記憶し、毎回評価する必要なく値を保存する関数を作成するために使用する方法を示しています。(print 2)そのコードは確かに読む価値があります!

于 2013-10-21T12:46:19.397 に答える
1

これは、Lisp で「場所」と見なされる任意の格納場所の「アドレスを取得」できるようにする Common Lisp モジュールです。変数だけでなく、構造体や配列などのスロットも同様です。

C の場合と同様に、任意の複雑な式でアドレスを取得するストレージの場所を参照できます。

たとえば、は、セルである(ref (foo-accessor (cdr (aref a 4)))array の 5 番目の要素を追跡し、その を取得して、そこから取得したオブジェクトに適用することによって取得される格納場所への参照を作成します。acdrfoo-accessor

参照を逆参照する場合、チェーン全体を毎回通過するのではなく、メモリの場所に直接移動します。

簡単な使用法は次のとおりです。

(defun mutate-to-five (ptr)          |            void mutate_to_five(int *a)
  (setf (deref ptr) 5))              |            { *ptr = 5; }
                                     |
(defparameter a 42)                  |            int a = 42;
                                     |            /*...*/
(mutate-to-five (ref a))             |              mutate_to_five(&a);

ここで得られるものは、C ポインターよりもはるかに安全であることに注意してください。これらのポインターは決して悪くなりません。このタイプの参照がどこかに存在する限り、その場所を保持するオブジェクトは消えません。たとえば、レキシカル変数への参照を安全に返すことができます。これらのポインターは安価ではありません。逆参照には、単にメモリ アドレスを追跡するのではなく、クロージャーへの関数呼び出しが含まれます。ストレージの場所にアクセスするために必要な情報は、クロージャのレキシカル変数バインディング環境に格納されます。取得と設定を実行するコードを実行するには、そのクロージャを呼び出す必要があります。

Lisp に似た言語は、拡張機能として実際のポインターに近いものをサポートできます。ガベージ コレクタが複雑になります。たとえば、ある配列の 53 番目の要素を直接指すアドレスであるポインターを使用できるとします (重いレキシカル クロージャー トリックは必要ありません)。ガベージ コレクターは、「内部」ポインターを介してこのように参照された配列を再利用しないように、これらのポインターをトレースする必要があります。このような拡張がない場合、Lisp ガベージ コレクタは内部ポインタを気にする必要がありません。内部ポインタは発生しません。ヒープ上のオブジェクトは、適切なベース アドレスへのポインターによって常に参照されます。内部ポインターは、ガベージ コレクターが「このアドレスを含むオブジェクトはどれか?」という質問を解決する必要があることを意味します。ソリューションには、ツリーなどの動的データ構造の検索が含まれる場合があります。(これは Linux カーネルで採用されているアプローチであり、任意の仮想アドレスをstruct vmaそのアドレスを保持する仮想メモリ マッピングを記述する記述子)。

実装:

;;;
;;; Lisp references: pointer-like place locators that can be passed around.
;;; Source: http://www.kylheku.com/cgit/lisp-snippets/plain/refs.lisp
;;; 
;;; How to use:
;;;
;;; Produce a reference which "lifts" the place designated
;;; by form P:
;;;
;;;   (ref p)
;;;
;;; Dereference a reference R to designate the original place:
;;;
;;;   (deref r)
;;;   (setf (deref r) 42) ;; store new value 42
;;;
;;; Shorthand notation instead of writing a lot of (deref)
;;; Over FORMS, A is a symbol macro which expands to 
;;; (DEREF RA), B expands to (DEREF RB):
;;;
;;;   (with-refs ((a ra) (b rb) ...)
;;;     
;;;     ... forms)
;;; 
(defstruct ref 
  (get-func) 
  (set-func))

(defun deref (ref) 
  (funcall (ref-get-func ref))) 

(defun (setf deref) (val ref) 
  (funcall (ref-set-func ref) val)) 

(defmacro ref (place-expression &environment env)
  (multiple-value-bind (temp-syms val-forms 
                        store-vars store-form access-form)
                        (get-setf-expansion place-expression env)
    (when (cdr store-vars)
      (error "REF: cannot take ref of multiple-value place"))
    `(multiple-value-bind (,@temp-syms) (values ,@val-forms)
       (make-ref
         :get-func (lambda () ,access-form)
         :set-func (lambda (,@store-vars) ,store-form)))))

(defmacro with-refs ((&rest ref-specs) &body forms) 
  `(symbol-macrolet 
     ,(loop for (var ref) in ref-specs 
            collecting (list var `(deref ,ref))) 
     ,@forms))
于 2013-10-23T23:13:24.537 に答える