Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Man, I love [switch]!

5 views
Skip to first unread message

John Seal

unread,
Jan 19, 2007, 11:11:06 AM1/19/07
to
I was coding some complex logic involving four boolean values:

-whether a file matched a pattern or not
-whether the file was explicitly included
-whether it was explicitly excluded
-the state of the file

I made a truth table, and knew what I wanted to do in each specific
case, many of which were errors. After driving myself crazy with nested
if's and complex logical expr's (and almost resorting to a Karnaugh
map!) I finally went back to old faithful [switch]:

set code $match$state$include$exclude
switch -- $code {
0010 {lremove ::Include $path ;# uninclude}
0100 {lappend ::Include $path ;# include}
1000 {lappend ::Exclude $path ;# exclude}
1101 {lremove ::Exclude $path ;# unexclude}
0-00 {set state 0 ;# no match, not included}
0-10 {set state 1 ;# no match, but included}
1-00 {set state 1 ;# match, not excluded}
1-01 {set state 0 ;# match, but excluded}
default {puts "error $code for $path"}
}

Note that this even handles the weirdness that one of the booleans is
really trinary, with "-" meaning "use the current state".

Being able to write code like that is just one reason why I love Tcl.

Gerald W. Lester

unread,
Jan 19, 2007, 11:41:18 AM1/19/07
to

Note, you could have been slightly more concise if you wanted:
switch -glob -- $code {


0010 {lremove ::Include $path ;# uninclude}
0100 {lappend ::Include $path ;# include}
1000 {lappend ::Exclude $path ;# exclude}
1101 {lremove ::Exclude $path ;# unexclude}

?-0? {set state 0}
?-?0 {set state 1}


default {puts "error $code for $path"}
}

I'd also put out a more detailed error message for the ??11 state.

--
+--------------------------------+---------------------------------------+
| Gerald W. Lester |
|"The man who fights for his ideals is the man who is alive." - Cervantes|
+------------------------------------------------------------------------+

Sektor van Skijlen

unread,
Jan 19, 2007, 12:13:58 PM1/19/07
to
Dnia Fri, 19 Jan 2007 11:11:06 -0500, John Seal skrobie:

That's why the scripting languages can be split into two groups:

"Tcl" and "the others". :)

--
// _ ___ Michal "Sektor" Malecki <sektor(whirl)kis.p.lodz.pl>
\\ L_ |/ `| /^\ ,() <ethouris(O)gmail.com>
// \_ |\ \/ \_/ /\ C++ bez cholesterolu: http://www.intercon.pl/~sektor/cbx
"Java is answer for a question that has never been stated"

Fredderic

unread,
Jan 20, 2007, 1:02:01 PM1/20/07
to
On Fri, 19 Jan 2007 10:41:18 -0600,
"Gerald W. Lester" <Gerald...@cox.net> wrote:

> John Seal wrote:
> > I made a truth table, and knew what I wanted to do in each specific
> > case, many of which were errors. After driving myself crazy with
> > nested if's and complex logical expr's (and almost resorting to a
> > Karnaugh map!) I finally went back to old faithful [switch]:
> >
> > set code $match$state$include$exclude
> > switch -- $code {
> > 0010 {lremove ::Include $path ;# uninclude}
> > 0100 {lappend ::Include $path ;# include}
> > 1000 {lappend ::Exclude $path ;# exclude}
> > 1101 {lremove ::Exclude $path ;# unexclude}
> > 0-00 {set state 0 ;# no match, not included}
> > 0-10 {set state 1 ;# no match, but included}
> > 1-00 {set state 1 ;# match, not excluded}
> > 1-01 {set state 0 ;# match, but excluded}
> > default {puts "error $code for $path"}
> > }
> >
> > Note that this even handles the weirdness that one of the booleans
> > is really trinary, with "-" meaning "use the current state".

> Note, you could have been slightly more concise if you wanted:
> switch -glob -- $code {
> 0010 {lremove ::Include $path ;# uninclude}
> 0100 {lappend ::Include $path ;# include}
> 1000 {lappend ::Exclude $path ;# exclude}
> 1101 {lremove ::Exclude $path ;# unexclude}
> ?-0? {set state 0}
> ?-?0 {set state 1}
> default {puts "error $code for $path"}
> }
> I'd also put out a more detailed error message for the ??11 state.

Actually, your response returns state 0 for input 1-00, which isn't
correct. There's also eight possible states in the ?-?? domain, and
the OP mentioned there's a lot of error states, so you can't very well
go making assumptions about the OPs intent with regard to those states
that weren't specified.

set code $match$state$include$exclude
switch -- $code {
0010 {lremove ::Include $path ;# uninclude}
0100 {lappend ::Include $path ;# include}
1000 {lappend ::Exclude $path ;# exclude}
1101 {lremove ::Exclude $path ;# unexclude}

0-00 - 1-01 {set state 0 ;# not included}
0-10 - 1-00 {set state 1 ;# not excluded}


default {puts "error $code for $path"}
}

should work, though...


Fredderic

Bruce Hartweg

unread,
Jan 20, 2007, 2:22:27 PM1/20/07
to


Actually 1-00 will match the ?-0? pattern which sets it to zero
switch evaluates patterns in order so this will work. It isn't as
readable though, I would pick the more explicit case myself for
that reason alone.

There's also eight possible states in the ?-?? domain, and
> the OP mentioned there's a lot of error states, so you can't very well
> go making assumptions about the OPs intent with regard to those states
> that weren't specified.
>
> set code $match$state$include$exclude
> switch -- $code {
> 0010 {lremove ::Include $path ;# uninclude}
> 0100 {lappend ::Include $path ;# include}
> 1000 {lappend ::Exclude $path ;# exclude}
> 1101 {lremove ::Exclude $path ;# unexclude}
> 0-00 - 1-01 {set state 0 ;# not included}
> 0-10 - 1-00 {set state 1 ;# not excluded}
> default {puts "error $code for $path"}
> }
>
> should work, though...

yep, reduces the duplicate bodies, but still explicitly shows all states
(and handles completely bogus input with an error vs silent corruption)

Bruce

Neil Madden

unread,
Jan 21, 2007, 4:26:05 AM1/21/07
to
John Seal wrote:
...

>
> set code $match$state$include$exclude
> switch -- $code {
> 0010 {lremove ::Include $path ;# uninclude}
> 0100 {lappend ::Include $path ;# include}
> 1000 {lappend ::Exclude $path ;# exclude}
> 1101 {lremove ::Exclude $path ;# unexclude}
> 0-00 {set state 0 ;# no match, not included}
> 0-10 {set state 1 ;# no match, but included}
> 1-00 {set state 1 ;# match, not excluded}
> 1-01 {set state 0 ;# match, but excluded}
> default {puts "error $code for $path"}
> }
>
> Note that this even handles the weirdness that one of the booleans is
> really trinary, with "-" meaning "use the current state".
>
> Being able to write code like that is just one reason why I love Tcl.

That is nice. I like languages which support some form of
pattern-matching. Tcl isn't unique here, though. Prolog supports
pattern-matching well, as do various typed functional programming
languages, e.g. Haskell:

case (match,state,include,exclude) of
(0,0,1,0) => ...
(0,1,0,0) => ...
(1,0,0,0) => ...
(1,1,0,1) => ...
etc

Those languages can also bind variables in the patterns, and match
against complex nested data structures. I wish more languages supported
this as it really makes code short and easy to read (the Visitor pattern
is a poor substitute, especially in cases like the above).

-- Neil

mark anthony

unread,
Jan 23, 2007, 6:16:03 AM1/23/07
to
John Seal wrote:
> set code $match$state$include$exclude
> switch -- $code {
> 0010 {lremove ::Include $path ;# uninclude}
> 0100 {lappend ::Include $path ;# include}
> 1000 {lappend ::Exclude $path ;# exclude}
> 1101 {lremove ::Exclude $path ;# unexclude}
> 0-00 {set state 0 ;# no match, not included}
> 0-10 {set state 1 ;# no match, but included}
> 1-00 {set state 1 ;# match, not excluded}
> 1-01 {set state 0 ;# match, but excluded}
> default {puts "error $code for $path"}
> }

an alternative would be to create a state namespace
and create procs that match your case.

namespace eval ::projectNamespace::state {
variable state
set state default


proc apply { code path } {

if {[catch { $code $path } ]} then {
return -code error "error $code for $path"
}
return
}

proc getState { } {
variable state
return $state
}
proc setState { thisState } {
variable state
set state $thisState
}

proc case { name code } { proc $name { path } $code }

# uninclude
case 0010 {lremove ::Include $path }
# include
case 0100 {lappend ::Include $path }
# exclude
case 1000 {lappend ::Exclude $path}
# unexclude
case 1101 {lremove ::Exclude $path }

# no match, not included
case 0-00 {setState 0 }


# no match, but included

case 0-10 {setState 1}
# match, not excluded
case 1-00 {setState 1}
# match, but excluded
case 1-01 {setState 0}
}

depends on which flavour you prefer (you might argue that this
is to much code, yet there might be more code for this module).


or in other words "Man, i love [tcl]!"

John Seal

unread,
Jan 23, 2007, 3:32:45 PM1/23/07
to
Gerald W. Lester wrote:
> Note, you could have been slightly more concise if you wanted:
> switch -glob -- $code {
[snip]

> I'd also put out a more detailed error message for the ??11 state.

Gerald:

In fact, I originally used a [switch -glob] much like that. Then I had
separate error messages for each error state. Then I decided I only
needed to know which error occurred, and settled on the code I posted.
Then I decided the ??11 state was egregious enough to warrant a separate
error message, but that got me thinking and I came up with a radically
simpler approach and scrapped the whole shebang.

But I still love [switch]!

John Seal

unread,
Jan 23, 2007, 3:40:17 PM1/23/07
to
Neil Madden wrote:
> That is nice. I like languages which support some form of
> pattern-matching. Tcl isn't unique here, though. Prolog supports
> pattern-matching well, as do various typed functional programming
> languages, e.g. Haskell:

Neil:

Thanks for the Haskell example; now I at least know *something* about
that language. I learned Prolog once upon a time... it was the hardest
language I've ever learned (even harder than Smalltalk and Lisp) simply
because the concepts are so radically different. But once you have those
concepts, there are things that are simple in Prolog that would be very
hard in other (procedural or functional) languages.

0 new messages