私は主にC++(したがって、オブジェクト指向/命令型)プログラマーですが、関数型言語である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)
等々...
言うまでもなく、あまり実用的ではありません。重複したコードのオーバーヘッドは言うまでもありません。
私はここで何かが足りないのですか、それとも他に方法はありませんか?
(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が手続きであるべきだということを意味しています。
見逃しているかもしれないことの1つは、Schemeには「ステートメント」のようなものがないということです。すべてが式であり、ステートメントと見なす可能性のあるものも値を返します。これはif
に適用され、通常は値を返すために使用されます(例:(if (tea-drinker?) 'tea 'coffee)
。 C++とは異なり、条件式のほとんどの用途は、変数の変更や値の出力には使用されません。これにより、if
句に複数の式を含める必要がなくなります。
ただし、RossとRajeshが指摘しているように、cond
(推奨)を使用するか、begin
句でif
sを使用できます。条件付きで多くの副作用のある計算がある場合は、Schemeを慣用的に使用していない可能性があることに注意してください。
@RajeshBhatは、begin withifステートメントを使用する良い例を示しました。
別の解決策は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 Schemeのドキュメントへのリンクです。PetiteChezは無料ですが(プチバージョンにはコンパイラはありません)、独自仕様であるため、使用している実装とは異なる場合があります(おそらく読む))
http://scheme.com/tspl4/syntax.html#./syntax:s39
編集:開始フォーム、したがって暗黙の開始を含むすべての式に関する重要な注意事項。
次のコード
(+ 2 (begin 3 4 5))
これは、begin
フォームの戻り値が最後の式であるためです。これは、使用を開始するときに覚えておくべきことです。ただし、副作用やディスプレイなどを使用すると、3と4がある位置で問題なく機能します。
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プログラムはそれ自体がs-exprであるため、基本的に制限はありません(字句スコープと、すべてを括弧で囲む必要があることを除いて)。
また、必要に応じて、副作用の使用を思いとどまらせないでください。スキームの栄光はあなたができることですあなたが望むものは何でも。
「内部」プロシージャですでに反復プロセスを使用しているので、named letを使用してこの定義を使用してみませんか。
(define (fact n)
(let inner ((counter 1) (result 1))
(if (> counter n)
result
(inner (+ counter 1) (* result counter)))))
プロセスの状態は2つの変数だけで判別できるため、それほど多くのメモリを使用しません。
たとえば(ファクト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)))