Определить тип переменной в Tcl

Я ищу способ найти тип переменной в Tcl. Например, если у меня есть переменная $a, и я хочу знать, является ли она целым числом.

До сих пор я использовал следующее:

    if {[string is boolean $a]} {
    #do something
    }

и это отлично работает для следующих типов:
alnum, alpha, ascii, boolean, control, digit, double, false, graph, integer, lower, print, punct, space, true, upper, wordchar, xdigit

Однако он не может сказать мне, может ли моя переменная быть массивом, списком или словарем. Кто-нибудь знает способ узнать, является ли переменная одной из этих трех?


person Tom    schedule 15.09.2011    source источник
comment
для какой версии TCL вам это нужно?   -  person jk.    schedule 15.09.2011
comment
Можете ли вы объяснить, зачем вам это нужно в первую очередь? Я имею в виду, поскольку Tcl по сути является бестиповым языком, то, о чем вы просите, выглядит как просьба о проблемах.   -  person kostix    schedule 15.09.2011
comment
@Kostix, конечно. Причина, по которой мне это нужно, состоит в том, чтобы создать процедуру, в которой словарь анализируется в JSON. Например, строки в JSON окружены, а целые числа — нет. Кроме того, если словарь должен содержать другой словарь, этот словарь должен получить свой собственный объект JSON внутри объекта JSON.   -  person Tom    schedule 15.09.2011
comment
@Tom, тогда я бы выбрал другой подход и потребовал бы от пользователя вашего пакета предоставить сериализатору (искусственную) аннотированную структуру с использованием явных тегов типа. Как serialize [object $mykey1 [int $value] $mykey2 [float $bar]] и так далее. Попытка вывести тип бестипового значения чревата ошибками, которые невозможно исправить. Не говоря уже об очевидных случаях, когда я хочу, чтобы строка 0123 действительно сериализовалась как строка, а не интерпретировалась как 123 или 83 (см. wiki.tcl.tk/498 для интересного)   -  person kostix    schedule 16.09.2011
comment
Я смущен, если у вас 8.5, у вас должна быть строка, доступная в списке, но ее нет в списке типов, которые вы даете   -  person jk.    schedule 16.09.2011


Ответы (6)


Переменные Tcl не имеют типов (за исключением того, являются ли они действительно ассоциативным массивом переменных — т. е. используют синтаксис $foo(bar) — для которого вы используете array exists), но значения Tcl имеют. Ну, несколько. Tcl может изменять значения между различными типами по своему усмотрению и не раскрывает эту информацию[*]; все, что вы действительно можете сделать, это проверить, соответствует ли значение определенному типу.

Такие проверки соответствия выполняются с помощью string is (где вам нужна опция -strict по уродливым историческим причинам):

if {[string is integer -strict $foo]} {
    puts "$foo is an integer!"
}

if {[string is list $foo]} {    # Only [string is] where -strict has no effect
    puts "$foo is a list! (length: [llength $foo])"
    if {[llength $foo]&1 == 0} {
        # All dictionaries conform to lists with even length
        puts "$foo is a dictionary! (entries: [dict size $foo])"
    }
}

Обратите внимание, что все значения соответствуют типу строк; Значения Tcl всегда сериализуемы.

[EDIT из комментариев]: Для сериализации JSON можно использовать грязные хаки для создания «правильной» сериализации (строго говоря, помещение всего в строку было бы правильным с точки зрения Tcl, но это не совсем полезно для других языков) с Tcl 8.6. Код для этого, изначально опубликованный на Rosetta Code:

package require Tcl 8.6

proc tcl2json value {
    # Guess the type of the value; deep *UNSUPPORTED* magic!
    regexp {^value is a (.*?) with a refcount} \
        [::tcl::unsupported::representation $value] -> type

    switch $type {
        string {
            # Skip to the mapping code at the bottom
        }
        dict {
            set result "{"
            set pfx ""
            dict for {k v} $value {
                append result $pfx [tcl2json $k] ": " [tcl2json $v]
                set pfx ", "
            }
            return [append result "}"]
        }
        list {
            set result "\["
            set pfx ""
            foreach v $value {
                append result $pfx [tcl2json $v]
                set pfx ", "
            }
            return [append result "\]"]
        }
        int - double {
            return [expr {$value}]
        }
        booleanString {
            return [expr {$value ? "true" : "false"}]
        }
        default {
            # Some other type; do some guessing...
            if {$value eq "null"} {
                # Tcl has *no* null value at all; empty strings are semantically
                # different and absent variables aren't values. So cheat!
                return $value
            } elseif {[string is integer -strict $value]} {
                return [expr {$value}]
            } elseif {[string is double -strict $value]} {
                return [expr {$value}]
            } elseif {[string is boolean -strict $value]} {
                return [expr {$value ? "true" : "false"}]
            }
        }
    }

    # For simplicity, all "bad" characters are mapped to \u... substitutions
    set mapped [subst -novariables [regsub -all {[][\u0000-\u001f\\""]} \
        $value {[format "\\\\u%04x" [scan {& } %c]]}]]
    return "\"$mapped\""
}

Внимание! Приведенный выше код не поддерживается. Это зависит от грязных взломов. Он может сломаться без предупреждения. (Но это действительно работает. Для переноса на Tcl 8.5 потребуется крошечное расширение C для считывания аннотаций типов.)


[*] Строго говоря, он предоставляет неподдерживаемый интерфейс для обнаружения аннотации текущего типа значения в 8.6 — как часть ::tcl::unsupported::representation — но эта информация представлена ​​в преднамеренно удобочитаемой форме и может быть изменена без объявления. Это для отладки, а не кода. Кроме того, Tcl использует внутри довольно много разных типов (например, кешированные имена команд и переменных), которые вы не захотите проверять в обычных обстоятельствах; под капотом все довольно сложно…

person Donal Fellows    schedule 15.09.2011
comment
Спасибо за отзыв, но похоже, что это не сработает. В части if {[string is list $foo]} {. Строки, содержащие несколько слов, также отображаются как списки. Кроме того, если бы у меня был список, содержащий четное количество элементов, этот код всегда классифицировал бы его как словарь. Я не уверен, возможно ли то, что я пытаюсь сделать, хотя ваш код может быть лучшим решением. - person Tom; 15.09.2011
comment
@Tom, это потому, что строка из нескольких слов не отличается от переменной, созданной с помощью команды «список». Например, попробуйте следующее: установите foo ab bb dd; линдекс $foo 1 - person TrojanName; 15.09.2011
comment
это string is list 8.5 или 8.6?, насколько я понимаю, это может быть недоступно для Тома? - person jk.; 15.09.2011
comment
Это версия 8.5, которая является текущей стандартной версией производственного уровня. Если вы используете версию 8.4 (или более раннюю), вы будете считаться «устаревшим» (или застрявшим в Java). - person Donal Fellows; 15.09.2011
comment
@Tom: Ну, все списки четной длины являются допустимыми словарями, хотя, возможно, с далеко не идеальными представлениями (например, дубликаты ключей). - person Donal Fellows; 15.09.2011
comment
Я начинаю подозревать, что то, чего я пытаюсь достичь (преобразование dict в полностью действительный JSON), может быть невозможно в TCL. - person Tom; 15.09.2011
comment
Для Tcl и JSON см. wiki.tcl.tk/13419 и tcllib.sourceforge.net/doc/json.html - person glenn jackman; 15.09.2011
comment
@Tom: я написал работающий сериализатор JSON для Tcl 8.6 (см. rosettacode.org/wiki/JSON#Tcl). для кода), но помните, что это не считается особенно хорошим стилем. Хорошим стилем было бы иметь отдельный дескриптор типа, указывающий, что производить; Предположения о типах в Tcl действительно не совпадают с предположениями JSON (или Javascript, или Python, или Ruby, или ряда других языков). - person Donal Fellows; 16.09.2011
comment
Хм, этот сериализатор JSON хрупок. Я уже вижу, как это сломается. :-) - person Donal Fellows; 16.09.2011

Все остальные ответы содержат очень полезную информацию, но стоит отметить то, что многие люди сначала не понимают.

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

string is integer $a

ты не спрашиваешь

Является ли значение в $a целым числом

То, что вы спрашиваете,

Могу ли я использовать значение в $a как целое число

Полезно рассмотреть разницу между двумя вопросами, когда вы думаете о том, «является ли это целым числом». Каждое целое число также является допустимым списком (из одного элемента)... поэтому can be used как любая, так и обе string is команды вернут true (как и несколько других для целого числа).

person RHSeeger    schedule 07.10.2011

Если вы хотите иметь дело с JSON, я настоятельно рекомендую вам прочитать страницу JSON на вики Tcl: http://wiki.tcl.tk/json.

На этой странице я разместил простую функцию, которая компилирует значения Tcl в строку JSON с заданным дескриптором форматирования. Я также нахожу обсуждение на этой странице очень информативным.

person slebetman    schedule 21.09.2011

Для массивов, которые вы хотите array exists для диктов, которые вы хотите dict exists

для списка я не думаю, что есть встроенный способ до 8.5?, есть это из http://wiki.tcl.tk/440

proc isalist {string} {
  return [expr {0 == [catch {llength $string}]}]
}
person jk.    schedule 15.09.2011
comment
Спасибо :) Можете ли вы объяснить, как бы вы использовали dict в этом случае? Я пытался использовать его так: if {[dict exists $testData2 nullval]} { #do something }, но он будет возвращать ошибки, если переменная не является dict. - person Tom; 15.09.2011
comment
@ Том, в этом случае я бы обернул ваш оператор if в «улов», что позволит вам обработать случай, когда переменная не является диктофоном. - person TrojanName; 15.09.2011

Чтобы определить, является ли переменная массивом:

proc is_array {var} {
    upvar 1 $var value
    if {[catch {array names $value} errmsg]} { return 1 } 
    return 0
}   

# How to use it
array set ar {}
set x {1 2 3}
puts "ar is array? [is_array ar]"; # ar is array? 1
puts "x is array? [is_array x]";   # x is array? 0
person Hai Vu    schedule 15.09.2011
comment
Вам не хватает [array exists varname] -- tcl.tk/man/tcl8.5 /TclCmd/array.htm - person glenn jackman; 15.09.2011
comment
jk, уже упомянутый массив существует, я просто хочу предложить другой способ сделать что-то. - person Hai Vu; 15.09.2011
comment
Понимаю. Генерация списка имен массивов (просто для того, чтобы их выбросить) обходится дорого: вместо этого вам может понадобиться array size. - person glenn jackman; 16.09.2011

Для конкретного случая определения того, можно ли использовать значение в качестве словаря, dicttool есть команда dict is_dict <value>, которая возвращает истинное значение, если <value> может действовать как таковое.

person Shawn    schedule 19.06.2020
comment
Довольно запоздалый ответ, но другой вопрос был просто закрыт как дубликат этого, поэтому он казался уместным. - person Shawn; 19.06.2020