24

私は主に C++ (したがって OO/命令型) プログラマーですが、関数型言語である Scheme の if ステートメントなどの条件ステートメントで、評価ごとにステートメントを 1 つしか持てないのは非常に奇妙です。

例えば:

 (let ((arg1 0) (arg2 1))
   (if (> arg1 arg2)
       arg1
       arg2)))

誤った例:

(let ((arg1 0) (arg2 1))
  (if (> arg1 arg2)
      (arg1 (display "cool"))
      (arg2 (display "not cool"))))

タイプ「プロシージャアプリケーション:予想されるプロシージャ、与えられた:2;引数は:#void」のエラーが表示されます

これは、その条件ステートメントを、定義された関数の本体内の異なるステートメントに配置することで解決できます。たとえば、条件ステートメントの本体には、次のように毎回個別のステートメントがあります。

(if (condition) statement1a statement2a)
(if (condition) statement1b statement2b)

等々...

あまり実用的でないことは言うまでもありません。重複コードのオーバーヘッドは言うまでもありません。

ここで何か不足していますか、それとも他に方法はありませんか?

4

5 に答える 5

24
(let((arg1 0)(arg2 1))
  (if (> arg1 arg2) 
      (begin
        (display arg1)
        (newline)
        (display "cool"))
      (begin
        (display arg2)
        (newline)
        (display "not cool"))))

(arg1 (disply "cool")) と言うときは、arg1 が手続きであることを暗示しています。

于 2012-06-29T14:21:42.737 に答える
10

見落としているかもしれないことの 1 つは、Scheme には「ステートメント」のようなものがないということです。すべてが式であり、ステートメントと見なされるものも値を返します。ifこれは、通常、値を返すために使用される に適用されます (例: (if (tea-drinker?) 'tea 'coffee).C++ とは異なり、条件のほとんどの使用は、変数の変更や値の出力には使用されません。これにより、if句に複数の式を含める必要性が減少します。

ただし、Ross と Rajesh が指摘したように、節で s を使用cond(推奨) または使用できます。条件に多くの副作用のある計算がある場合、Scheme を慣用的に使用していない可能性があることに注意してください。beginif

于 2012-06-29T15:27:33.777 に答える
5

@RajeshBhat は、if ステートメントで開始する使用の良い例を示しました。

別の解決策はcondフォームです

(let ([arg1 0] [arg2 1])
  (cond
    [(< arg1 0) (display "negative!")]
    [(> arg1 arg2) (display arg1) (newline) (display "cool")]
    [else (display arg2) (newline) (display "not cool")]))

フォームの各行にはcond暗黙beginの があり、 の実装を見れば実際に確認できますcond

(リンクは Chez スキームのドキュメントへのリンクです。Petite Chez は無料ですが (小柄なバージョンにはコンパイラはありません)、プロプライエタリであるため、使用している実装とは異なる可能性があります (読む: おそらく)))

http://scheme.com/tspl4/syntax.html#./syntax:s39

編集: begin フォーム、したがって暗黙的な begin を持つすべての式に関する重要な注意事項。

次のコード

(+ 2 (begin 3 4 5))

これは、フォームの戻り値がbegin最後の式であるためです。これは、使用を開始するときに覚えておくべきことです。ただし、副作用やディスプレイなどを使用すると、3 と 4 の位置で問題なく動作します。

于 2012-06-29T14:57:00.663 に答える
1

Schemeの構文に制限があると感じた場合は、マクロを定義することでいつでも構文を変更できます。マクロはラムダに似ていますが、コンパイル時にコードを生成し(C ++テンプレートのように)、マクロが呼び出される前に引数が評価されない点が異なります。

マクロを簡単に作成して、通常はプロシージャアプリケーションを意味する構文を使用できるようにすることができます(arg1 "cool")。たとえば、「各項目の後に改行を付けて括弧内のすべてを表示する」という意味です。(もちろん、それはマクロの内部だけを意味します。)このように:

(define-syntax print-if
  (syntax-rules ()
    [(_ c (true-print ...) (false-print ...))
      (if c
          (display-with-newlines true-print ...)
          (display-with-newlines false-print ...))]))

(define-syntax display-with-newlines
  (syntax-rules ()
    [(_ e)
      (begin (display e) (newline))]
    [(_ e0 e* ...)
      (begin (display-with-newlines e0) (display-with-newlines e* ...)) ]))

(let ([arg1 0] [arg2 1])
  (print-if (> arg1 arg2)
            (arg1 "cool")
            (arg2 "not cool")))

出力:

1
not cool

マクロ定義が現在どのように機能するかを理解していなくても心配しないでください。C ++をマスターした後でSchemeを試しているだけの場合は、多くのフラストレーションを経験していることは間違いありません。スキームが実際に持っている種類のパワーと柔軟性を垣間見る必要があります。

SchemeマクロとC++テンプレートの大きな違いは、マクロでは、Scheme言語全体を使用できることです。マクロは、Schemeを使用して、s-exprをSchemeコードに変換する方法を任意の方法で指示します。次に、コンパイラは、マクロによって出力されたSchemeコードをコンパイルします。Schemeプログラムはそれ自体がs-exprsであるため、基本的に制限はありません(字句スコープと、すべてを括弧で囲む必要があることを除いて)。

また、必要に応じて、副作用の使用を思いとどまらせないでください。スキームの栄光は、あなたがやりたいことを何でもできるということです。

于 2012-07-03T15:25:08.843 に答える
1

「内部」プロシージャですでに反復プロセスを使用しているため、名前付き letを使用してこの定義を使用しないでください。

(define (fact n)
  (let inner ((counter 1) (result 1))
    (if (> counter n)
        result
        (inner (+ counter 1) (* result counter)))))

プロセスの状態は 2 つの変数だけで判断できるため、それほど多くのメモリを使用しません。

たとえば (fact 6) は次のように計算されます

(inner 1 1)
(inner 2 1)
(inner 3 2)
(inner 4 6)
(inner 5 24)
(inner 6 120)
(inner 7 720)
720

以下は、同じ手順の letrec バージョンです。

(define (fact n)
  (letrec ((inner
            (lambda (counter result)
              (if (> counter n)
                  result
                  (inner (+ counter 1) (* result counter))))))
    (inner 1 1)))
于 2012-06-30T13:03:23.457 に答える