Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unbind or undefine a variable in raku

Tags:

raku

rakudo

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?

like image 678
anquegi Avatar asked Oct 22 '20 15:10

anquegi


1 Answers

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.

like image 149
raiph Avatar answered Nov 23 '22 17:11

raiph