1

Tcl 8.5では、次のようなことができます。

apply llength { 1 2 3 }

ただし、その適用はv8.4では定義されていません。

v8.4でTclを使用して適用を定義するにはどうすればよいですか?

いくつかのlispコードをTclに変換しているので、これが必要です。lispコードには、次のように移植したいいくつかの構造があります。

array set levels {
  TRACE  0
  DEBUG  1
  INFO   2
  WARN   3
  ERROR  4
}

set LOG_LEVEL INFO
proc setLogLevel { level } {
  global LOG_LEVEL

  set LOG_LEVEL $level
}

proc log { tag msg args } {
  global levels
  global LOG_LEVEL

  # Filter out any messages below the logging severity threshold.
  if { $levels($LOG_LEVEL) <= $levels($tag) } then {
    apply format $msg $args
  }
}

proc logTrace { msg args } {
  apply log TRACE $msg $args
}
proc logDebug { msg args } {
  apply log DEBUG $msg $args
}
proc logInfo { msg args } {
  apply log INFO $msg $args
}
proc logWarn { msg args } {
  apply log WARN $msg $args
}
proc logError { msg args } {
  apply log ERROR $msg $args
}

# Close solution (not quite correct)
proc apply {func args} {
  eval [list $func] $args
}

# Example usage:
set instName "myInst"
set viewName "myView"
set cellName "myCell"
logError "Divide by zero."

# Filtered message:
logTrace "Evaluating callbacks for instance %s." $instName
# Enable that same message
setLogLevel TRACE
logTrace "Evaluating callbacks for instance %s." $instName

# This one fails with apply definition given here
logInfo "Opening cellView %s@%s." $viewName $cellName

ありがとう。

-ウィリアム

4

4 に答える 4

5

元の質問に対する私のコメントに対するあなたの回答に基づいて、あなたが思うように機能する適用関数を作成しようとするのではなく、 eval を適切に使用することを学ぶことをお勧めします。私の推論は、eval を理解していない場合、apply コマンドの作成方法と使用方法を理解するのに十分な知識がないということです。

信じられないかもしれませんが、apply コマンドの実装は多かれ少なかれ正しいのですが、使い方が間違っていました。問題を解決する他の方法がある場合、それを適切に使用する方法と理由を説明することは、面倒なことではありません。

あなたの問題はこれに要約されます: 関数と N 個の引数が与えられ、正確に N 個の引数でその関数を呼び出す方法が必要です。そのための適切な解決策は、eval を使用することです。

ログ関数を書き直す方法は次のとおりです。私は自由にコードを追加して、コードのように結果を計算して返すのではなく、実際に結果を出力しました。エラーレベルを出力するコードも追加しました。

proc log { tag msg args } {
  global levels
  global LOG_LEVEL

  # Filter out any messages below the logging severity threshold.
  if { $levels($LOG_LEVEL) <= $levels($tag) } then {
      set result [eval format \$msg $args]
      puts "$LOG_LEVEL: $result"
  }
}

ここで理解しておくべき重要な点がいくつかあります。まず、'args' という単語は特別で、すべての追加の引数がリストに集められることを意味します。したがって、log を引数なし、1 つの引数、または N 個の引数のいずれで呼び出しても、args はリストであり、値が 0 または 1 つのリストであっても、常にリストになります。

あなたが発見したように、 format コマンドは (潜在的に) N 個の引数のリストではなく N 個の引数を必要とします。Tcl でこれを回避するには、eval ステートメントを使用します。単純化した説明は、eval によって行が 2 回解析されることです。

これは、1 レベルの "listness" を効果的に削除するという点で $args に適しています。N 個の項目のリストだったものが、N 個の別個の項目になります。ただし、$msg は N 項目のリストではないため、2 回解析されることは望ましくありません。$ の前にバックスラッシュがあるのはそのためです。これにより、パーサーの最初のパスからドル記号が隠されます。[list $msg] を好む人もいますが、同じタスクを達成する方法は他にもあります。

(この特定のコードを使用したこの特定のケースでは、$msg が 2 回解析されても問題はないことに注意してください。eval を使用するときに明示的に展開したくないものを常に保護することをお勧めします。これは、ここで説明する価値のない理由からです)。

次に、他のログ機能に注意を向ける必要があります。それらは同様に機能し、同様の治療が必要です。これらはすべて基本的にパススルー コマンドであり、引数が 1 つ追加されています。再び eval を使用すると、logInfo は次のようになります。

proc logInfo {msg args} {
    eval log INFO \$msg $args
}

ここでも、$msg の前にバックスラッシュがあることに注意してください。これは上記と同じ理由によるものです -- $msg ではなく、$args の追加の解析ラウンドが必要です。

これら 2 つの変更により、コードが機能します。

ただし、logX 関数を実装するには、間違いなくより良い方法があります。追加の引数を追加して、他のすべてをそのままログ関数に渡すだけなので、エイリアスを作成するインタープリターの機能を利用できます。例えば:

interp alias {} logTrace {} log TRACE
interp alias {} logDebug {} log DEBUG
interp alias {} logInfo {} log INFO
interp alias {} logWarn {} log WARN
interp alias {} logError {} log ERROR

上記のコードでは、中括弧は単に「現在のインタープリター内」を意味します。Tcl には複数のインタープリターを実行する機能がありますが、それは目前の問題では重要ではありません。たとえば、logTrace を呼び出すと、Tcl は実際に「log TRACE」を呼び出し、最後に追加の引数を追加します。したがって、「logTrace foo bar」は「log TRACE foo bar」になります。

あなたは大量の LISP コードを Tcl に移植することに関心があり、頭の体操をできるだけ少なくしたいと考えていますが、これは理解できることです。あなたの特定のケースでは、LISPコードで適用されるところはどこでも、「eval」に置き換えることができると言うのがおそらく安全だと思います。次に、追加の解析を必要としないものを保護するための追加の手順を実行します。

于 2009-09-22T18:21:21.500 に答える
0

驚いたことに、Tcl でもapplyと呼ばれています。

于 2009-09-21T18:07:49.160 に答える
0

あなたのソリューションは を使用する必要があると確信していevalますuplevel.uplevelまたはupvar.

于 2009-09-21T18:22:08.570 に答える
0

簡潔な答え:

変数にコマンド名がある場合は、変数を行の最初の単語として配置することで実行できます。

set mycommand puts
$mycommand "hello world"

より長い答え:

コマンドのエッジケースを壊さずに拡張したい引数があるので、 eval を使用して行を作成したら「再解析」できます。基本的に、「eval」を使用してすべての引数を展開し、「list」を使用して特定の引数を展開から保護できます

% proc {my llength of args} {args} { return [llength $args] }
% set mycommand {my llength of args}
% set args "1 2 3"
% eval $mycommand $args ;# Expands the command, so may blow up
ambiguous command name "my": {my llength} {my llength of args}
% eval [list $mycommand] [list $args] ;# protect the args, so it's not expanded, not what you want
1
% eval [list $mycommand] $args ;# Protect the things you don't want expanded (command named), but allow the args to be expanded to individual arguments
3
于 2009-09-21T20:23:11.613 に答える