Как правильно вернуть строковую константу из выражения? - PullRequest
0 голосов
/ 11 февраля 2019

Допустим, у меня есть следующее:

proc one_or_other {v1 v2} {
    if {[expr {round(rand())}]} {
        expr {$v1}
    } else {
        expr {$v2}
    }
}

Случайно возвращает одно из двух значений $v1 или $v2.Довольно простоИ он работает должным образом, пока вы не передадите ему строку типа «01232», которая может быть интерпретирована как восьмеричное число как expr.Итак, one_or_other 1234 01232 дает вам 666 половину времени.

Если я хочу, чтобы эта функция давала мне точно одну из двух строк, которые я ей передал (например, она дает мне "1234" или "01232"), что мне заменить expr {$v1} на?

1 Ответ

0 голосов
/ 11 февраля 2019

В общем, если вы хотите, чтобы в качестве результата команды использовалась общая строковая константа, эта команда не должна быть expr.Проблема заключается в том, что expr имеет значение , определенное для преобразования его результата в каноническую числовую форму, если это возможно, даже если нет других операций.

Это означает, что если x установлено в0x123, я бы всегда ожидал, что expr {$x} произведет 291.


Давайте немного отогнем капот и рассмотрим разборку байт-кода для expr {$x}:

% tcl::unsupported::disassemble script {expr {$x}}
ByteCode 0x0x7f9683041b10, refCt 1, epoch 17, interp 0x0x7f9683024410 (epoch 17)
  Source "expr {$x}"
  Cmds 1, src 9, inst 5, litObjs 1, aux 0, stkDepth 1, code/src 0.00
  Commands 1:
      1: pc 0-3, src 0-8
  Command 1: "expr {$x}"
    (0) push1 0     # "x"
    (2) loadStk 
    (3) tryCvtToNumeric 
    (4) done 

Есть куча вещей, которые мы можем игнорировать, но коды операций в конце - это вставка константы (которая является именем переменной) в стек операндов, чтениепеременная, названная в стеке операндов (в сочетании с предыдущим операцией, это $x), tryCvtToNumeric (подробнее об этом чуть позже) и done, чтобы отметить конец этого маленького сценария.

Так что же делает tryCvtToNumeric?Он реализует семантику результата expr и помещается туда всегда (кроме случаев, когда компилятор может доказать, что это не требуется, что на самом деле верно для большей части кода).Нет способа отключить это.

Разборка вашей процедуры показывает это.(Я пропущу биты, которые мы можем игнорировать здесь.)

(0) push1 0     # "tcl::mathfunc::round"
(2) push1 1     # "tcl::mathfunc::rand"
(4) invokeStk1 1 
(6) invokeStk1 2 
(8) nop 
(9) nop 
(10) jumpFalse1 +16     # pc 26
(12) startCommand +12 1     # next cmd at pc 24, 1 cmds start here
(21) loadScalar1 %v0    # var "v1"
(23) tryCvtToNumeric 
(24) jump1 +14  # pc 38
(26) startCommand +12 1     # next cmd at pc 38, 1 cmds start here
(35) loadScalar1 %v1    # var "v2"
(37) tryCvtToNumeric 
(38) done 

Как вы можете видеть, там есть tryCvtToNumeric экземпляров;ваш код имеет преобразования в нем.(Также обратите внимание, что код использует более эффективные операции с таблицами локальных переменных для чтения переменных. Это хорошо.)


Когда вам нужен общий строковый результат, используйте вместо него другие стандартные команды Tcl.В частности, set x (т. Е. one аргумент) - это команда, подобная $x, string cat 0x123 - это команда, которая выдает буквенную строку 0x123 и результат if(часто игнорируется) - результат выполнения сценария в ветке.Ваш настоящий сценарий затем становится (без дополнительных expr с):

proc one_or_other {v1 v2} {
    if {round(rand())} {
        set v1
    } else {
        set v2
    }
}

Давайте проверим, разобрав:

(0) push1 0     # "tcl::mathfunc::round"
(2) push1 1     # "tcl::mathfunc::rand"
(4) invokeStk1 1 
(6) invokeStk1 2 
(8) nop 
(9) jumpFalse1 +15  # pc 24
(11) startCommand +11 1     # next cmd at pc 22, 1 cmds start here
(20) loadScalar1 %v0    # var "v1"
(22) jump1 +13  # pc 35
(24) startCommand +11 1     # next cmd at pc 35, 1 cmds start here
(33) loadScalar1 %v1    # var "v2"
(35) done 

Это тот же код ... за исключением без tryCvtToNumeric opsкоторые доставляли вам неприятности.(Также на один меньше без операции.)

Лично я бы вместо этого использовал эту несколько более эффективную версию:

proc one_or_other {v1 v2} {
    if {rand() < 0.5} {
        return $v1
    } else {
        return $v2
    }
}

Я предпочитаю использовать явные return s и избегатьвызовы функций мне не нужны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...