list ensemble

bll 2018-7-24: This was an exercise in learning about namespace ensemble.

This is an implementation (one possible anyways) of the list ensemble command.

It seems to work, but has not been tested extensively.

See the comment at the bottom for why a list ensemble that tries to replace using the name list will never work...

Seventh draft: This will work, but note that it is named: nlist. And since there's no need to try and be backwards compatible, the code can be reverted back to a namespace ensemble.

```#!/usr/bin/tclsh
#
# Originally written by Brad Lanam
# In the public domain
#

namespace eval ::nlist {
namespace export {[a-z]*}
# size is just added for orthogonality purposes
namespace ensemble create \
-map {
create  ::list
size    ::llength
append  ::lappend
assign  ::lassign
index   ::lindex
insert  ::linsert
length  ::llength
map     ::lmap
range   ::lrange
repeat  ::lrepeat
replace ::lreplace
reverse ::lreverse
search  ::lsearch
set     ::lset
sort    ::lsort
}
}

package provide listensemble 0.7

# test code
if { 1 } {
proc orig { } {
set l [list a b c d e f]
set li [lindex \$l 2]
set l [linsert \$l 3 C]
set lr [lrange \$l 2 3]
set lp [lrepeat 2 x]
set l [lreplace \$l 4 4 \$lp]
set l [lreverse \$l]
lset l 5 c2
set ls [lsearch -inline -all -nocase -glob \$l {C*} ]
set l [lsort -nocase \$l]
lassign \$l a1 a2 rest
# I have no idea how to use lmap
set nl [lmap a \$l {set a \${a}X}]
set ll [llength \$l]
set n [list]
puts "\$l / \$li / \$lr / \$ls / \$ll / \$a1 / \$a2 / \$rest / \$nl"
}

proc test { } {
set l [nlist create a b c d e f]
set li [nlist index \$l 2]
set l [nlist insert \$l 3 C]
set lr [nlist range \$l 2 3]
set lp [nlist repeat 2 x]
set l [nlist replace \$l 4 4 \$lp]
set l [nlist reverse \$l]
nlist set l 5 c2
set ls [nlist search -inline -all -nocase -glob \$l {C*} ]
set l [nlist sort -nocase \$l]
nlist assign \$l a1 a2 rest
# I have no idea how to use lmap
set nl [nlist map a \$l {set a \${a}X}]
set ll [nlist length \$l]
set n [nlist create]
puts "\$l / \$li / \$lr / \$ls / \$ll / \$a1 / \$a2 / \$rest / \$nl"
}

orig
test
}```

Discussion

(Can't nest discussion blocks, so this is a lot of code interspersed).

First pass

```#!/usr/bin/tclsh
#
# Originally written by Brad Lanam
# In the public domain
#

rename ::list ::_list

namespace eval ::list {
::lappend cmdlist repeat replace reverse search set sort
::lappend cmdlist create size
namespace export {*}\$cmdlist
namespace ensemble create \
-unknown ::list::_unknown \
-subcommands \$cmdlist
# I also tried -map {size length}, but that did not work out.
# I suppose there must be a way to get -map to work, but I could not find it.

variable _cflag false
variable _csave {}

proc _unknown { args } {
variable _cflag
variable _csave

# Unfortunately, the unknown handler is very specific to
# a namespace ensemble and is not a general handler.
# argument index 1 is stripped out and lost.
# Have to jump through some hoops here.
# There's a lot of weirdness here
# " If the -unknown handler returns anything else,
#   it is interpreted as a command prefix"
# does not seem to be true.  'return create' does not work,
# and in fact, 'return create' fails to pass the appended argument list,
# the create routine gets called, and errors out with a bad
# subcommand error.

# save the needed value for the create command
set _csave [::lindex \$args 1]
set _cflag true
return [::_list ::list create]
}

proc create { args } {
variable _cflag
variable _csave

if { \$_cflag } {
# more weirdness, cannot seem to set and use local variables, or
# even global variables.
# I tried:
#   set val [::_list \$_csave {*}\$args]
#   set _csave {}
#   return \$val
# and it did not work.
# Tried:
#   variable _tval
#   set _tval [::_list \$_csave {*}\$args]
#   set _csave {}
#   return \$_tval
# and it did not work.
set _cflag false
return [::_list \$_csave {*}\$args]
}
return [::_list {*}\$args]
}
# for orthogonality...
proc size { args } {
return [::llength {*}\$args]
}

proc append { args } {
return [uplevel 1 ::lappend {*}\$args]
}
proc assign { args } {
return [uplevel 1 ::lassign {*}\$args]
}
proc index { args } {
return [::lindex {*}\$args]
}
proc insert { args } {
return [::linsert {*}\$args]
}
proc length { args } {
return [::llength {*}\$args]
}
proc map { args } {
return [uplevel 1 ::lmap {*}\$args]
}
proc range { args } {
return [::lrange {*}\$args]
}
proc repeat { args } {
return [::lrepeat {*}\$args]
}
proc replace { args } {
return [::lreplace {*}\$args]
}
proc reverse { args } {
return [::lreverse {*}\$args]
}
proc search { args } {
return [::lsearch {*}\$args]
}
proc set { args } {
return [uplevel 1 ::lset {*}\$args]
}
proc sort { args } {
return [::lsort {*}{\$args}]
}
}

package provide listensemble 0.1```

2018-07-24: You can make use of ensemble create -map like this:

```namespace eval list {
namespace export {[a-z]*}
namespace ensemble create -map {
create  ::_list
size    ::llength
append  ::lappend
assign  ::lassign
index   ::lindex
insert  ::linsert
length  ::llength
map     ::lmap
range   ::lrange
repeat  ::lrepeat
replace ::lreplace
reverse ::lreverse
search  ::lsearch
set     ::lset
sort    ::lsort
each    ::foreach
}
}```

bll 2018-7-24: Thanks for that. That simplifies things. I think I was trying to -map on to routines internal to the namespace (in different ways), and it wasn't working.

This simplifies things. <strikeout>It appears namespace ensemble runs the -map'd commands in the correct frame.</strikeout>

I tried the 'each', but that doesn't work. Perhaps the syntax to use it eludes me.

join is also debatable, but I'm just going to leave this as the explicit list commands.

Not sure how to format this wiki page at this time, perhaps after all the discussion is done, the final form can be displayed up top, and roll the rest into a discussion block.

Second pass

```#!/usr/bin/tclsh
#
# Originally written by Brad Lanam
# In the public domain
#

rename ::list ::_list

namespace eval ::list {
namespace export {[a-z]*}
# size is just added for orthogonality purposes
namespace ensemble create \
-unknown ::list::_unknown \
-subcommands create \
-map {
size    ::llength
append  ::lappend
assign  ::lassign
index   ::lindex
insert  ::linsert
length  ::llength
map     ::lmap
range   ::lrange
repeat  ::lrepeat
replace ::lreplace
reverse ::lreverse
search  ::lsearch
set     ::lset
sort    ::lsort
}

variable _cflag false
variable _csave {}

proc _unknown { args } {
variable _cflag
variable _csave

# Unfortunately, the unknown handler is very specific to
# a namespace ensemble and is not a general handler.
# argument index 1 is stripped out and lost (though it
# is available initially.
# Have to jump through some hoops here.
#
# There's a lot of weirdness here...
# " If the -unknown handler returns anything else,
#   it is interpreted as a command prefix"
# does not seem to be true.  'return create' does not work,
# and in fact, 'return create' fails to pass the appended argument list,
# the create routine gets called, and errors out with a bad
# subcommand error.
#
# I suspect this is just a documentation error, where
# 'command prefix' should just read 'command'.

# save the needed value for the create command
set _csave [::lindex \$args 1]
set _cflag true
return [::_list ::list create]
}

proc create { args } {
variable _cflag
variable _csave
if { \$_cflag } {
# more weirdness, cannot seem to set and use local variables, or
# even global variables.
# I tried:
#   set val [::_list \$_csave {*}\$args]
#   set _csave {}
#   return \$val
# and it did not work.
# Tried:
#   variable _tval
#   set _tval [::_list \$_csave {*}\$args]
#   set _csave {}
#   return \$_tval
# and it did not work.
set _cflag false
return [::_list \$_csave {*}\$args]
}
return [::_list {*}\$args]
}
}

package provide listensemble 0.2```

bll 2018-7-24: I was wrong. Executed in the wrong frame. Now that I have tested properly...

Third pass

```#!/usr/bin/tclsh
#
# Originally written by Brad Lanam
# In the public domain
#

rename ::list ::_list

namespace eval ::list {
namespace export {[a-z]*}
# size is just added for orthogonality purposes
namespace ensemble create \
-unknown ::list::_unknown \
-subcommands {create append assign map set} \
-map {
size    ::llength
index   ::lindex
insert  ::linsert
length  ::llength
range   ::lrange
repeat  ::lrepeat
replace ::lreplace
reverse ::lreverse
search  ::lsearch
sort    ::lsort
}

variable _cflag false
variable _csave {}

proc _unknown { args } {
variable _cflag
variable _csave

# Unfortunately, the unknown handler is very specific to
# a namespace ensemble and is not a general handler.
# argument index 1 is stripped out and lost (though it
# is available initially.
# Have to jump through some hoops here.
#
# There's a lot of weirdness here...
# " If the -unknown handler returns anything else,
#   it is interpreted as a command prefix"
# does not seem to be true.  'return create' does not work,
# and in fact, 'return create' fails to pass the appended argument list,
# the create routine gets called, and errors out with a bad
# subcommand error.
#
# I suspect this is just a documentation error, where
# 'command prefix' should just read 'command'.

# save the needed value for the create command
set _csave [::lindex \$args 1]
set _cflag true
return [::_list ::list create]
}

proc create { args } {
variable _cflag
variable _csave
if { \$_cflag } {
# more weirdness, cannot seem to set and use local variables, or
# even global variables.
# I tried:
#   set val [::_list \$_csave {*}\$args]
#   set _csave {}
#   return \$val
# and it did not work.
# Tried:
#   variable _tval
#   set _tval [::_list \$_csave {*}\$args]
#   set _csave {}
#   return \$_tval
# and it did not work.
set _cflag false
return [::_list \$_csave {*}\$args]
}
return [::_list {*}\$args]
}

proc append { args } {
return [uplevel 1 ::lappend {*}\$args]
}
proc assign { args } {
return [uplevel 1 ::lassign {*}\$args]
}
proc map { args } {
return [uplevel 1 ::lmap {*}\$args]
}
proc set { args } {
return [uplevel 1 ::lset {*}\$args]
}
}

package provide listensemble 0.3```

You can have append, assign etc in the map, you don't need to put them in procedures. The each subcommand works just like foreach (the following is run off my initial suggestion):

```list append x a b c
a b c
set x
a b c
list each i \$x {list append y \$i}
set y
a b c```

bll 2018-7-24: My first test showed otherwise. Now I am confused -- no idea what went wrong the first time I tested. Could not get 'each' to work either. This is good though. assign was not working well at all.

dkf - 2018-07-25 07:19:07

The main reason we haven't done something like this already is that it will break a lot of code. Probably won't even be possible in Tcl 9 because of the level of incompatibility; plain list is used a lot, often with completely uncontrolled first arguments.

bll 2018-7-25 There is one problem left that I just found. A plain list command does not work. I don't know if it possible to work around that using namespace ensemble. It may be necessary to rewrite this without namespace ensemble to make it work.

With that caveat (a rather large caveat), this is completely backwards compatible and should be completely usable. It should not break any existing code.

bll 2018-7-25 Ok, new code. This should be completely backwards compatible. Would it be worth updating namespace ensemble to make it possible to call it without a sub-command?

Even though I think having a list ensemble would be nice, I'm not entirely sure if I would actually use this in my code. The alternative doesn't bother me that much. This was originally an exercise in using namespace ensemble, but since that does not work, the new code.

dkf - 2018-07-26 18:01:47

The big issue is that there is simply no way to make it safe for existing code, and list is pervasively used in situations where its handling of its first argument is critical. (I remember looking at this for 8.5 - or maybe 8.4 - when making ensembles for other commands.)

If you want fancier handling of missing subcommands, try a TclOO object. Those delegate to their unknown method handler even when no method name is provided... so you can unexport everything (especially destroy) and get complete control...

bll 2018-7-26: Thought I would try it in my code, just the package require, no other changes. Here's the major reason why it will never work (lol):

`namespace eval ::mpris [list set havedbus 1]`

 Enter Category Here