TCL multiple assignment (as in Perl or Ruby) - variables

In Ruby or Perl one can assign more than variable by using parentheses. For example (in Ruby):
(i,j) = [1,2]
(k,m) = foo() #foo returns a two element array
Can one accomplish the same in TCL, in elegant way? I mean I know that you can
do:
foreach varname { i j } val { 1 2 } { set $varname $val }
foreach varname { k m } val [ foo ] { set $varname $val }
But I was hoping for something shorter/ with less braces.

Since Tcl 8.5, you can do
lassign {1 2} i j
lassign [foo] k m
Note the somewhat unintuitive left-to-right order of value sources -> variables. It's not a unique design choice: e.g. scan and regexp use the same convention. I'm one of those who find it a little less readable, but once one has gotten used to it it's not really a problem.
If one really needs a Ruby-like syntax, it can easily be arranged:
proc mset {vars vals} {
uplevel 1 [list lassign $vals {*}$vars]
}
mset {i j} {1 2}
mset {k m} [foo]
Before Tcl 8.5 you can use
foreach { i j } { 1 2 } break
foreach { k m } [ foo ] break
which at least has fewer braces than in your example.
Documentation: break, foreach, lassign, list, proc, uplevel

Related

wants to set a variable with another variable in it using Tcl

i want to execute this code but it is not waorking , the tcl script is as follows:
set i 0
foreach pattern { tiger cat horse dog} {
set pat$i abc
puts "pat$i=${pat$i}"
set i [expr {$i + 1}]
}
the desired result i want in every loop is :
pat0=abc
pat1=abc
pat2=abc
pat3=abc
Please help me to find my mistake
You can read a variable with a computed name with the one-argument form of set:
set i 0
foreach pattern { tiger cat horse dog} {
set pat$i abc
puts "pat$i=[set pat$i]"
set i [expr {$i + 1}]
}
The $... syntax can be considered shorthand for that (except with different, more limited parsing rules that make it more convenient to write).
Alternatively, you can use upvar 0 to make a variable alias with a simple name:
set i 0
foreach pattern { tiger cat horse dog} {
upvar 0 pat$i p
set p abc
puts "pat$i=$p"
set i [expr {$i + 1}]
}
That is much more favoured in a procedure.
HOWEVER, are you sure you need the variable to be called that? Can you use an associative array instead? That's often just as convenient.
set i 0
foreach pattern { tiger cat horse dog} {
set pat($i) abc
puts "pat$i=$pat($i)"
incr i; # The incr command is *right there*
}

Support Whatever ranges in multidimensional subscript access with AT-POS

How can I implement AT-POS such that it supports multidimensional Whatever ranges, like [0;*] and [*;0]?
In the implementation below, I get Index out of range errors:
class Foo {
has #.grid;
multi method elems { #!grid.elems }
multi method AT-POS($y, $x) is rw { #!grid[ $y ; $x ] }
multi method ASSIGN-POS ($y, $x, $new) { #!grid[ $y; $x ] = $new }
multi method EXISTS-POS($y, $x) { #!grid[ $y; $x ]:exists }
}
my $foo = Foo.new: :grid[ ['a'], ['b', 'c'] ];
say $foo[0;0]; # a
say $foo[0;*].elems; # Expect 1, get 2
say $foo[0;*]; # Expect (a), get "Index out of range"
say $foo[*;0]; # Expect (a b), get "Index out of range"
The doc says the API is AT-POS($index).
When I replace your AT-POS with:
multi method AT-POS($index) is rw { #!grid[ $index ] }
your test cases give the results you expect.
Your ASSIGN-POS is unnecessary and may make things go wrong.

Concatenating lists in Raku

I'm looking for a simpler solution.
I have a list of prefixes with corresponding suffixes and a list of roots.
my #prefixes = 'A'..'E';
my #suffixes = 'a'..'e';
my #roots = 1, 2;
I would like to make all the possible 'words': A1a, B1b...A2a...E2e.
my #words;
for #roots -> $r {
for #prefixes.kv -> $i, $p {
my $s = #suffixes[$i];
my $word = [~] $p, $r, $s;
#words.push: $word;
}
}
say #words; # [A1a B1b C1c D1d E1e A2a B2b C2c D2d E2e]
I suppose that it is possible to do it much easier using something like zip or cross, but can't figure out how...
My solution would be:
say #roots.map: |(#prefixes >>~>> * <<~<< #postfixes);
Create a WhateverCode for metaopping concatenation, slipping the result to get a Seq with only scalar values at the end.
A few more ways to write it:
say #roots X[&join] (#prefixes Z #suffixes);
say #roots.map({ |(#prefixes Z #suffixes)».join($_) });
say #roots.map({ (#prefixes X~ $_) Z~ #suffixes }).flat;
say (|#prefixes xx *) Z~ (#roots X~ #suffixes);
my #formats = (#prefixes Z #suffixes).flat.map(* ~ '%s' ~ *);
say #formats X[&sprintf] #roots;
(Note: This one prints them in a different order.)
say do for #roots -> $root {
|do for (#prefixes Z #suffixes) -> [$prefix, $suffix] {
$prefix ~ $root ~ $suffix
}
}

Why does a Perl 6 Str do the Positional role, and how can I change []?

I'm playing around with a positional interface for strings. I'm aware of How can I slice a string like Python does in Perl 6?, but I was curious if I could make this thing work just for giggles.
I came up with this example. Reading positions is fine, but I don't know how to set up the multi to handle an assignment:
multi postcircumfix:<[ ]> ( Str:D $s, Int:D $n --> Str ) {
$s.substr: $n, 1
}
multi postcircumfix:<[ ]> ( Str:D $s, Range:D $r --> Str ) {
$s.substr: $r.min, $r.max - $r.min + 1
}
multi postcircumfix:<[ ]> ( Str:D $s, List:D $i --> List ) {
map( { $s.substr: $_, 1 }, #$i ).list
}
multi postcircumfix:<[ ]> ( Str:D $s, Int:D $n, *#a --> Str ) is rw {
put "Calling rw version";
}
my $string = 'The quick, purple butterfly';
{ # Works
my $single = $string[0];
say $single;
}
{ # Works
my $substring = $string[5..9];
say $substring;
}
{ # Works
my $substring = $string[1,3,5,7];
say $substring;
}
{ # NOPE!
$string[2] = 'Perl';
say $string;
}
The last one doesn't work:
T
uick,
(h u c)
Index out of range. Is: 2, should be in 0..0
in block <unit> at substring.p6 line 36
Actually thrown at:
in block <unit> at substring.p6 line 36
I didn't think it would work, though. I don't know what signature or traits it should have to do what I want to do.
Why does the [] operator work on a Str?
$ perl6
> "some string"[0]
some string
The docs mostly imply that the [] works on things that do the Positional roles and that those things are in list like things. From the [] docs in operators:
Universal interface for positional access to zero or more elements of a #container, a.k.a. "array indexing operator".
But a Str surprisingly does the necessary role even though it's not an #container (as far as I know):
> "some string".does( 'Positional' )
True
Is there a way to test that something is an #container?
Is there a way to get something to list all of its roles?
Now, knowing that a string can respond to the [], how can I figure out what signature will match that? I want to know the right signature to use to define my own version to write to this string through [].
One way to achieve this, is by augmenting the Str class, since you really only need to override the AT-POS method (which Str normally inherits from Any):
use MONKEY;
augment class Str {
method AT-POS($a) {
self.substr($a,1);
}
}
say "abcde"[3]; # d
say "abcde"[^3]; # (a b c)
More information can be found here: https://docs.raku.org/language/subscripts#Methods_to_implement_for_positional_subscripting
To make your rw version work correctly, you first need to make the Str which might get mutated also rw, and it needs to return something which in turn is also rw. For the specific case of strings, you could simply do:
multi postcircumfix:<[ ]> ( Str:D $s is rw, Int:D $i --> Str ) is rw {
return $s.substr-rw: $i, 1;
}
Quite often, you'll want an rw subroutine to return an instance of Proxy:
multi postcircumfix:<[ ]> ( Str:D $s is rw, Int:D $i --> Str ) is rw {
Proxy.new: FETCH => sub { $s.substr: $i },
STORE => sub -> $newval { $s.substr-rw( $i, 1 ) = $newval }
}
Although I haven't yet seen production code which uses it, there is also a return-rw operator, which you'll occasionally need instead of return.
sub identity( $x is rw ) is rw { return-rw $x }
identity( my $y ) = 42; # Works, $y is 42.
sub identity-fail( $x is rw ) is rw { return $x }
identity-fail( my $z ) = 42; # Fails: "Cannot assign to a readonly variable or a value"
If a function reaches the end without executing a return, return-rw or throwing an exception, the value of the last statement is returned, and (at present), this behaves as if it were preceded return-rw.
sub identity2( $x is rw ) is rw { $x }
identity2( my $w ) = 42; # Works, $w is 42.
There's a module that aims to let you do this:
https://github.com/zoffixznet/perl6-Pythonic-Str
However:
This module does not provide Str.AT-POS or make Str type do Positional or Iterable roles. The latter causes all sorts of fallout with core and non-core code due to inherent assumptions that Str type does not do those roles. What this means in plain English is you can only index your strings with [...] postcircumfix operator and can't willy-nilly treat them as lists of characters—simply call .comb if you need that.`

parse string with pairs of values into hash the Perl6 way

I have a string which looks like that:
width=13
height=15
name=Mirek
I want to turn it into hash (using Perl 6). Now I do it like that:
my $text = "width=13\nheight=15\nname=Mirek";
my #lines = split("\n", $text);
my %params;
for #lines {
(my $k, my $v) = split('=', $_);
%params{$k} = $v;
}
say %params.perl;
But I feel there should exist more concise, more idiomatic way to do that. Is there any?
In Perl, there's generally more than one way to do it, and as your problem involves parsing, one solution is, of course, regexes:
my $text = "width=13\nheight=15\nname=Mirek";
$text ~~ / [(\w+) \= (\N+)]+ %% \n+ /;
my %params = $0>>.Str Z=> $1>>.Str;
Another useful tool for data extraction is comb(), which yields the following one-liner:
my %params = $text.comb(/\w+\=\N+/)>>.split("=").flat;
You can also write your original approach that way:
my %params = $text.split("\n")>>.split("=").flat;
or even simpler:
my %params = $text.lines>>.split("=").flat;
In fact, I'd probably go with that one as long as your data format does not become any more complex.
If you have more complex data format, you can use grammar.
grammar Conf {
rule TOP { ^ <pair> + $ }
rule pair {<key> '=' <value>}
token key { \w+ }
token value { \N+ }
token ws { \s* }
}
class ConfAct {
method TOP ($/) { make (%).push: $/.<pair>».made}
method pair ($/) { make $/.<key>.made => $/.<value>.made }
method key ($/) { make $/.lc }
method value ($/) { make $/.trim }
}
my $text = " width=13\n\theight = 15 \n\n nAme=Mirek";
dd Conf.parse($text, actions => ConfAct.new).made;