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;
}
a
inmutate
は 内にのみ存在する変数であるため、コードは 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
を渡し、その の値を変更できます。car
3
cons
car
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と呼ばれる、読み書き用のメモリ位置を参照する非常に広範な方法を定義しています。
一般化された参照は、読み書き可能な変数であるかのように、場所と呼ばれることもあるフォームの使用です。場所の値は、場所フォームが評価するオブジェクトです。場所の値は、setf を使用して変更できます。場所を束縛するという概念は Common Lisp では定義されていませんが、この概念を定義することによって言語を拡張する実装が許可されています。
Common Lisp では、 を使用して場所に割り当てることができますsetf
。sds が提供した提案は、グローバル変数シンボルを の場所として使用するか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
最初の引数は、または のいずれかのシンボルである必要があり、最初の引数が の場合に指定する必要があるwrite
2 番目の引数は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)
そのコードは確かに読む価値があります!