web-dev-qa-db-ja.com

定義、レット、セットの違い!

OK、これはかなり基本的な質問です。SICPビデオをフォローしていますが、defineletset!の違いについて少し混乱しています。

1)ビデオのSussmanによると、defineは変数に値を1回だけアタッチできます(REPL内を除く)。特に、2つのインライン定義は許可されていません。しかし、Guileはこのコードを楽しく実行しています

(define a 1)
(define a 2)
(write a)

期待どおり2を出力します。私がこれをしようとすると(編集:上記の定義の後)なので、状況は少し複雑になります。

(define a (1+ a))

エラーが発生する

(set! a (1+ a))

許可されている。それでも、これがset!defineの唯一の違いだとは思いません。何が欠けているのですか?

2)defineletの違いは私をさらに困惑させます。理論的には、letはローカルスコープの変数をバインドするために使用されます。それでも、これはdefineと同じように機能するようです、たとえば、私は置き換えることができます

(define (f x)
    (let ((a 1))
        (+ a x)))

(define (g x)
    (define a 1)
    (+ a x))

fgは同じように機能します。特に、変数agの外でもバインドされていません。

これが役立つと思う唯一の方法は、letのスコープが関数定義全体よりも短いことです。それでも、必要なスコープを作成するためにいつでも無名関数を追加して、すぐにそれを呼び出すことができるように思えます。JavaScriptで行うようにです。それで、letの本当の利点は何ですか?

40
Andrea

(+ 1 a)ではなく(1+ a)ですか?後者は構文的に有効ではありません。

letで定義された変数のスコープは後者にバインドされているため、

(define (f x)
  (let ((a 1))
    (+ a x)))

構文的には可能ですが、

(define (f x)
  (let ((a 1)))
  (+ a x))

ではありません。

すべての変数は、関数の先頭でdefinedにする必要があるため、次のコードが可能です。

(define (g x)
  (define a 1)
  (+ a x))

このコードはエラーを生成しますが:

(define (g x)
  (define a 1)
  (display (+ a x))
  (define b 2)
  (+ a x))

定義後の最初の式は、他の定義がないことを意味するためです。

set!は変数を定義せず、変数に新しい値を割り当てるために使用されます。したがって、これらの定義は無意味です。

(define (f x)
  (set! ((a 1))
    (+ a x)))

(define (g x)
  (set! a 1)
  (+ a x))

set!の有効な使用法は次のとおりです。

(define x 12)
> (set! x (add1 x))
> x
13

Schemeは関数型言語であるため、お勧めできません。

16

混乱は合理的です。「let」と「define」はどちらも新しいバインディングを作成します。 「レット」の1つの利点は、その意味が非常に明確に定義されていることです。昔ながらの「レット」が何を意味するかについて、さまざまなSchemeシステム(ラケットを含む)の間に絶対的な不一致はありません。

「定義」フォームは、別の魚のやかんです。 'let'とは異なり、本体(バインディングが有効な領域)を括弧で囲みません。また、それはトップレベルと内部で異なることを意味する可能性があります。 Schemeシステムが異なれば、「定義」の意味も劇的に異なります。実際、ラケットは最近、発生する可能性のある新しいコンテキストを追加することにより、「定義」の意味を変更しました。

一方、「定義」のような人々。インデントが少なく、通常、「do-what-I-mean」レベルのスコープがあり、再帰的および相互再帰的なプロシージャの自然な定義を可能にします。実際、私は先日これに噛まれました:)。

最後に、「設定!」; 「let」、「set!」のように非常に簡単です。既存のバインディングを変更します。

FWIW、DrRacketでこれらのスコープを理解する1つの方法(使用している場合)は、[構文の確認]ボタンを使用してから、さまざまな識別子にカーソルを合わせて、それらがバインドされている場所を確認します。

34
John Clements

ジョン・クレメンツの答えは良いです。場合によっては、Schemeの各バージョンでdefinesがどうなるかを確認できます。これは、何が起こっているのかを理解するのに役立つ場合があります。

たとえば、Chez Scheme 8.の場合(独自のdefine癖、特にR6RSに対して!):

> (expand '(define (g x)
             (define a 1)
             (+ a x)))
(begin
  (set! g (lambda (x) (letrec* ([a 1]) (#2%+ a x))))
  (#2%void))

「トップレベル」の定義はset!になります(ただし、defineを展開するだけで状況が変わる場合があります!)が、内部の定義(つまり、defineが別のブロック内にある場合)はletrec*になります。異なるスキームは、その表現を異なるものに拡張します。

MzScheme v4.2.4

> (expand '(define (g x)
             (define a 1)
             (+ a x)))
(define-values
 (g)
 (lambda (x)
   (letrec-values (((a) '1)) (#%app + a x))))
7
erjiang

defineを複数回使用できる場合がありますが、それは慣用的ではありません。defineは環境に定義を追加していることを意味し、set!はいくつかの変数を変更していることを意味します。

私はGuileについて、そしてそれが(set! a (+1 a))を許可する理由についてはわかりませんが、aがまだ定義されていない場合、それは機能しません。通常、defineを使用して新しい変数を導入し、後でそれをset!でのみ変更します。

letの代わりに無名関数アプリケーションを使用できます。実際、これは通常、letが展開するものとまったく同じであり、ほとんど常にマクロです。これらは同等です:

(let ((a 1) (b 2))
  (+ a b))

((lambda (a b)
   (+ a b))
 1 2)

letを使用する理由は、わかりやすいためです。変数名は値のすぐ隣にあります。

内部定義の場合、Yasirが正しいかどうかはわかりません。少なくとも私のマシンでは、R5RSモードと通常モードでラケットを実行すると、関数定義の中央に内部定義が表示されるようになりましたが、標準の内容がよくわかりません。いずれにせよ、SICPのかなり後の方で、内部がポーズを定義する際の注意点について詳しく説明します。第4章では、相互再帰的な内部定義を実装する方法と、メタサーキュラーインタープリターの実装にとってそれが何を意味するかについて説明します。

だからそれにこだわる! SICPは素晴らしい本であり、ビデオ講義は素晴らしいです。

2
michiakig