Variable declaration reset: Difference between revisions

From Rosetta Code
Content added Content deleted
(Add Red)
m (→‎{{header|Red}}: add some comments)
Line 301: Line 301:


=={{header|Red}}==
=={{header|Red}}==
* Blocks start at index 1 in Red.
* <code>all</code> short-circuits, so <code>prev</code> will be defined by the time <code>curr = prev</code> is checked.
<lang rebol>Red[]
<lang rebol>Red[]
s: [1 2 2 3 4 4 5]
s: [1 2 2 3 4 4 5]

Revision as of 08:52, 16 April 2022

Variable declaration reset is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

A decidely non-challenging task to highlight a potential difference between programming languages.

Using a straightforward longhand loop as in the JavaScript and Phix examples below, show the locations of elements which are identical to the immediately preceding element in {1,2,2,3,4,4,5}. The (non-blank) results may be 2,5 for zero-based or 3,6 if one-based.
The purpose is to determine whether variable declaration (in block scope) resets the contents on every iteration.
There is no particular judgement of right or wrong here, just a plain-speaking statement of subtle differences.
Should your first attempt bomb with "unassigned variable" exceptions, feel free to code it as (say)

 // int prev // crashes with unassigned variable
    int prev = -1 // predictably no output

If your programming language does not support block scope (eg assembly) it should be omitted from this task.

ALGOL 68

Algol 68 should produce no output as each iteration of the loop has a separate instance of the variables.
The Algol 68 equivalent of the Phix program is as below, whether this works as expected depends on how the implementation treats uninitialised variables... <lang algol68>BEGIN

   []INT s = ( 1, 2, 2, 3, 4, 4, 5 );
   FOR i TO ( UPB s -LWB s ) + 1 DO
       INT curr := s[ i ], prev;
       IF IF i > 1 THEN curr = prev ELSE FALSE FI THEN
           print( ( i, newline ) )
       FI;
       prev := curr
   OD

END</lang>

Output:

with Algol 68G

5             IF i > 1 AND curr = prev THEN
                                  1
a68g-2.8.3: runtime error: 1: attempt to use an uninitialised INT value (detected in VOID conditional-clause starting at "IF" in this line).

with Rutgers Algol 68:
No output.

Factor

Normally you would not use lexical scoping for something like this in Factor. But since it is possible, here we go. Note that:

  • Factor doesn't allow you to declare a lexical variable without initializing it.
  • Lexical variables are immutable by default. To make a lexical variable mutable, you need to add a ! to its declaration.


Works with: Factor version 0.99 2022-04-03

<lang factor>USING: kernel math prettyprint sequences ;

[let

   { 1 2 2 3 4 4 5 } :> s
   s length <iota> [| i |
       i s nth -1 :> ( curr prev! )    
       i 1 > curr prev = and
       [ i . ] when
       curr prev!
   ] each

]</lang>

Output:

[none]


To get output, we need to declare prev outside the each quotation.

Works with: Factor version 0.99 2022-04-03

<lang factor>USING: kernel math prettyprint sequences ;

[let

   { 1 2 2 3 4 4 5 } -1 :> ( s prev! )
   s length <iota> [| i |
       i s nth :> curr
       i 1 > curr prev = and
       [ i . ] when
       curr prev!
   ] each

]</lang>

Output:
2
5

Now compare to how you would normally solve this in Factor, where issues of variables and scope are irrelevant:

Works with: Factor version 0.99 2022-04-03

<lang factor>USING: grouping math.vectors prettyprint sequences.extras ;

{ 1 2 2 3 4 4 5 } 2 <clumps> [ all-eq? ] arg-where 1 v+n .</lang>

Go

Note firstly that unassigned variables are impossible in Go. If a variable is created (using the 'var' keyword) without giving it an explicit value, then it is assigned the default value for its type which in the case of numbers is zero. Fortunately, this doesn't clash with values in the slice in the following program. <lang go>package main

import "fmt"

func main() {

   s := []int{1, 2, 2, 3, 4, 4, 5}
   // There is no output as 'prev' is created anew each time
   // around the loop and set implicitly to zero.
   for i := 0; i < len(s); i++ {
       curr := s[i]
       var prev int
       if i > 0 && curr == prev {
           fmt.Println(i)
       }
       prev = curr
   }
   // Now 'prev' is created only once and reassigned
   // each time around the loop producing the desired output.
   var prev int
   for i := 0; i < len(s); i++ {
       curr := s[i]
       if i > 0 && curr == prev {
           fmt.Println(i)
       }
       prev = curr
   }

}</lang>

Output:
2
5

JavaScript

<lang javascript><!DOCTYPE html> <html lang="en" >

<head>
 <meta charset="utf-8"/>
 <meta name="viewport" content="width=device-width, initial-scale=1"/>
 <title>variable declaration reset</title>
</head>
<body>
 <script>

"use strict"; let s = [1, 2, 2, 3, 4, 4, 5]; for (let i=0; i<7; i+=1) {

   let curr = s[i], prev;
   if (i>0 && (curr===prev)) {
       console.log(i);
   }
   prev = curr;

}

 </script>
</body>

</html></lang> No output
Manually moving the declaration of prev to before the loop, or changing the third "let" to "var" (causes legacy hoisting and) gives:

Output:
2
5

Phix

with javascript_semantics
sequence s = {1,2,2,3,4,4,5}
for i=1 to length(s) do
    integer curr = s[i], prev
    if i>1 and curr=prev then
        ?i
    end if
    prev = curr
end for
Output:
3
6

Like the first/unchanged JavaScript example, under pwa/p2js there is no output (at least as things currently stand)
Obviously you can achieve consistent results by manually hoisting the declaration of prev to before/outside the loop.

PL/M

Works with: 8080 PL/M Compiler

... under CP/M (or an emulator)

Although PL/M has block scope, all variables are static, so PREV retains its value between iterations of the loop.
Note the extra DO which is necessary to introduce a new scope as declarations are not allowed in a DO loop. <lang pli>100H:

  /* CP/M BDOS SYSTEM CALL */
  BDOS: PROCEDURE( FN, ARG ); DECLARE FN BYTE, ARG ADDRESS; GOTO 5;END;
  /* CONSOLE OUTPUT ROUTINES */
  PR$CHAR:   PROCEDURE( C ); DECLARE C BYTE;    CALL BDOS( 2, C ); END;
  PR$STRING: PROCEDURE( S ); DECLARE S ADDRESS; CALL BDOS( 9, S ); END;
  PR$NL:     PROCEDURE; CALL PR$STRING( .( 0DH, 0AH, '$' ) );      END;
  PR$NUMBER: PROCEDURE( N );
     DECLARE N ADDRESS;
     DECLARE V ADDRESS, N$STR( 6 ) BYTE INITIAL( '.....$' ), W BYTE;
     N$STR( W := LAST( N$STR ) - 1 ) = '0' + ( ( V := N ) MOD 10 );
     DO WHILE( ( V := V / 10 ) > 0 );
        N$STR( W := W - 1 ) = '0' + ( V MOD 10 );
     END;
     CALL PR$STRING( .N$STR( W ) );
  END PR$NUMBER;
  /* TASK */
  DECLARE S( 6 ) BYTE INITIAL( 1, 2, 2, 3, 4, 4, 5 );
  DECLARE I BYTE;
  DO I = 0 TO LAST( S );
     DO;
        DECLARE ( CURR, PREV ) BYTE;
        CURR = S( I );
        IF I > 1 AND CURR = PREV THEN DO;
           CALL PR$NUMBER( I );
           CALL PR$NL;
        END;
        PREV = CURR;
     END;
  END;

EOF</lang>

Output:
2
5

Raku

By default, Raku variables need a prefix sigil indicating the storage / interface, and a scope declarator to indicate the variables' accessibility. The vast majority of the time, variables are declared with a "my" scope declarator that constrains them to the present block and any enclosed sub blocks. When a 'my' variable is declared inside a loop (block), a new independent instance of the variable is instantiated every time through.

<lang perl6>my @s = 1, 2, 2, 3, 4, 4, 5; loop (my $i = 0; $i < 7; $i += 1) {

   my $curr = @s[$i];
   my $prev;
   if $i > 1 and $curr == $prev {
       say $i;
   }
   $prev = $curr;

}</lang>

Yields:
Use of uninitialized value of type Any in numeric context
  in block <unit> at var.p6 line 5
Use of uninitialized value of type Any in numeric context
  in block <unit> at var.p6 line 5
Use of uninitialized value of type Any in numeric context
  in block <unit> at var.p6 line 5
Use of uninitialized value of type Any in numeric context
  in block <unit> at var.p6 line 5
Use of uninitialized value of type Any in numeric context
  in block <unit> at var.p6 line 5

Lots of warnings but nothing else. If we suppress the warnings:

<lang perl6>my @s = 1, 2, 2, 3, 4, 4, 5; quietly loop (my $i = 0; $i < 7; $i += 1) {

   my $curr = @s[$i];
   my $prev;
   if $i > 1 and $curr == $prev {
       say $i;
   }
   $prev = $curr;

}</lang>

No output.


We can however, declare the variable with an "our" scope, which effectively makes it a package global. Use of 'our' scoping is discouraged except in a few very specific situations. It "works" (for some value of works), but pollutes the namespace. The 'our' variable will trample any other instance of a variable with that name anywhere in the program in any other scope.

<lang perl6>my @s = 1, 2, 2, 3, 4, 4, 5; loop (my $i = 0; $i < 7; $i += 1) {

   my $curr = @s[$i];
   our $prev;
   if $i > 1 and $curr == $prev {
       say $i;
   }
   $prev = $curr;

}</lang>

Yields:
2
5

A better solution is to declare a state variable. A 'state' variable is essentially scoped similar to a 'my' variable (visible only inside the block), but is persistent across calls.

<lang perl6>my @s = 1, 2, 2, 3, 4, 4, 5; loop (my $i = 0; $i < 7; $i += 1) {

   my $curr = @s[$i];
   state $prev;
   if $i > 1 and $curr == $prev {
       say $i;
   }
   $prev = $curr;

}</lang>

Yields:
2
5

If you really want to run fast and loose, and bypass all the protections Raku builds in, you can turn off strict scoping with a pragma. This is heavily discouraged. Anyone trying to release code with strictures turned off will receive some degree of side-eye... but just because something is a bad idea in general doesn't mean Raku forbids it. The Raku mindset is "Make it difficult to make casual mistakes but make it possible to bypass those protections if you have the need."

No scope declarators at all. Every variable is a global. Bad idea. Do not do this casually. <lang perl6>no strict; @s = 1, 2, 2, 3, 4, 4, 5; loop ($i = 0; $i < 7; $i += 1) {

   $curr = @s[$i];
   if $i > 1 and $curr == $prev {
       say $i;
   }
   $prev = $curr;

}</lang>

Yields:
2
5

Red

  • Blocks start at index 1 in Red.
  • all short-circuits, so prev will be defined by the time curr = prev is checked.

<lang rebol>Red[] s: [1 2 2 3 4 4 5] repeat i length? s [

   curr: s/:i
   if all [i > 1 curr = prev][
       print i
   ]
   prev: curr

]</lang>

Output:
3
6

Visual Basic .NET

<lang vbnet>Option Strict On Option Explicit On

Imports System.IO

Module vMain

   Public Sub Main
       Dim s As Integer() = New Integer(){1, 2, 2, 3, 4, 4, 5}
       For i As Integer = 0 To Ubound(s)
           Dim curr As Integer = s(i)
           Dim prev As Integer
           If i > 1 AndAlso curr = prev Then
                 Console.Out.WriteLine(i)
           End If
           prev = curr
       Next i
   End Sub

End Module</lang>

Output:
2
5

Wren

Note firstly that unassigned variables are impossible in Wren. If a variable is created without giving it an explicit value, then it is assigned the special value 'null' which is the only instance of the Null class and therefore distinct from all other values in the language. <lang ecmascript>var s = [1, 2, 2, 3, 4, 4, 5]

// There is no output as 'prev' is created anew each time // around the loop and set implicitly to null. for (i in 0...s.count) {

   var curr = s[i]
   var prev
   if (i > 0 && curr == prev) System.print(i)
   prev = curr

}

// Now 'prev' is created only once and reassigned // each time around the loop producing the desired output. var prev for (i in 0...s.count) {

   var curr = s[i]
   if (i > 0 && curr == prev) System.print(i)
   prev = curr

}</lang>

Output:
2
5