顯示具有 Syntax 標籤的文章。 顯示所有文章
顯示具有 Syntax 標籤的文章。 顯示所有文章

2018-10-08

Tcl: {*}

{*} was new in Tcl 8.5, and resulted in the Endekalogue becoming the Dodekalogue.

{*} 可以說是 Tcl 語法上一個比較大的修改(也可以說是語法糖)。將 List 的每個 item 轉變為目前 command 的參數。這是 TIP 293  的提案。

下面是簡單的測試:
set command [list set a 5]
set value [eval {*}$command]
puts $value

我最常用的是在使用 exec 時:
set command [list ls -al]
exec >@stdout 2>@stderr {*}$command


大抵來說,是個很好的改進。

2018-06-07

try and finally

在 comp.lang.tcl 看到這個寫法,所以我寫了一個練習用的程式來學習這件事:
try 是 Tcl 8.6 內建命令,可以在 finally 釋放所使用的資源。

#!/usr/bin/env tclsh
package require http
package require tls

http::register https 443 [list ::tls::socket -ssl3 0 -ssl2 0 -tls1 1]

set tok {}
set url {https://duckduckgo.com/}

try {
    set tok [http::geturl $url -method GET -timeout 3000]
    puts "Status: [::http::ncode $tok]"
    puts "Status Text: [http::status $tok]"
    puts "Headers: [http::meta $tok]"
} on error {em} {
    puts "Error: $em"
} finally {
    # cleanup here
    if {[info exists tok]==1} {
        http::cleanup $tok
    }
}

(更新:拔掉網路線以後測試,我想需要檢查 tok 是否存在)

相關連結:
try ... finally ... (@ Tclers Wiki!)
TIP 329: Try/Catch/Finally syntax

2017-08-01

Swapping variables

資料來自於 Tcler's Wiki - Tcl Gems


使用 Tcl 8.5 內建命令 lassign:
lassign [list $b $a] a b

lassign 可以將一個 list unpack 然後設定其它的變數,利用這一點,就可以交換二個變數的值,很有趣的做法。

2017-06-13

apply

set twice {{f v} {apply $f [apply $f $v]}}
set f {{v} {return [expr $v + 5]}}

puts [apply $twice $f 10]

這只是在說明,你可以將匿名函數的內容設給一個變數,然後使用 apply 來呼叫。

2017-04-29

package vcompare

可以看 Wiki 的解釋。

Compares the two version numbers given by version1 and version2. Returns -1 if version1 is an earlier version than version2, 0 if they are equal, and 1 if version1 is later than version2.


實際上的用法可以看 tcllib 中 zipfile::decode module。

if {[package vcompare $tcl_patchLevel "8.6"] < 0} {
  # Only needed pre-8.6
  package require Trf                       ; # Wrapper to zlib
  package require zlibtcl                   ; # Zlib usage. No commands, access through Trf
  set ::zipfile::decode::native_zip_functs 0
} else {
  set ::zipfile::decode::native_zip_functs 1
}

也就是如果 >= 8.6,使用內建的 zlib,如果小於就使用 Trf 與 zlibtcl。

接下來我檢查了一下 zipfile::decode module,發現 zipfile::decode module 並沒有修改完全,::zipfile::decode::GetFile 中沒有使用 native_zip_functs 來選擇要使用的 command。下面是修正的 patch。

     deflate {
         go     $fd(fileloc)
         nbytes $fd(csize)
-        return [zip -mode decompress -nowrap 1 -- [getval]]
+        if {$::zipfile::decode::native_zip_functs} {
+          return [zlib inflate [getval]]
+        } else {
+          return [zip -mode decompress -nowrap 1 -- [getval]]
+        }
     }
     default {
         Error "Unable to handle file \"$src\" compressed with method \"$fd(cm)\"" \



我已經在 Tcl Library Source Repository 開了一個 Ticket 來記錄這件事。

2015-12-22

Converting Characters (format command)

資料來源:Tcler's wiki


使用 format command 來做到 chr() function 的效果。
interp alias {} chr {} format %c

測試:
set a admin[chr 48]

很有趣的寫法。

2012-09-23

proc body and args

考慮下面 Tcl 文件裡的示範:
proc printProc {procName} {
    set result [list proc $procName]
    set formals {}
    foreach var [info args $procName] {
        if {[info default $procName $var def]} {
            lappend formals [list $var $def]
        } else {
            # Still need the list-quoting because variable
            # names may properly contain spaces.
            lappend formals [list $var]
        }
    }
    puts [lappend result $formals [info body $procName]]
}

所以我們可以很容易的知道 proc 參數和函式文字內容的資訊。也就是說,我們可以使用 info command 來拿到某個函式的內容(也就是使用 info body $procname 來拿到),並且使用 eval 執行之。

下面是一些練習的嘗試:
proc hello {x} {
    puts $x
}

proc rand {x} {
    set num [expr rand()]
    return [expr $num * $x]
}

proc hello2 {procname x} {
    if {[string compare "hello" $procname]==0} {
        incr x
    }     
    eval [info body $procname]
}   

hello2 hello 3
hello2 rand 10

很神奇吧?雖然我不知道有什麼用處,但是可以直接拿別的 proc 的 function body 來執行,對我而言是很神奇的。

2012-01-08

Coroutines

在 8.6 引進新的 Non-Recursive Engine (NRE) 之後,帶進了新的功能 Coroutines。不過 Coroutine 對我而言是很陌生的東西,所以做一下初學筆記。

在看過網路上對於 Coroutine 的介紹以後,我想可以簡單的總結如下:

Croutine 是自 1960 年代就已經現身的多工實做技術,原理相當單純,在 Coroutine 的 "yield" 是屬於程式語言層面,透過特定技巧或機制,讓原本循序執行的陳述指令,得以做出交錯執行的結果,可以說是 Windows 3.X 「合作式多工」的基礎概念(十分類似)。

用簡單的一句話來說 Coroutine,就是可以暫時中斷,之後再繼續執行的程序。


下面是從 Tcl 8.6 manual page copy 出來的例子:

coroutine accumulator apply {{} {
    set x 0
    while 1 {
        # yield $x: 丟資料並且把主控權交給呼叫者
        incr x [yield $x]
    }
}}

for {set i 0} {$i < 10} {incr i} {
    puts "$i -> [accumulator $i]"
}

在 manual page 裡還有其它的例子幫助理解,希望我對 Coroutines 的理解是正確的。

2010-10-03

apply and procedure

你的程式語言可以這樣做嗎?  讀完以後,用 Tcl 實驗匿名函式能力以後的結果。


寫程式的時候,你注意到有二段 code 一模一樣,除了一個反覆呼叫一個叫 BoomBoom 的函數,另一個反覆呼叫一個喚作 PutInPot 的。

puts "get the lobster"
PutInPot "lobster"
PutInPot "water"

and

puts "get the chicken"
BoomBoom "chicken"
BoomBoom "coconut"

現在你需要一個辦法,使得你可以將一個函數用作另一個函數的參數。這是個重要的能力,因為你更容易將常用的程式碼收藏在一個函數內。而 Tcl 所具有的彈性讓我們可以這樣做:

proc Cook {arg1 arg2 func} {
   puts -nonewline "get the "
   puts $arg1
   
   $func $arg1
   $func $arg2
}

所以我們就可以這樣用:

Cook  "lobster" "water" "PutInPot"
Cook  "chicken" "coconut" "BoomBoom" 

這樣就可以將函數作為參數了。

假設你未定義 PutInPot 或 BoomBoom 這些函數。如果能直接將它寫進一行內,不是比在其他地方宣告它們更好嗎?

proc Cook {arg1 arg2 func} {
   puts -nonewline "get the "
   puts $arg1
   
   set result [info procs $func]

   if {[string length $result] == 0} {
    apply $func $arg1
    apply $func $arg2
   } else {
    $func $arg1
    $func $arg2
   }
}

所以:

Cook "chicken" "coconut" {{food} { 
    puts -nonewline "BoomBoom --- "
    puts $food}
}

這樣是不是很方便?我建立函數時,甚至不用考慮怎麼命名,直接拿起它們,丟到一個函數內就可以了。

apply - Apply an anonymous function
apply func ?arg1 arg2 ...?

其中的 func 定義了參數和 procedure 要執行的東西(procedure body),思考一下 Tcl 如何定義 procedure,就知道該怎麼做:

proc anonymous_function {food} {
    puts -nonewline "BoomBoom --- "
    puts $food}
}

所以

Cook "chicken" "coconut" {{food} {
    puts -nonewline "BoomBoom --- "
    puts $food
  }
}

2009-07-17

math function

在 Tcl 8.5 以前,Tcl 的數學運算都是透過 expr 這個 command 做到的,而在 8.5 則引進了 tcl::mathfunc,讓使用一些數學運算的 function 上可以更直覺,使用上也更簡單。

如果不想寫一堆很長的 name space path,可以這樣做:

namespace path {::tcl::mathop ::tcl::mathfunc}
所以我們就可以這樣做:

srand [clock seconds]
這樣不管是撰寫程式還是讀 code 的時候,看起來都比較具可讀性。


另外,8.5 同時也 export 出來一些運算子(tcl::mathop)。
puts [* [sqrt 49] [+ 1 2 3]]
也就是,看起來會是類似 lisp 的前序式運算法。

2009-07-16

Access global variables in proc

在 procedure 裡存取全域變數,有二種做法:

1. 使用 global 命令宣告要存取的全域變數
2. 在 Tcl 支援 name space 之後,可以使用 name space 的表達方式(::)來存取變數
set x 0

proc appendMe {number} {
global x
incr x $number
}

proc appendMe2 {number} {
incr ::x $number
}

appendMe 3
puts $x

上面就是這二種方式的說明。

2009-06-25

Try/Catch/Finally syntax

Tcl 原本就有錯誤處理的機制(使用 catch),而在 TIP #329  (in 8.6 b1)又加入了類似 Try/Catch/Finally 來增強原本的機制。
proc read_hex_file {fname} {
set f [open $fname "r"]
set data {}
try {
while { [gets $f line] >= 0 } {
append data [binary format H* $line]
}
} trap {POSIX} {} {
puts "POSIX-type error"
} on error {em} {
error "Could not process file '$fname': $em"
} finally {
close $f
}
}
也就是說,原本 catch 是執行如果出錯,那就依據取得的 error code,再來判斷怎麼處理;而 try/finally 則是如果發生錯誤,會依據 error code 而把程式的流程流向該處理這個錯誤的 error handler,因此整個 code 如果寫的好會看起來比較清晰。對於錯誤的處理策略看起來比較清晰。

2009-06-24

Tcl/TK 8.5 new syntax: {*} 與 Eval

Tcl/Tk 8.5 引進了新的語法 {*},可以用來動態的建立命令執行,可以讓程式看起來更為簡單(使用 eval 會看起來比較複雜)。

我們可以用 Eval 這個指令將參數串接成一個字串後,將字串視為一個 Tcl Script 丟給解譯器去執行。下面是使用 Eval 版:
eval button .b $stdargs -text \$mytext -bd $border

{*} 可以將可以將串列的各個值分開為不同的參數,下面是改寫過後的版本:
button .b {*}$stdargs -text $mytext -bd $border