First class environments

From Rosetta Code
Revision as of 03:28, 2 October 2013 by rosettacode>BenGoldberg (Added Perl Implementation)
Task
First class environments
You are encouraged to solve this task according to the task description, using any language you may know.

According to Wikipedia, "In computing, a first-class object ... is an entity that can be constructed at run-time, passed as a parameter, returned from a subroutine, or assigned into a variable".

Often this term is used in the context of "first class functions". In an analogous way, a programming language may support "first class environments".

The environment is minimally, the set of variables accessable to a statement being executed. Change the environments and the same statement could produce different results when executed.

Often an environment is captured in a closure, which encapsulates a function together with an environment. That environment, however, is not first-class, as it cannot be created, passed etc. independently from the function's code.

Therefore, a first class environment is a set of variable bindings which can be constructed at run-time, passed as a parameter, returned from a subroutine, or assigned into a variable. It is like a closure without code. A statement must be able to be executed within a stored first class environment and act according to the environment variable values stored within.

The task: Build a dozen environments, and a single piece of code to be run repeatedly in each of these envionments.

Each environment contains the bindings for two variables: A value in the Hailstone sequence, and a count which is incremented until the value drops to 1. The initial hailstone values are 1 through 12, and the count in each environment is zero.

When the code runs, it calculates the next hailstone step in the current environment (unless the value is already 1) and counts the steps. Then it prints the current value in a tabular form.

When all hailstone values dropped to 1, processing stops, and the total number of hailstone steps for each environment is printed.

BBC BASIC

Here the 'environment' consists of all the dynamic variables; the static integer variables (A%-Z%) are not affected. <lang bbcbasic> DIM @environ$(12)

     @% = 4 : REM Column width
     
     REM Initialise:
     FOR E% = 1 TO 12
       PROCsetenvironment(@environ$(E%))
       seq% = E%
       cnt% = 0
       @environ$(E%) = FNgetenvironment
     NEXT
     
     REM Run hailstone sequences:
     REPEAT
       T% = 0
       FOR E% = 1 TO 12
         PROCsetenvironment(@environ$(E%))
         PRINT seq% ;
         IF seq% <> 1 THEN
           T% += 1
           cnt% += 1
           IF seq% AND 1 seq% = 3 * seq% + 1 ELSE seq% DIV= 2
         ENDIF
         @environ$(E%) = FNgetenvironment
       NEXT
       PRINT
     UNTIL T% = 0
     
     REM Print counts:
     PRINT "Counts:"
     FOR E% = 1 TO 12
       PROCsetenvironment(@environ$(E%))
       PRINT cnt% ;
       @environ$(E%) = FNgetenvironment
     NEXT
     PRINT
     END
     
     DEF FNgetenvironment
     LOCAL e$ : e$ = STRING$(216, CHR$0)
     SYS "RtlMoveMemory", !^e$, ^@%+108, 216
     = e$
     
     DEF PROCsetenvironment(e$)
     IF LEN(e$) < 216 e$ = STRING$(216, CHR$0)
     SYS "RtlMoveMemory", ^@%+108, !^e$, 216
     ENDPROC</lang>

Output:

   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
   1   1   1   1   1   1   1   1   1   1   1   1
Counts:
   0   1   7   2   5   8  16   3  19   6  14   9

Bracmat

<lang bracmat>( (environment=(cnt=0) (seq=)) & :?environments & 13:?seq & whl

 ' ( !seq+-1:>0:?seq
   & new$environment:?env
   & !seq:?(env..seq)
   & !env !environments:?environments
   )

& out$(Before !environments) & whl

 ' ( !environments:? (=? (seq=>1) ?) ?
   & !environments:?envs
   &   whl
     ' ( !envs:(=?env) ?envs
       &   (   
             ' ( $env
                 ( 
                 =   
                   .     put$(!(its.seq) \t)
                       & !(its.seq):1
                     |   1+!(its.cnt):?(its.cnt)
                       & 1/2*!(its.seq):~/?(its.seq)
                     | 3*!(its.seq)+1:?(its.seq)
                 )
               )
           . 
           )
         $ 
       )
   & out$
   )

& out$(After !environments) )</lang> Output:

  Before
  (=(cnt=0) (seq=1))
  (=(cnt=0) (seq=2))
  (=(cnt=0) (seq=3))
  (=(cnt=0) (seq=4))
  (=(cnt=0) (seq=5))
  (=(cnt=0) (seq=6))
  (=(cnt=0) (seq=7))
  (=(cnt=0) (seq=8))
  (=(cnt=0) (seq=9))
  (=(cnt=0) (seq=10))
  (=(cnt=0) (seq=11))
  (=(cnt=0) (seq=12))
1       2       3       4       5       6       7       8       9       10      11      12
1       1       10      2       16      3       22      4       28      5       34      6
1       1       5       1       8       10      11      2       14      16      17      3
1       1       16      1       4       5       34      1       7       8       52      10
1       1       8       1       2       16      17      1       22      4       26      5
1       1       4       1       1       8       52      1       11      2       13      16
1       1       2       1       1       4       26      1       34      1       40      8
1       1       1       1       1       2       13      1       17      1       20      4
1       1       1       1       1       1       40      1       52      1       10      2
1       1       1       1       1       1       20      1       26      1       5       1
1       1       1       1       1       1       10      1       13      1       16      1
1       1       1       1       1       1       5       1       40      1       8       1
1       1       1       1       1       1       16      1       20      1       4       1
1       1       1       1       1       1       8       1       10      1       2       1
1       1       1       1       1       1       4       1       5       1       1       1
1       1       1       1       1       1       2       1       16      1       1       1
1       1       1       1       1       1       1       1       8       1       1       1
1       1       1       1       1       1       1       1       4       1       1       1
1       1       1       1       1       1       1       1       2       1       1       1
  After
  (=(cnt=0) (seq=1))
  (=(cnt=1) (seq=1))
  (=(cnt=7) (seq=1))
  (=(cnt=2) (seq=1))
  (=(cnt=5) (seq=1))
  (=(cnt=8) (seq=1))
  (=(cnt=16) (seq=1))
  (=(cnt=3) (seq=1))
  (=(cnt=19) (seq=1))
  (=(cnt=6) (seq=1))
  (=(cnt=14) (seq=1))
  (=(cnt=9) (seq=1))

C

Well, this fits the semantics, not sure about the spirit… <lang C>#include <stdio.h>

  1. define JOBS 12
  2. define jobs(a) for (switch_to(a = 0); a < JOBS || !printf("\n"); switch_to(++a))

typedef struct { int seq, cnt; } env_t;

env_t env[JOBS] = Template:0, 0; int *seq, *cnt;

void hail() { printf("% 4d", *seq); if (*seq == 1) return; ++*cnt; *seq = (*seq & 1) ? 3 * *seq + 1 : *seq / 2; }

void switch_to(int id) { seq = &env[id].seq; cnt = &env[id].cnt; }

int main() { int i; jobs(i) { env[i].seq = i + 1; }

again: jobs(i) { hail(); } jobs(i) { if (1 != *seq) goto again; }

printf("COUNTS:\n"); jobs(i) { printf("% 4d", *cnt); }

return 0; }</lang>

Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1

COUNTS:
   0   1   7   2   5   8  16   3  19   6  14   9

D

D doesn't have first class environments, this is an approximation.

Translation of: Python

<lang d>import std.stdio, std.algorithm, std.range, std.array;

struct Prop {

   int[string] data;
   ref opDispatch(string s)() pure nothrow {
       return data[s];
   }

}

immutable code = ` writef("% 4d", e.seq); if (e.seq != 1) {

   e.cnt++;
   e.seq = (e.seq & 1) ? 3 * e.seq + 1 : e.seq / 2;

}`;

void main() {

   auto envs = 12.iota.map!(i => Prop(["cnt": 0, "seq": i+1])).array;
   while (envs.any!(env => env.seq > 1)) {
       foreach (e; envs) {
           mixin(code);
       }
       writeln;
   }
   writefln("Counts:\n%(% 4d%)", envs.map!(env => env.cnt));

}</lang>

Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
Counts:
   0   1   7   2   5   8  16   3  19   6  14   9

Erlang

The Erlang modifiable environment, aka process dictionary, has the following warning in the documentation:

Note that using the Process Dictionary: 
Destroys referencial transparency 
Makes debugging difficult 
Survives Catch/Throw

There is a lot of code below to manage the tabular printout. Otherwise the task is simple. <lang Erlang> -module( first_class_environments ).

-export( [task/0] ).

task() -> Print_pid = erlang:spawn( fun() -> print_loop() end ), Environments = lists:seq( 1, 12 ), Print_pid ! "Environment: Sequence", Pids = [erlang:spawn(fun() -> hailstone_in_environment(Print_pid, X) end) || X <- Environments], Counts = counts( Pids ), Print_pid ! "{Environment, Step count}", Print_pid ! lists:flatten( io_lib:format("~p", [Counts]) ), ok.


counts( Pids ) -> My_pid = erlang:self(), [X ! {count, My_pid} || X <- Pids], counts( Pids, [] ).

counts( [], Acc ) -> Acc; counts( Pids, Acc ) -> receive {count, N, Count, Pid} -> counts( lists:delete(Pid, Pids), [{N, Count} | Acc] ) end.

hailstone_in_environment( Print_pid, N ) -> erlang:put( hailstone_value, N ), erlang:put( count, 0 ), hailstone_loop( hailstone_loop_done(N), Print_pid, N, [N] ).

hailstone_loop( stop, Print_pid, N, Acc ) -> Environment = lists:flatten( io_lib:format("~11B:", [N]) ), Sequence = lists:flatten( [io_lib:format("~4B", [X]) || X <- lists:reverse(Acc)] ), Print_pid ! Environment ++ Sequence, Count= erlang:get( count ), receive {count, Pid} -> Pid ! {count, N, Count, erlang:self()} end; hailstone_loop( keep_going, Print_pid, N, Acc ) -> Next = hailstone_next( erlang:get(hailstone_value) ), erlang:put( hailstone_value, Next ), Count = erlang:get( count ), erlang:put( count, Count + 1 ), hailstone_loop( hailstone_loop_done(Next), Print_pid, N, [Next | Acc] ).

hailstone_loop_done( 1 ) -> stop; hailstone_loop_done( _N ) -> keep_going.

hailstone_next( 1 ) -> 1; hailstone_next( Even ) when (Even rem 2) =:= 0 -> Even div 2; hailstone_next( Odd ) -> (3 * Odd) + 1.

print_loop() -> receive String -> io:fwrite("~s~n", [String] ) end, print_loop(). </lang>

Output:
13>  first_class_environments:task().
Environment:  Sequence
          1:   1
          2:   2   1
          3:   3  10   5  16   8   4   2   1
          4:   4   2   1
          5:   5  16   8   4   2   1
          6:   6   3  10   5  16   8   4   2   1
          7:   7  22  11  34  17  52  26  13  40  20  10   5  16   8   4   2   1
          8:   8   4   2   1
          9:   9  28  14   7  22  11  34  17  52  26  13  40  20  10   5  16   8   4   2   1
         10:  10   5  16   8   4   2   1
         11:  11  34  17  52  26  13  40  20  10   5  16   8   4   2   1
         12:  12   6   3  10   5  16   8   4   2   1
{Environment, Step count}
[{12,9}, {11,14}, {10,6}, {9,19}, {8,3}, {7,16}, {6,8}, {5,5}, {4,2}, {3,7}, {2,1}, {1,0}]

Icon and Unicon

The simplest way to create an environment with variables isolated from code in Icon/Unicon is to create instances of records or class objects. <lang Icon>link printf

procedure main()

  every put(environment := [], hailenv(1 to 12,0))  # setup environments 
  printf("Sequences:\n")
  while (e := !environment).sequence > 1 do {
     every hailstep(!environment) 
     printf("\n")
     }
  printf("\nCounts:\n")
  every printf("%4d ",(!environment).count)
  printf("\n")

end

record hailenv(sequence,count)

procedure hailstep(env)

  printf("%4d ",env.sequence)
   if env.sequence ~= 1 then {
       env.count +:= 1
       if env.sequence % 2 = 0 then env.sequence /:= 2 
       else env.sequence := 3 * env.sequence + 1
       }

end</lang>

printf.icn provides formatting

Output:
Sequences:
   1    2    3    4    5    6    7    8    9   10   11   12 
   1    1   10    2   16    3   22    4   28    5   34    6 
   1    1    5    1    8   10   11    2   14   16   17    3 
   1    1   16    1    4    5   34    1    7    8   52   10 
   1    1    8    1    2   16   17    1   22    4   26    5 
   1    1    4    1    1    8   52    1   11    2   13   16 
   1    1    2    1    1    4   26    1   34    1   40    8 
   1    1    1    1    1    2   13    1   17    1   20    4 
   1    1    1    1    1    1   40    1   52    1   10    2 
   1    1    1    1    1    1   20    1   26    1    5    1 
   1    1    1    1    1    1   10    1   13    1   16    1 
   1    1    1    1    1    1    5    1   40    1    8    1 
   1    1    1    1    1    1   16    1   20    1    4    1 
   1    1    1    1    1    1    8    1   10    1    2    1 
   1    1    1    1    1    1    4    1    5    1    1    1 
   1    1    1    1    1    1    2    1   16    1    1    1 
   1    1    1    1    1    1    1    1    8    1    1    1 
   1    1    1    1    1    1    1    1    4    1    1    1 
   1    1    1    1    1    1    1    1    2    1    1    1 

Counts:
   0    1    7    2    5    8   16    3   19    6   14    9 

J

I have tried to determine what makes this task interesting (see talk page), but I am still confused.

Here is my current interpretation of the task requirements: <lang j>coclass 'hailstone'

step=:3 :0

 NB. and determine next element in hailstone sequence
 if.1=N do. N return.end.
   NB. count how many times this has run when N was not 1
   STEP=:STEP+1
 if.0=2|N do.
   N=: N%2
 else.
   N=: 1 + 3*N
 end.

)

create=:3 :0

 STEP=: 0
 N=: y

)

current=:3 :0

 N__y

)

run1=:3 :0

 step__y
 STEP__y

)

run=:3 :0

 old=: 
 while. -. old -: state=: run1"0 y do.
   smoutput 4j0 ": current"0 y
   old=: state
 end.

)</lang>

Example use:

<lang j> environments=: conew&'hailstone'"0 (1+i.12)

  run_hailstone_ environments
  1   1  10   2  16   3  22   4  28   5  34   6
  1   1   5   1   8  10  11   2  14  16  17   3
  1   1  16   1   4   5  34   1   7   8  52  10
  1   1   8   1   2  16  17   1  22   4  26   5
  1   1   4   1   1   8  52   1  11   2  13  16
  1   1   2   1   1   4  26   1  34   1  40   8
  1   1   1   1   1   2  13   1  17   1  20   4
  1   1   1   1   1   1  40   1  52   1  10   2
  1   1   1   1   1   1  20   1  26   1   5   1
  1   1   1   1   1   1  10   1  13   1  16   1
  1   1   1   1   1   1   5   1  40   1   8   1
  1   1   1   1   1   1  16   1  20   1   4   1
  1   1   1   1   1   1   8   1  10   1   2   1
  1   1   1   1   1   1   4   1   5   1   1   1
  1   1   1   1   1   1   2   1  16   1   1   1
  1   1   1   1   1   1   1   1   8   1   1   1
  1   1   1   1   1   1   1   1   4   1   1   1
  1   1   1   1   1   1   1   1   2   1   1   1
  1   1   1   1   1   1   1   1   1   1   1   1

0 1 7 2 5 8 16 3 19 6 14 9</lang> In essence: run is a static method of the class hailstone which, given a list of objects of the class runs all of them until their hailstone sequence number stops changing. It also displays the hailstone sequence number from each of the objects at each step. Its result is the step count from each object.

Lua

In Lua, environments capture reads and writes to "global" variables (the environment for locals is static). Functions can have their own environments, or multiple functions can share an environment. Functions inherit the environment of their enclosing function when they are instantiated.

The way in which environments are manipulated depends on the Lua version:

  • Lua 5.1 and before: the setfenv function
  • Lua 5.2: an upvalue called _ENV

<lang lua> local envs = { } for i = 1, 12 do

   -- fallback to the global environment for io and math
   envs[i] = setmetatable({ count = 0, n = i }, { __index = _G })

end

local code = [[ io.write(("% 4d"):format(n)) if n ~= 1 then

   count = count + 1
   n = (n % 2 == 1) and 3 * n + 1 or math.floor(n / 2)

end ]]

while true do

   local finished = 0
   for _, env in ipairs(envs) do
       if env.n == 1 then finished = finished + 1 end
   end
   if finished == #envs then break end
   for _, env in ipairs(envs) do
       -- 5.1; in 5.2, use load(code, nil, nil, env)() instead
       setfenv(loadstring(code), env)() 
   end 
   io.write "\n"

end

print "counts:" for _, env in ipairs(envs) do

   io.write(("% 4d"):format(env.count))

end </lang>

Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
counts:
   0   1   7   2   5   8  16   3  19   6  14   9

Order

Order supports environments as a first-class type, but since all values are immutable, updating a value means using one environment to update the next in a chain (nothing unusual for languages with immutable data structures): <lang c>#include <order/interpreter.h>

  1. define ORDER_PP_DEF_8hail ORDER_PP_FN( \

8fn(8N, 8cond((8equal(8N, 1), 1) \

         (8is_0(8remainder(8N, 2)), 8quotient(8N, 2)) \
         (8else, 8inc(8times(8N, 3))))) )
  1. define ORDER_PP_DEF_8h_loop ORDER_PP_FN( \

8fn(8S, \

   8let((8F, 8fn(8E, 8env_ref(8(8H), 8E))),                        \
        8do(                                                       \
          8print(8seq_to_tuple(8seq_map(8F, 8S)) 8space),          \
          8let((8S, 8h_once(8S)),                                  \
               8if(8equal(1,                                       \
                          8seq_fold(8times, 1, 8seq_map(8F, 8S))), \
                   8print_counts(8S),                              \
                   8h_loop(8S)))))) )
  1. define ORDER_PP_DEF_8h_once ORDER_PP_FN( \

8fn(8S, \

   8seq_map(                                                      \
     8fn(8E,                                                      \
         8eval(8E,                                                \
               8quote(                                            \
                 8env_bind(8(8C),                                 \
                           8env_bind(8(8H),                       \
                                     8env_bind(8(8E), 8E, 8E),    \
                                     8hail(8H)),                  \
                           8if(8equal(8H, 1), 8C, 8inc(8C))) ))), \
     8S)) )
  1. define ORDER_PP_DEF_8print_counts ORDER_PP_FN( \

8fn(8S, \

   8print(8space 8(Counts:)                    \
          8seq_to_tuple(8seq_map(8fn(8E, 8env_ref(8(8C), 8E)), 8S)))) )

ORDER_PP(

 8let((8S,    // Build a list of environments
       8seq_map(8fn(8N, 8seq_of_pairs_to_env(
                          8seq(8pair(8(8H), 8N), 8pair(8(8C), 0),
                               8pair(8(8E), 8env_nil)))),
                8seq_iota(1, 13))),
      8h_loop(8S))

)</lang>

Output:

<lang>(1,2,3,4,5,6,7,8,9,10,11,12) (1,1,10,2,16,3,22,4,28,5,34,6) (1,1,5,1,8,10,11,2,14,16,17,3) (1,1,16,1,4,5,34,1,7,8,52,10) (1,1,8,1,2,16,17,1,22,4,26,5) (1,1,4,1,1,8,52,1,11,2,13,16) (1,1,2,1,1,4,26,1,34,1,40,8) (1,1,1,1,1,2,13,1,17,1,20,4) (1,1,1,1,1,1,40,1,52,1,10,2) (1,1,1,1,1,1,20,1,26,1,5,1) (1,1,1,1,1,1,10,1,13,1,16,1) (1,1,1,1,1,1,5,1,40,1,8,1) (1,1,1,1,1,1,16,1,20,1,4,1) (1,1,1,1,1,1,8,1,10,1,2,1) (1,1,1,1,1,1,4,1,5,1,1,1) (1,1,1,1,1,1,2,1,16,1,1,1) (1,1,1,1,1,1,1,1,8,1,1,1) (1,1,1,1,1,1,1,1,4,1,1,1) (1,1,1,1,1,1,1,1,2,1,1,1) Counts:(0,1,7,2,5,8,16,3,19,6,14,9)</lang> The C preprocessor cannot output newlines, so the output is all on one line, but easily parsable.

PicoLisp

Runtime environments can be controlled with the 'job' function: <lang PicoLisp>(let Envs

  (mapcar
     '((N) (list (cons 'N N) (cons 'Cnt 0)))  # Build environments
     (range 1 12) )
  (while (find '((E) (job E (> N 1))) Envs)   # Until all values are 1:
     (for E Envs
        (job E                                # Use environment 'E'
           (prin (align 4 N))
           (unless (= 1 N)
              (inc 'Cnt)                      # Increment step count
              (setq N
                 (if (bit? 1 N)               # Calculate next hailstone value
                    (inc (* N 3))
                    (/ N 2) ) ) ) ) )
     (prinl) )
  (prinl (need 48 '=))
  (for E Envs                                 # For each environment 'E'
     (job E
        (prin (align 4 Cnt)) ) )              # print the step count
  (prinl) )</lang>
Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
================================================
   0   1   7   2   5   8  16   3  19   6  14   9

Perl

The Safe module (which is part of perl's standard distribution) is everything that one might want in a First Class Environment, and a bit more. The module's primary purpose is to provide a safe execution environment for untrustworthy code, and by doing so, it lends itself very well to this task's goal.

My use of the module is relatively straightforward.

Create a Safe object. Within that Safe, set up $value and $count variables, with appropriate initial values. Tell the Safe what "external" functions (from outside of the Safe object) it may run. Add a function, inside of the Safe, which will be run in the Safe, using the Safe's $value and $count variables. Add the Safe to an array. Repeat eleven more times.

Notice that to the function in the safe, $value and $count look like (and are!) perfectly ordinary variables.

Next, repeatedly perform the task, until the required conditions are met, and print the counts.

<lang perl> use strict; use warnings;

use Safe;

sub hail_next {

   my $n = shift;
   return 1 if $n == 1;
   return $n * 3 + 1 if $n % 2;
   $n / 2;

};

my @enviornments; for my $initial ( 1..12 ) {

  my $env = Safe->new;
  ${ $env->varglob('value') } = $initial;
  ${ $env->varglob('count') } = 0;
  $env->share('&hail_next');
  $env->reval(q{
     sub task {
        return if $value == 1;
        $value = hail_next( $value );
        ++$count;
     }
  });
  push @enviornments, $env;

}

my @value_refs = map $_->varglob('value'), @enviornments; my @tasks = map $_->varglob('task'), @enviornments; while( grep { $$_ != 1 } @value_refs ) {

   printf "%4s", $$_ for @value_refs;
   print "\n";
   $_->() for @tasks;

}

print "Counts\n";

printf "%4s", ${$_->varglob('count')} for @enviornments; print "\n"; </lang>

Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
Counts
   0   1   7   2   5   8  16   3  19   6  14   9

Perl 6

Fairly straightforward. Set up an array of hashes containing the current values and iteration counts then pass each hash in turn with a code reference to a routine to calculate the next iteration.

<lang perl6>my $calculator = sub ($n is rw) {

   return ($n == 1) ?? 1 !! $n %% 2 ?? $n div 2 !! $n * 3 + 1

};

sub next (%this is rw, &get_next) {

   return %this if %this.<value> == 1;
   %this.<value>.=&get_next;
   %this.<count>++;
   return %this;

};

my @hailstones = map { $_ = %(value => $_, count => 0) }, 1 .. 12;

while not all( map { $_.<value> }, @hailstones ) == 1 {

   say [~] map { $_.<value>.fmt("%4s") }, @hailstones;
   @hailstones[$_].=&next($calculator) for ^@hailstones;

}

say 'Counts';

say [~] map { $_.<count>.fmt("%4s") }, @hailstones;</lang>

Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
Counts
   0   1   7   2   5   8  16   3  19   6  14   9


Python

In Python, name bindings are held in dicts, one for global scope and another for local scope. When exec'ing code, you are allowed to give your own dictionaries for these scopes. In this example, two names are held in dictionaries that are used as the local scope for the evaluation of source. <lang python>environments = [{'cnt':0, 'seq':i+1} for i in range(12)]

code = print('% 4d' % seq, end=) if seq != 1:

   cnt += 1
   seq = 3 * seq + 1 if seq & 1 else seq // 2

while any(env['seq'] > 1 for env in environments):

   for env in environments:
       exec(code, globals(), env)
   print()

print('Counts') for env in environments:

   print('% 4d' % env['cnt'], end=)

print()</lang>

Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
Counts
   0   1   7   2   5   8  16   3  19   6  14   9

R

<lang R>code <- quote(

         if (n == 1) n else {
           count <- count + 1;
           n <- if (n %% 2 == 1) 3 * n + 1 else n/2
         })

eprint <- function(envs, var="n")

 cat(paste(sprintf("%4d", sapply(envs, `[[`, var)), collapse=" "), "\n")

envs <- mapply(function(...) list2env(list(...)), n=1:12, count=0)

while (any(sapply(envs, eval, expr=code) > 1)) {eprint(envs)} eprint(envs)

cat("\nCounts:\n") eprint(envs, "count")</lang>

Output:
   1    2    3    4    5    6    7    8    9   10   11   12 
   1    1   10    2   16    3   22    4   28    5   34    6 
   1    1    5    1    8   10   11    2   14   16   17    3 
   1    1   16    1    4    5   34    1    7    8   52   10 
   1    1    8    1    2   16   17    1   22    4   26    5 
   1    1    4    1    1    8   52    1   11    2   13   16 
   1    1    2    1    1    4   26    1   34    1   40    8 
   1    1    1    1    1    2   13    1   17    1   20    4 
   1    1    1    1    1    1   40    1   52    1   10    2 
   1    1    1    1    1    1   20    1   26    1    5    1 
   1    1    1    1    1    1   10    1   13    1   16    1 
   1    1    1    1    1    1    5    1   40    1    8    1 
   1    1    1    1    1    1   16    1   20    1    4    1 
   1    1    1    1    1    1    8    1   10    1    2    1 
   1    1    1    1    1    1    4    1    5    1    1    1 
   1    1    1    1    1    1    2    1   16    1    1    1 
   1    1    1    1    1    1    1    1    8    1    1    1 
   1    1    1    1    1    1    1    1    4    1    1    1 
   1    1    1    1    1    1    1    1    2    1    1    1 

Counts:
   0    1    7    2    5    8   16    3   19    6   14    9 

Racket

<lang Racket>

  1. lang racket

(define namespaces

 (for/list ([i (in-range 1 13)])
   (define ns (make-base-namespace))
   (eval `(begin (define N ,i) (define count 0)) ns)
   ns))

(define (get-var-values name)

 (map (curry namespace-variable-value name #t #f) namespaces))

(define code

 '(when (> N 1)
    (set! N (if (even? N) (/ N 2) (+ 1 (* N 3))))
    (set! count (add1 count))))

(define (show-nums nums)

 (for ([n nums]) (display (~a n #:width 4 #:align 'right)))
 (newline))

(let loop ()

 (define Ns (get-var-values 'N))
 (show-nums Ns)
 (unless (andmap (λ(n) (= n 1)) Ns)
   (for ([ns namespaces]) (eval code ns))
   (loop)))

(displayln (make-string (* 4 12) #\=)) (show-nums (get-var-values 'count)) </lang>

Output:

   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
   1   1   1   1   1   1   1   1   1   1   1   1
================================================
   0   1   7   2   5   8  16   3  19   6  14   9

REXX

<lang rexx>/*REXX program illustrates first-class environments (using hailstone #s)*/ parse arg #envs .; env_.=; if #envs== then #envs=12

/*═════════════════════════════════════initialize (twelve) environments.*/

 do init=1  for #envs;    env_.init=init;    end

/*═════════════════════════════════════process environments until done. */

    do forever   until env_.0;        env_.0=1
         do k=1  for #envs
         env_.k=env_.k hailstone(k)   /*where the rubber meets the road*/
         end   /*k*/
    end        /*forever*/

/*═════════════════════════════════════show results in tabular form. */ count=0; do lines=-1 until _==; _=

                   do j=1  for #envs
                         select
                         when count== 1 then _=_ right(words(env_.j)-1,3)
                         when lines==-1 then _=_ right(j,3)
                         when lines== 0 then _=_ right(,3,'─')
                         otherwise       _=_ right(word(env_.j,lines),3)
                         end   /*select*/
                   end         /*j*/
           if count==1  then count=2
           _=strip(_,'T')
           if _==     then count=count+1
           if count==1  then _=copies(' ═══',#envs)
           if _\==    then say substr(_,2)
           end   /*lines*/

exit /*stick a fork in it, we're done.*/

/*─────────────────────────────────────HAILSTONE (Collatz) subroutine───*/ hailstone: procedure expose env_.; arg n; _=word(env_.n,words(env_.n)) if _==1 then return ; env_.0=0; if _//2==0 then return _%2; return _*3+1</lang>

Output:
  1   2   3   4   5   6   7   8   9  10  11  12
─── ─── ─── ─── ─── ─── ─── ─── ─── ─── ─── ───
  1   2   3   4   5   6   7   8   9  10  11  12
      1  10   2  16   3  22   4  28   5  34   6
          5   1   8  10  11   2  14  16  17   3
         16       4   5  34   1   7   8  52  10
          8       2  16  17      22   4  26   5
          4       1   8  52      11   2  13  16
          2           4  26      34   1  40   8
          1           2  13      17      20   4
                      1  40      52      10   2
                         20      26       5   1
                         10      13      16
                          5      40       8
                         16      20       4
                          8      10       2
                          4       5       1
                          2      16
                          1       8
                                  4
                                  2
                                  1
═══ ═══ ═══ ═══ ═══ ═══ ═══ ═══ ═══ ═══ ═══ ═══
  0   1   7   2   5   8  16   3  19   6  14   9

Ruby

Translation of: PicoLisp

The object is an environment for instance variables. These variables use the @ sigil. We create 12 objects, and put @n and @cnt inside these objects. We use Object#instance_eval to switch the current object and bring those instance variables into scope. <lang ruby># Build environments envs = (1..12).map do |n|

 Object.new.instance_eval {@n = n; @cnt = 0; self}

end

  1. Until all values are 1:

while envs.find {|e| e.instance_eval {@n} > 1}

 envs.each do |e|
   e.instance_eval do          # Use environment _e_
     printf "%4s", @n
     unless 1 == @n
       @cnt += 1               # Increment step count
       @n = if 1 & @n == 1     # Calculate next hailstone value
              @n * 3 + 1
            else
              @n / 2
            end
     end
   end
 end
 puts

end puts '=' * 48 envs.each do |e| # For each environment _e_

 e.instance_eval do
   printf "%4s", @cnt          # print the step count
 end

end puts</lang> Ruby also provides the binding, an environment for local variables. The problem is that local variables have lexical scope. Ruby needs the lexical scope to parse Ruby code. So, the only way to use a binding is to evaluate a string of Ruby code. We use Kernel#binding to create the bindings, and Kernel#eval to evaluate strings in these bindings. The lines between <<-'eos' and eos are multi-line string literals. <lang ruby># Build environments envs = (1..12).map do |n|

 e = class Object
       # This is a new lexical scope with no local variables.
       # Create a new binding here.
       binding
     end
 eval(<<-'eos', e).call(n)
   n, cnt = nil, 0
   proc {|arg| n = arg}
 eos
 next e

end

  1. Until all values are 1:

while envs.find {|e| eval('n > 1', e)}

 envs.each do |e|
   eval(<<-'eos', e)           # Use environment _e_
     printf "%4s", n
     unless 1 == n
       cnt += 1                # Increment step count
       n = if 1 & n == 1       # Calculate next hailstone value
             n * 3 + 1
           else
             n / 2
           end
     end
   eos
 end
 puts

end puts '=' * 48 envs.each do |e| # For each environment _e_

 eval(<<-'eos', e)
   printf "%4s", cnt           # print the step count
 eos

end puts</lang>

Tcl

The simplest way to make a first-class environment in Tcl is to use a dictionary; the dict with command (and dict update; not shown here) will expand a dictionary and bind it to variables for the duration of its body script. <lang tcl>package require Tcl 8.5

for {set i 1} {$i <= 12} {incr i} {

   dict set hailenv hail$i [dict create num $i steps 0]

} while 1 {

   set loopagain false
   foreach k [dict keys $hailenv] {

dict with hailenv $k { puts -nonewline [format %4d $num] if {$num == 1} { continue } elseif {$num & 1} { set num [expr {3*$num + 1}] } else { set num [expr {$num / 2}] } set loopagain true incr steps }

   }
   puts ""
   if {!$loopagain} break

} puts "Counts..." foreach k [dict keys $hailenv] {

   dict with hailenv $k {

puts -nonewline [format %4d $steps]

   }

} puts ""</lang>

Output:
   1   2   3   4   5   6   7   8   9  10  11  12
   1   1  10   2  16   3  22   4  28   5  34   6
   1   1   5   1   8  10  11   2  14  16  17   3
   1   1  16   1   4   5  34   1   7   8  52  10
   1   1   8   1   2  16  17   1  22   4  26   5
   1   1   4   1   1   8  52   1  11   2  13  16
   1   1   2   1   1   4  26   1  34   1  40   8
   1   1   1   1   1   2  13   1  17   1  20   4
   1   1   1   1   1   1  40   1  52   1  10   2
   1   1   1   1   1   1  20   1  26   1   5   1
   1   1   1   1   1   1  10   1  13   1  16   1
   1   1   1   1   1   1   5   1  40   1   8   1
   1   1   1   1   1   1  16   1  20   1   4   1
   1   1   1   1   1   1   8   1  10   1   2   1
   1   1   1   1   1   1   4   1   5   1   1   1
   1   1   1   1   1   1   2   1  16   1   1   1
   1   1   1   1   1   1   1   1   8   1   1   1
   1   1   1   1   1   1   1   1   4   1   1   1
   1   1   1   1   1   1   1   1   2   1   1   1
   1   1   1   1   1   1   1   1   1   1   1   1
Counts...
   0   1   7   2   5   8  16   3  19   6  14   9