LINUX.ORG.RU

[tcl] upvar: как использовать в мирных целях?

 


0

0

Дорогой ЛОР,

Читая tutorial по tcl, я наткнулся на команду upvar, которая позволяет доставать переменные из других областей видимости. Например, так:

proc upvar_test {} {
    upvar 1 x _x
    if { [info exists _x] } {
        set _x "???"
    } {
        puts "variable x is not set"
    }
}

set x 1    ;# x is set to 1
upvar_test ;# x is now set to "???"

Страшновато, правда? Понятно, что upvar можно использовать во имя добра (чтобы эмулировать call by reference). Хорошо.

Вопрос. Можно ли как-то ограничить возможное злоупотребление командой upvar?


Ограничить? т.е. сделать переменную недоставаемой по upvar? Нет.

Можешь положит её (переменную) в жутко секретный неймспейс

set namespace [namespace eval ::mySecretNamespace::[sha1::sha1 -hex [clock clicks]] "variable myVar $mySecretVar"]

И доставать оттуда. :)

А пример в данном случае проще сделать так:

proc upvar_test {} {
    uplevel {set x "???"}
}

set x 1    ;# x is set to 1
upvar_test ;# x is now set to "???"
naryl ★★★★★
()
Ответ на: комментарий от naryl

Спасибо за развернутый ответ. В особенности - за параноидальное решение (как раз для моего случая: в приведенном примере подразумевается, что кто-то (хакиры?) знает имена моих переменных).

Мне казалось, что было бы полезно (для отладки, например) иметь формальные гарантии того, что вне области видимости мои переменные никто не тронет (за исключением случая, когда я сам передаю их куда-нибудь как аргумент). Возможно, проблема надумана. Мне не приходилось раньше много работать с динамическими языками (за исключением python, но там подобным образом можно достать только глобальные переменные).

ovidii
() автор топика
Ответ на: комментарий от ovidii

Попробуй eLisp ;) Там переменные, при отсутствии в данной области видимости, автоматически достаются у вызывающей функции, а если и там не будет - у вызвавшей её и т.д.

naryl ★★★★★
()
Ответ на: комментарий от naryl

Ты имеешь в виду, что бывает и хуже? Согласен. Я проверил еще раз. Python (2.5.2) ругается на такой код:

def test():
   print x # prints 0 (so x is visible)
   x += 1  # error: local variable 'x' referenced before assignment

x = 0
test()

Но допускает вот такое:

def test():
   x.append[1]

x = []
test() # x is [1] now

Так что в случае tcl все относительно логично.

ovidii
() автор топика
Ответ на: комментарий от ovidii

В python тоже логично - прямое присваивание запрещено, но global x и все работает.

rymis ★★
()

В подчинённом интерпретаторе можно скрыть команду upvar, можно также сделать менее мощный её аналог (например, проверять $level==1, которого для многих случаев достаточно). Но тогда нужно и uplevel запрещать: им можно сделать то же самое, чего вы опасаетесь. А уж trace такой trace, что утопиться можно.

В целом, если вы хотите «запретить злоупотребления» для модулей из вашего же проекта — это вам не к TCL (и я даже затрудняюсь сказать, куда; к психиатру вроде невежливо); если вы хотите исполнять недоверяемый код, тогда изолируйте его в safe interpreter, чтобы он ничего в основной программе не испортил; а если вы хотите минимизировать ошибки в проекте (наподобие dict set $myDictVar ..., в котором лишний $ не ловится даже в run time), тогда вам к статическим checker'ам (про frink слышал хорошее; на базе макропакета sugar можно сделать неплохое, но это надо делать — мои наработки ещё далеки от публикабельности). При этом upvar в качестве источника таких ошибок достаточно редок — перепутать имя переменной со значением в подкомандах dict или в списковых lset/linsert/lreplace/lindex гораздо легче и потенциально опаснее.

LeninGad
()
Ответ на: комментарий от scabarocci

Он для другого совсем :) Если я хочу локальную переменную отдать вызываемой процедуре, чтобы она над ней поработала, global в этом никак не поможет.

Частный случай upvar #0 — действительно похож на global, но умеет переименовывать переменные (что используется в стандартном пакете http, например: upvar #0 $token state).

Кстати, помимо global есть ещё variable, и как правило оно уместнее (т.е. если вы не принимаете решение, что всё изначально пишется в корневом namespace). Ещё с 8.5 есть namespace upvar, но он бывает нужен очень редко (в проектах наподобие snit).

LeninGad
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.