Given the following code, it seems that I cannot iterate over a Buf if it had been assigned to a variable, unless I cast it to a list, even though it's not a lazy sequence. What gives?
my $file = open $path, bin => True;
$_.chr.say for $file.read: 8; # works well
my $test = $file.read: 8;
$_.chr.say for $test; # fails with "No such method 'chr' for invocant of type 'Buf[uint8]'"
$_.chr.say for $test.list; # works well
$test.is-lazy.say; # False
The reason it fails, is that:
my $test = $file.read: 8;
puts the Buf that is returned by $file.read into a Scalar variable, aka inside a container. And containerized is interpreted by for as itemized, to be considered a single item. So with:
.chr.say for $test;
you're calling the .chr method on the whole Buf, rather than on the individual elements.
There are a number of solutions to this:
make sure there's no container:
my $test := $file.read: 8;
This makes sure there is no container by binding the Buf.
make it look like an array
my #test := $file.read: 8;
Same as 1 basically, make the #test be an alias for the Buf. Note that this should also use binding, otherwise you'll get the same effect as you saw.
make it work like an Iterable
.chr.say for #$test;
By prefixing the # you're telling to iterate over it. This is basically syntactic sugar for the $test.list workaround you already found.
Re the $test.is-lazy.say, that is False for just about anything, e.g. 42.is-lazy.say; # False, so that doesn't tell you very much :-)
Related
After reading the Raku documentation, I only found this for undefining a variable. I believe that in Raku there are differences between assignment and binding.
Defining and undefining a scalar is easy.
> my $n
(Any)
> $n.defined
False
> $n = 3
3
> $n.defined
True
> $n = Nil
(Any)
> $n.defined
False
When the variable is binded, it's not possible.
> my $k := 5
5
> $k := Nil
===SORRY!=== Error while compiling:
Cannot use bind operator with this left-hand side
at line 2
------> <BOL>⏏<EOL>
> $k = Nil
Cannot assign to an immutable value
in block <unit> at <unknown file> line 1
For arrays or hashes, I can empty it, but the variable is still defined.
For functions, when you define a function with sub, you cannot undefine it, but you can with an anonymous function.
> my &pera = -> $n { $n + 2}
-> $n { #`(Block|140640519305672) ... }
> &pera = Nil
(Callable)
> &pera.defined
False
> my &pera = -> $n { $n + 2}
-> $n { #`(Block|140640519305672) ... }
> &pera = Nil
(Callable)
> &pera.defined
False
> sub foo ($n) { $n + 1}
&foo
> &foo.defined
True
> &foo = Nil
Cannot modify an immutable Sub (&foo)
in block <unit> at <unknown file> line 1
So what's the difference between assignment and binding?
How can I undefine a variable?
Lots of different issues to discuss here.
> my $k := 5;
> $k := Nil;
Cannot use bind operator
This first problem is the Raku REPL. cf your last SO. Have you tried CommaIDE or repl.it?
Your code is perfectly valid:
my $k := 5;
$k := Nil;
say $k; # Nil
Moving on:
my $k := 5;
$k = Nil;
Cannot assign to an immutable value
This is different. After binding 5 to $k, the $k = Nil code is attempting to assign a value into a number. Only containers[1] support assignment. A number isn't a container, so you can't assign into it.
Clarifying some cases you mentioned but didn't explicitly cover:
my #foo;
#foo := Nil;
Type check failed in binding; expected Positional...
While scalar variables (ones with a $ sigil) will bind to any value or container, an # sigil'd variable will only bind to a Positional container such as an Array. (Likewise a % to an Associative such as a Hash.)
Not only that, but these containers are always defined. So they still return True for .defined even if they're empty:
my #foo := Array.new; # An empty array
say #foo.elems; # 0 -- zero elements
say #foo.defined; # True -- despite array being empty
Now assigning Nil to an array:
my #foo;
#foo = Nil;
say #foo; # [(Any)]
If a declaration of an # sigil'd variable doesn't bind it to some explicit Positional type it is instead bound to the default choice for an # variable. Which is an Array with a default element value of Any.
The #foo = Nil; statement above assigns a Nil value into the first element of #foo. The assignment of a value into a non-existing element of a multi-element container means a new Scalar container pops into existence and is bound to that missing element before assignment continues. And then, because we're assigning a Nil, and because a Nil denotes an absence of a value, the Array's default value ((Any)) is copied into the Scalar instead of the Nil.
On to the sub case...
sub foo {}
&foo = {} # Cannot modify an immutable Sub (&foo)
&foo := {} # Cannot use bind operator ...
While a sub foo declaration generates an &foo identifier, it is deliberately neither assignable nor bindable. If you want a Callable variable, you must declare one using ordinary variable declaration.
Unbind or undefine a variable
You can't unbind variables in the sense of leaving them bound to nothing at all. (In other words, Raku avoids the billion dollar mistake.) In some cases you can rebind variables.
In some cases you can bind or assign an undefined value to a variable. If it's not a scalar variable then that's like the # variable example covered above. The scalar cases are considered next.
An example of the binding case:
my $foo := Any;
say $foo.defined; # False
say $foo; # (Any)
say $foo.VAR.WHAT; # (Any)
We'll see what the .VAR is about in a moment.
The assignment case:
my $foo = Any;
say $foo.defined; # False
say $foo.WHAT; # (Any)
say $foo.VAR.WHAT; # (Scalar)
It's important to understand that in this case the $foo is bound to a Scalar, which is a container, which is most definitely "defined", for some definition of "defined", despite appearances to the contrary in the say $foo.defined; line.
In the say $foo.WHAT; line the Scalar remains hidden. Instead we see an (Any). But the (Any) is the type of the value held inside the Scalar container bound to $foo.
In the next line we've begun to pierce the veil by calling .VAR.WHAT on $foo. The .VAR gets the Scalar to reveal itself, rather than yielding the value it contains. And so we see the type Scalar.
But if you call .defined on that Scalar it still insists on hiding!:
my $foo;
say $foo.VAR.defined; # False
say $foo.VAR.DEFINITE; # True
(The only way to force Raku to tell you the ultimate truth about its view of definiteness is to call the ultimate arbiter of definiteness, .DEFINITE.)
So what are the rules?
The compiler will let you assign or bind a given new value or container to a variable, if doing so is valid according to the original variable declaration.
Assigning or binding an undefined value follows the same rules.
Binding
All variables must be bound by the end of their declaration.
If a variable's declaration allows an undefined value to be bound/assigned to that variable, then the variable can be undefined in that sense. But in all other circumstances and senses variables themselves can never be "unbound" or "undefined".
Binding is about making a variable correspond to some container or value:
Variables with # and % sigils must be bound to a container (default Array and Hash respectively).
Variables with the $ sigil must be bound to either a container (default Scalar) or a value.
Variables with the & sigil must be bound to a Callable value or a Scalar constrained to contain a Callable value. (The & sigil'd variable that's visible as a consequence of declaring a sub does not allow rebinding or assignment.)
Assignment
Assignment means copying a value into a container.
If a variable is bound to a container, then you can assign into it, provided the assignment is allowed by the compiler according to the original variable declaration.
If a variable is not bound to a container, then the compiler will reject an assignment to it.
Scalar variables
If you use a scalar container as if it were a value, then you get the value that's held inside the container.
Binding a value (defined or undefined) to a scalar variable will mean it will stop acting as a container. If you then try to assign to that variable it won't work. You'd need to rebind it back to a container.
Footnotes
[1] In this answer I've used the word "container" to refer to any value that can serve as a container for containing other values. For example, instances of Array, Hash, or Scalar.
Hash with typed keys…
use v6;
class Foo {}
my Hash[Foo, Foo] $MAP;
my $f1 = Foo.new;
my $f2 = Foo.new;
$MAP{$f1} = $f2;
produces the error:
Invocant of method 'ASSIGN-KEY' must be an object instance of type 'Hash[Foo,Foo]', not a type object of type 'Hash[Foo,Foo]'. Did you forget a '.new'?
I find it misleading; what's the real error and what do I have to write instead?
I already tried the % sigil for the hash variable, that doesn't work, either.
In the way you have defined it, $MAP is actually a role. You need to instantiate (actually, pun) it:
class Foo {}
my Hash[Foo, Foo] $MAP;
my $map = $MAP.new;
my $f1 = Foo.new;
my $f2 = Foo.new;
$map{$f1} = $f2;
say $map;
Dead giveaway here was that classes can't be parametrized, roles do.
Also:
say $MAP.DEFINITE; # False
say $map.DEFINITE; # True
But actually the error message was pretty informative, up to and including the suggestion to use .new, as I do here.
We can shorten it down to:
class Foo {}
my %map = Hash[Foo, Foo].new ;
%map{Foo.new} = Foo.new;
%map.say;
By doing the punning from the definition, we don't need the $MAP intermediate class.
TL;DR JJ's answer is right, but the explanation left me confused. I currently view the problem you showed as an autovivification error/bug and/or LTA error message.
say my Any $Any; # (Any)
say my Hash $Hash; # (Hash)
say my Hash[Int] $Hash-Int; # (Hash[Int])
$Any<a> = 42; # OK
$Hash<a> = 42; # OK
$Hash-Int.new<a> = 42; # OK
$Hash-Int<a> = 42; # must be an object instance, not a type object
Imo this is a bug or pretty close to one.
A bug/problem applies for arrays too in the same scenario:
say my Any $Any; # (Any)
say my Array $Array; # (Array)
say my Array[Int] $Array-Int; # (Array[Int])
$Any[42] = 42; # OK
$Array[42] = 42; # OK
$Array-Int.new[42] = 42; # OK
$Array-Int[42] = 42; # Type check failed ... expected Array[Int] but got Array
If it's best considered notabug, then perhaps the error message should be changed. While I agree with JJ that the error message is actually on point (when you understand how raku works and figure out what's going on), I think it's nevertheless an LTA error message if we don't change raku(do) to dwim.
On the gripping hand, it's not obvious to me how one could best improve the error message. And now we have this SO. (cf my point about that in Is the ... error message LTA? in a recent answer I wrote.)
Another solution
I already tried the % sigil for the hash variable, that doesn't work, either.
JJ has provided a solution that initializes with a value with an explicit .new. But that drops the constraint from the variable. To retain it:
class Foo {}
constant FooFoo = Hash[Foo:D,Foo:D];
my %foo is FooFoo;
%foo{Foo.new} = Foo.new;
Ideally the constant wouldn't be needed, and perhaps one day it won't, but I think trait parsing is limited.
This code:
constant %what = { doesn't => 'change' };
%what = { will => "change" }
Should say something along the lines of "Cannot modify an immutable hash". However, it says:
Potential difficulties:
Useless use of hash composer on right side of hash assignment; did you mean := instead?
Positionals have pretty much the same problem, but the error is different. In this case it's about cannot modify an immutable, but an Str:
constant #what = <does not change>;
#what = <does change> # Cannot modify an immutable Str (does)
A Scalar works as expected. Is this a case of LTA error message, or is some container magic at work here that I'm missing?
This code:
constant %what = { doesn't => 'change' };
%what = { will => "change" }
Should say something along the lines of "Cannot modify an immutable hash".
Who says so? I mean this rhetorically, not rudely. I get why you think so but it's important to be careful with use of the word "should" because it implies some authority says so, eg the specification, or a design document, or someone's common sense, or whatever.
Per the current spec, and Rakudo implementation, what constant foo ... does is permanently (constantly) bind foo to some particular "value".
If that "value" is a container, then foo constantly refers to that container. (Yes, a container can be a "value", for some definition of "value" that is appropriate here.)
So your code above has changed the elements contained within that container, and that is, per spec, perfectly cromulent:
say %what; # {will => change}
In the meantime, the warning message legitimately mentions useless use of a hash constructor, plus it notes:
did you mean := instead?
If you try that:
constant %what = { doesn't => 'change' };
%what := { will => "change" }
You get:
Cannot use bind operator with this left-hand side
Because, as already established, %what is a compile time constant permanently bound to a hash created and initialized at compile time and that aspect -- the permanent binding of %what to that particular hash -- can't be changed during this program run.
Positionals have pretty much the same problem, but the error is different. In this case it's about cannot modify an immutable, but an Str:
constant #what = <does not change>;
#what = <does change> # Cannot modify an immutable Str (does)
That's a bit different. A constant declaration binds, regardless of whether you write = or :=. So the constant declarations are equivalent to:
constant %what := { doesn't => 'change' }
constant #what := <does not change>;
The first line binds %what to { doesn't => 'change' } which is a mutable Hash.
The second line binds #what to <does not change> which is an immutable List.
You could instead write:
constant #what = [<does not change>];
#what = <does change>;
say #what; # [does change]
A Scalar works as expected.
Not quite.
A scalar (lowercase, the generic term) does:
constant $scalar = 42;
$scalar = 99; # Cannot assign to an immutable value
Remembering that constant always binds, the above code parallels:
my $scalar := 42;
$scalar = 99; # Cannot assign to an immutable value
But a Scalar works the same as the other containers in this context:
constant $scalar = $ = 42;
$scalar = 99; # OK
(Don't write code like that unless you want to annoy people.)
Is this a case of LTA error message, or is some container magic at work?
That's a good question, and one I'm not going to try answer.
I do not succeed in getting returned a mouse event from getmouse. Are there errors in my code?
#!/usr/bin/env perl6
use v6;
use NCurses;
my $win = initscr;
raw();
keypad( $win, True );
my Array[int32] $old;
mousemask( ALL_MOUSE_EVENTS +| REPORT_MOUSE_POSITION, $old ) or die;
loop {
my $key = getch();
if $key == KEY_MOUSE {
my NCurses::MEVENT $event;
my $ok = getmouse( $event );
endwin;
say "ok: ", $ok.perl; # -1
say "event: ", $event.perl; # NCurses::MEVENT
exit;
}
}
NCurses
The usual idiom
If you have to write a type's name as part of a variable declaration you might as well write it as the variable's type constraint and use .= new rather than using either Marty's or your solution:
my NCurses::MEVENT $event .= new
Marty's solution:
my $event = NCurses::MEVENT.new
works because $event now contains what the getevent($event) call needs it to contain, namely a new NCurses::MEVENT object. But it passes up an easy opportunity to add type-checking.
Your solution:
my NCurses::MEVENT $event = NCurses::MEVENT.new
means the same as the usual idiom for this situation but isn't DRY.
What went wrong
The line of code that glues the Perl 6 getmouse call to the underlying native NCurses library starts:
sub getmouse(MEVENT) ...
This tells Perl 6 that getmouse needs one argument of type NCurses::MEVENT.
Marty's solution worked. They didn't tell Perl 6 what type of value $event should contain. But they put the right value in it anyway so the lack of type-checking didn't matter.
Your original solution enabled useful type-checking. It told Perl 6 to make sure that $event only ever contained an object of type NCurses::MEVENT. Unfortunately you didn't explicitly initialize the variable so it contained...
Hang on. What did it contain? Shouldn't Perl 6 have made sure that there was an NCurses::MEVENT object in $event anyway?
In fact it did! But rather than putting an ordinary new NCurses::MEVENT object in there Perl 6 put an old NCurses::MEVENT type object in there as a placeholder. Among other things, Type objects represent the notion of an uninitialized object. (A bit like "undef" in Perl 5.)
This mechanism normally works well for surfacing errors like forgetting to suitably initialize a variable. But not in this case. So what went wrong?
Back to the getmouse declaration. It should have been:
sub getmouse(MEVENT:D) ...
The :D "type smiley" would tell Perl 6 that the argument has to be defined, ie that an uninitialized NCurses::MEVENT isn't good enough. With this smiley you'd have gotten an appropriate error rather than silence.
A less-than-awesome silent failure is masking the fact that you're passing a type object into getmouse(). I only found it by substituting $event.perl with $event.x on line 18 as an information fishing expedition. Doing that produces;
user#Ubuntu-iMac:~$ ./getmouse.p6
ok: -1
Invocant requires an instance of type NCurses::MEVENT, but a type object was passed. Did you forget a .new?
in block at ./getmouse.p6 line 17
...which is just a little more informative.
I'm sure you get it now but, for the record, you typed the $event variable but didn't assign any value to it, so it gets the type object which according to the Perl6 class tutorial is an undefined, "empty instance" of the type.
By simply substituting my $event = NCurses::MEVENT.new; for my NCurses::MEVENT $event; on line 13, one gets;
user#Ubuntu-iMac:~$ ./getmouse.p6
ok: 0
event: NCurses::MEVENT.new(id => 0, x => 70, y => 26, z => 0, bstate => 128)
... and all is well with the world.
I've found the missing part:
#!/usr/bin/env perl6
use v6;
use NCurses;
my $win = initscr;
raw();
keypad( $win, True );
my Array[int32] $old;
mousemask( ALL_MOUSE_EVENTS +| REPORT_MOUSE_POSITION, $old ) or die;
loop {
my $key = getch();
if $key == KEY_MOUSE {
my NCurses::MEVENT $event = NCurses::MEVENT.new;
my $ok = getmouse( $event );
endwin;
say "ok: ", $ok.perl;
say "event: ", $event.perl;
exit;
}
}
I'm studying dynamic/static scope with deep/shallow binding and running code manually to see how these different scopes/bindings actually work. I read the theory and googled some example exercises and the ones I found are very simple (like this one which was very helpful with dynamic scoping) But I'm having trouble understanding how static scope works.
Here I post an exercise I did to check if I got the right solution:
considering the following program written in pseudocode:
int u = 42;
int v = 69;
int w = 17;
proc add( z:int )
u := v + u + z
proc bar( fun:proc )
int u := w;
fun(v)
proc foo( x:int, w:int )
int v := x;
bar(add)
main
foo(u,13)
print(u)
end;
What is printed to screen
a) using static scope? answer=180
b) using dynamic scope and deep binding? answer=69 (sum for u = 126 but it's foo's local v, right?)
c) using dynamic scope and shallow binding? answer=69 (sum for u = 101 but it's foo's local v, right?)
PS: I'm trying to practice doing some exercises like this if you know where I can find these types of problems (preferable with solutions) please give the link, thanks!
Your answer for lexical (static) scope is correct. Your answers for dynamic scope are wrong, but if I'm reading your explanations right, it's because you got confused between u and v, rather than because of any real misunderstanding about how deep and shallow binding work. (I'm assuming that your u/v confusion was just accidental, and not due to a strange confusion about values vs. references in the call to foo.)
a) using static scope? answer=180
Correct.
b) using dynamic scope and deep binding? answer=69 (sum for u = 126 but it's foo's local v, right?)
Your parenthetical explanation is right, but your answer is wrong: u is indeed set to 126, and foo indeed localizes v, but since main prints u, not v, the answer is 126.
c) using dynamic scope and shallow binding? answer=69 (sum for u = 101 but it's foo's local v, right?)
The sum for u is actually 97 (42+13+42), but since bar localizes u, the answer is 42. (Your parenthetical explanation is wrong for this one — you seem to have used the global variable w, which is 17, in interpreting the statement int u := w in the definition of bar; but that statement actually refers to foo's local variable w, its second parameter, which is 13. But that doesn't actually affect the answer. Your answer is wrong for this one only because main prints u, not v.)
For lexical scope, it's pretty easy to check your answers by translating the pseudo-code into a language with lexical scope. Likewise dynamic scope with shallow binding. (In fact, if you use Perl, you can test both ways almost at once, since it supports both; just use my for lexical scope, then do a find-and-replace to change it to local for dynamic scope. But even if you use, say, JavaScript for lexical scope and Bash for dynamic scope, it should be quick to test both.)
Dynamic scope with deep binding is much trickier, since few widely-deployed languages support it. If you use Perl, you can implement it manually by using a hash (an associative array) that maps from variable-names to scalar-refs, and passing this hash from function to function. Everywhere that the pseudocode declares a local variable, you save the existing scalar-reference in a Perl lexical variable, then put the new mapping in the hash; and at the end of the function, you restore the original scalar-reference. To support the binding, you create a wrapper function that creates a copy of the hash, and passes that to its wrapped function. Here is a dynamically-scoped, deeply-binding implementation of your program in Perl, using that approach:
#!/usr/bin/perl -w
use warnings;
use strict;
# Create a new scalar, initialize it to the specified value,
# and return a reference to it:
sub new_scalar($)
{ return \(shift); }
# Bind the specified procedure to the specified environment:
sub bind_proc(\%$)
{
my $V = { %{+shift} };
my $f = shift;
return sub { $f->($V, #_); };
}
my $V = {};
$V->{u} = new_scalar 42; # int u := 42
$V->{v} = new_scalar 69; # int v := 69
$V->{w} = new_scalar 17; # int w := 17
sub add(\%$)
{
my $V = shift;
my $z = $V->{z}; # save existing z
$V->{z} = new_scalar shift; # create & initialize new z
${$V->{u}} = ${$V->{v}} + ${$V->{u}} + ${$V->{z}};
$V->{z} = $z; # restore old z
}
sub bar(\%$)
{
my $V = shift;
my $fun = shift;
my $u = $V->{u}; # save existing u
$V->{u} = new_scalar ${$V->{w}}; # create & initialize new u
$fun->(${$V->{v}});
$V->{u} = $u; # restore old u
}
sub foo(\%$$)
{
my $V = shift;
my $x = $V->{x}; # save existing x
$V->{x} = new_scalar shift; # create & initialize new x
my $w = $V->{w}; # save existing w
$V->{w} = new_scalar shift; # create & initialize new w
my $v = $V->{v}; # save existing v
$V->{v} = new_scalar ${$V->{x}}; # create & initialize new v
bar %$V, bind_proc %$V, \&add;
$V->{v} = $v; # restore old v
$V->{w} = $w; # restore old w
$V->{x} = $x; # restore old x
}
foo %$V, ${$V->{u}}, 13;
print "${$V->{u}}\n";
__END__
and indeed it prints 126. It's obviously messy and error-prone, but it also really helps you understand what's going on, so for educational purposes I think it's worth it!
Simple and deep binding are Lisp interpreter viewpoints of the pseudocode. Scoping is just pointer arithmetic. Dynamic scope and static scope are the same if there are no free variables.
Static scope relies on a pointer to memory. Empty environments hold no symbol to value associations; denoted by word "End." Each time the interpreter reads an assignment, it makes space for association between a symbol and value.
The environment pointer is updated to point to the last association constructed.
env = End
env = [u,42] -> End
env = [v,69] -> [u,42] -> End
env = [w,17] -> [v,69] -> [u,42] -> End
Let me record this environment memory location as AAA. In my Lisp interpreter, when meeting a procedure, we take the environment pointer and put it our pocket.
env = [add,[closure,(lambda(z)(setq u (+ v u z)),*AAA*]]->[w,17]->[v,69]->[u,42]->End.
That's pretty much all there is until the procedure add is called. Interestingly, if add is never called, you just cost yourself a pointer.
Suppose the program calls add(8). OK, let's roll. The environment AAA is made current. Environment is ->[w,17]->[v,69]->[u,42]->End.
Procedure parameters of add are added to the front of the environment. The environment becomes [z,8]->[w,17]->[v,69]->[u,42]->End.
Now the procedure body of add is executed. Free variable v will have value 69. Free variable u will have value 42. z will have the value 8.
u := v + u + z
u will be assigned the value of 69 + 42 + 8 becomeing 119.
The environment will reflect this: [z,8]->[w,17]->[v,69]->[u,119]->End.
Assume procedure add has completed its task. Now the environment gets restored to its previous value.
env = [add,[closure,(lambda(z)(setq u (+ v u z)),*AAA*]]->[w,17]->[v,69]->[u,119]->End.
Notice how the procedure add has had a side effect of changing the value of free variable u. Awesome!
Regarding dynamic scoping: it just ensures closure leaves out dynamic symbols, thereby avoiding being captured and becoming dynamic.
Then put assignment to dynamic at top of code. If dynamic is same as parameter name, it gets masked by parameter value passed in.
Suppose I had a dynamic variable called z. When I called add(8), z would have been set to 8 regardless of what I wanted. That's probably why dynamic variables have longer names.
Rumour has it that dynamic variables are useful for things like backtracking, using let Lisp constructs.
Static binding, also known as lexical scope, refers to the scoping mechanism found in most modern languages.
In "lexical scope", the final value for u is neither 180 or 119, which are wrong answers.
The correct answer is u=101.
Please see standard Perl code below to understand why.
use strict;
use warnings;
my $u = 42;
my $v = 69;
my $w = 17;
sub add {
my $z = shift;
$u = $v + $u + $z;
}
sub bar {
my $fun = shift;
$u = $w;
$fun->($v);
}
sub foo {
my ($x, $w) = #_;
$v = $x;
bar( \&add );
}
foo($u,13);
print "u: $u\n";
Regarding shallow binding versus deep binding, both mechanisms date from the former LISP era.
Both mechanisms are meant to achieve dynamic binding (versus lexical scope binding) and therefore they produce identical results !
The differences between shallow binding and deep binding do not reside in semantics, which are identical, but in the implementation of dynamic binding.
With deep binding, variable bindings are set within a stack as "varname => varvalue" pairs.
The value of a given variable is retrieved from traversing the stack from top to bottom until a binding for the given variable is found.
Updating the variable consists in finding the binding in the stack and updating the associated value.
On entering a subroutine, a new binding for each actual parameter is pushed onto the stack, potentially hiding an older binding which is therefore no longer accessible wrt the retrieving mechanism described above (that stops at the 1st retrieved binding).
On leaving the subroutine, bindings for these parameters are simply popped from the binding stack, thus re-enabling access to the former bindings.
Please see the the code below for a Perl implementation of deep-binding dynamic scope.
use strict;
use warnings;
use utf8;
##
# Dynamic-scope deep-binding implementation
my #stack = ();
sub bindv {
my ($varname, $varval);
unshift #stack, [ $varname => $varval ]
while ($varname, $varval) = splice #_, 0, 2;
return $varval;
}
sub unbindv {
my $n = shift || 1;
shift #stack while $n-- > 0;
}
sub getv {
my $varname = shift;
for (my $i=0; $i < #stack; $i++) {
return $stack[$i][1]
if $varname eq $stack[$i][0];
}
return undef;
}
sub setv {
my ($varname, $varval) = #_;
for (my $i=0; $i < #stack; $i++) {
return $stack[$i][1] = $varval
if $varname eq $stack[$i][0];
}
return bindv($varname, $varval);
}
##
# EXERCICE
bindv( u => 42,
v => 69,
w => 17,
);
sub add {
bindv(z => shift);
setv(u => getv('v')
+ getv('u')
+ getv('z')
);
unbindv();
}
sub bar {
bindv(fun => shift);
setv(u => getv('w'));
getv('fun')->(getv('v'));
unbindv();
}
sub foo {
bindv(x => shift,
w => shift,
);
setv(v => getv('x'));
bar( \&add );
unbindv(2);
}
foo( getv('u'), 13);
print "u: ", getv('u'), "\n";
The result is u=97
Nevertheless, this constant traversal of the binding stack is costly : 0(n) complexity !
Shallow binding brings a wonderful O(1) enhanced performance over the previous implementation !
Shallow binding is improving the former mechanism by assigning each variable its own "cell", storing the value of the variable within the cell.
The value of a given variable is simply retrieved from the variable's
cell (using a hash table on variable names, we achieve a
0(1) complexity for accessing variable's values!)
Updating the variable's value is simply storing the value into the
variable's cell.
Creating a new binding (entering subs) works by pushing the old value
of the variable (a previous binding) onto the stack, and storing the
new local value in the value cell.
Eliminating a binding (leaving subs) works by popping the old value
off the stack into the variable's value cell.
Please see the the code below for a trivial Perl implementation of shallow-binding dynamic scope.
use strict;
use warnings;
our $u = 42;
our $v = 69;
our $w = 17;
our $z;
our $fun;
our $x;
sub add {
local $z = shift;
$u = $v + $u + $z;
}
sub bar {
local $fun = shift;
$u = $w;
$fun->($v);
}
sub foo {
local $x = shift;
local $w = shift;
$v = $x;
bar( \&add );
}
foo($u,13);
print "u: $u\n";
As you shall see, the result is still u=97
As a conclusion, remember two things :
shallow binding produces the same results as deep binding, but runs faster, since there is never a need to search for a binding.
The problem is not shallow binding versus deep binding versus
static binding BUT lexical scope versus dynamic scope (implemented either with deep or shallow binding).