lmap

lmap , a built-in Tcl command introduced in version 8.6, iterates through items in one or more lists and processes those items into a new list.

Documentation

official reference
TIP 405 , Add Collecting Loops, the 'lmap' and 'dict map' Commands

Synopsis

lmap names list ?names list ...? body

Description

lmap repeatedly takes enough items are from each list to store one item in each of the variables named in names, evaluates body as a script, appends the result to another list, and returns that list once all items in all list arguments have been used. If any list is used up before any others, the empty string is assigned as needed to the relevant variables.

When there are not enough values remaining in a list to satisfy the associated varlist, the remaining variables in varlist are filled in with the empty string.

If the result of any evaluation is break, no further iterations are performed and the list that has been accumulated to that point is returned. If the result of an evaluation is continue, it is ignored.

AMG: Does the 8.6 lmap catch return calls, or will a return cause the caller of lmap to return?

AMG: I just checked... the latter is the case.

lmap as a Filter

HaO 2013-11-25: To get only the elements which fulfill a certain condition, one may use:

lmap v $list {
    if {!<condition>} continue
    set v
}

For example to get all numbers:

% lmap v {1 2 3 a b c} {
    if {![string is entier $v]} continue
    set v
    }
1 2 3

PYK 2016-01-02: And to wrap it into a procedure for a little less typing:

proc lwhere {varname list condition} {
    uplevel [list lmap $varname $list \
        "if [list $condition] {set [list $varname]} continue"]
}

KPV 2017-09-20: another example of lmap, this time to find the length of the longest word in a list:

set words {now is the time for all good men to come to the aid of their country}
set how_long [::tcl::mathfunc::max {*}[lmap v $words {string length $v}]]
set longest_word [lindex [lsort -index 1 -integer [lmap v $words { list $v [string length $v] }]] end 0]

An Earlier Version of lmap in Tcl

Richard Suchenwirth 2005-04-02: lmap is a "collecting foreach" which returns a list of its results. In Jim it is built in, but it can be easily had in pure Tcl:

proc lmap {_var list body} {
    upvar 1 $_var var
    set res {}
    foreach var $list {lappend res [uplevel 1 $body]}
    set res
}

Several usage examples are at Multiplication tables. lmap is a compromise between Tcl and the classical functional programming map function, in that it takes a "quasi-lambda" which is split up into the _var name and the body arguments. However, this style is well-known from foreach, and somehow reads better:

lmap i {1 2 3 4 5} {expr {$i * $i}}

vs.

map [lambda i {expr {$i*$i}}] {1 2 3 4 5}

Discussion

Jim's lmap uses continue to skip the accumulation of the current iteration, so it works like map and filter at the same time. In Jim, lmap, like foreach, supports multiple lists in input, so you can do interesting things like this:

. lmap a {1 2 3} b {A B C} {list $a $b}
{1 A} {2 B} {3 C}

Multiple lists + accumulation + continue to skip makes it also somewhat similar to list comprehension (but simpler to use in the my (SS) opinion).


A cute variation is fmap (influenced by ApplyAll in Backus' FP; Joy has a comparable operator in cleave) which maps a list of functions on one argument:

proc fmap {functions x}  {lmap f $functions {$f $x}}

Then we can write a file reader like this:

proc << filename  {lindex [fmap {read close} [open $filename]] 0}

NEM: I like that one! There is an mmap function that I wrote with Monadic TOOT which is similar to lmap (look about 1/3 way down that page). Instead of using continue, it uses a maybe monad to decide which results to accumulate. (Actually, it's quite general, and any monad could be used).


iu2 2009-10-15: I like to eliminate the "helper variables":

proc lmap {list body} {
    upvar 1 0 var  ;# $0 will be available automatically!
    set res {}
    foreach var $list {lappend res [uplevel 1 $body]}
    set res
}

So we can write

lmap {1 2 3 4 5} {expr {$0 * $0}}

As I often replace the original list variable with the mapped list, instead of

set list [lmap $list {expr {$0 * $0}}]

I can do

lmap! list {expr $0 * $0}

where lmap! is

proc lmap! {listvar body} {
    upvar 1 $listvar res
    set res [lmap $res[set res {}] $body]
}

Finally, the current Tcl constructs are just fine for a one liner

set list2 {}
foreach x $list {
    lappend list2 [expr {$x * $x}]
}

An lmap implementation in Tcl that allows multiple varname--listval pairs (see also lmap forward compatibility):

package require Tcl 8.5
proc lmap args {
    set body [lindex $args end]
    set args [lrange $args 0 end-1]
    set n 0
    set pairs [list]
    foreach {varname listval} $args {
        upvar 1 $varname var$n
        lappend pairs var$n $listval
        incr n
    }
    set temp [list]
    foreach {*}$pairs {
        lappend temp [uplevel 1 $body]
    }
    set temp
}

The above command uses the {*} thingy, so it's still no good for Tcl 8.4 users: one more implementation is perhaps needed.

package require Tcl 8.4
proc lmap args {
    set body [lindex $args end]
    set args [lrange $args 0 end-1]
    set n 0
    set pairs [list]
    foreach {varname listval} $args {
        upvar 1 $varname var$n
        lappend pairs var$n $listval
        incr n
    }
    set temp [list]
    eval foreach $pairs [list {
        lappend temp [uplevel 1 $body]
    }]
    set temp
}

See Also

lcomp
list comprehensions!
lolcat
for when you need more than one result per iteration

Historical

The contents in this section do not apply to 8.6 and later versions of Tcl, but are retained here for the history.


DKF: Note that lmap imposes quite a cost:

% time {lmap i {1 2 3 4 5} {expr {$i*$i}}} 100000
24.6734621 microseconds per iteration
% time {set res {}; foreach i {1 2 3 4 5} {lappend res [expr {$i*$i}]};set res} 100000
7.2637871 microseconds per iteration
% time {apply {l {set res {}; foreach i $l {lappend res [expr {$i*$i}]};set res}} {1 2 3 4 5}} 100000
2.75048408 microseconds per iteration

So... lmap is 9 times slower than inlining it (the use of apply shows that the effect of compilation of foreach is a fair part of it, but that cuts both ways).

DKF: Note that there's a proposal to implement this (in a manner much like the Jim version), which will not incur the performance cost mentioned above. (The cost was largely due to the fact that using a procedure like that defeated efficient handling of variables, together with some overhead due to stack frame handling.)

PYK 2014-06-30: These days, lmap is significantly faster:

% time {lmap i {1 2 3 4 5} {expr {$i*$i}}} 100000
3.85421 microseconds per iteration
% time {set res {}; foreach i {1 2 3 4 5} {lappend res [expr {$i*$i}]};set res} 100000
5.27972 microseconds per iteration
% time {apply {l {set res {}; foreach i $l {lappend res [expr {$i*$i}]};set res}} {1 2 3 4 5}} 100000
1.90479 microseconds per iteration

Googie 2012-10-03: As this command is currently being implemented for 8.6 I feel a need to express my concern. When I first saw lmap being mentioned my first thought was "oh, the string map for lists, great!", which is a wrong interpretation given what actually the command does. I think that more people will catch themselves with the same impression. I really think that the functionality of this command is expressed much better by lapply name, wouldn't you agree? I know it's a little late for changing any plans for 8.6, but still - worth of consideration.

Same applies for proposed dict map -> dict apply.

DKF: We can't make everyone happy, and this page indicates that majority opinion is that it's a code-driven transformation.