First class environments

Revision as of 17:08, 2 July 2011 by rosettacode>Dkf (→‎Tcl: Added implementation)

A first class environment is a set of variable bindings which can be stored in variables, passed to functions, compared, etc.

First class environments 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.

The task is to 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.

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<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>

Go

Works with: weekly.2011-06-23

Using the exp/eval package. Exp packages are experimental, meaning this RC solution is likely just a sketch for something better that will be possible in the future as Go evolves.

Variance: exp/eval (being an incomplete experiment) does not handle packages, so the common piece of code has no way to print hv, as requested by the task. Instead, printing is done outside of the common piece of code, by directly inspecting the environments. <lang go>package main

import (

   "exp/eval"
   "fmt"
   "go/parser"
   "go/token"
   "os"

)

type pair struct {

   name  string
   value int

}

type environment struct {

   binding map[string]*intV
   world   *eval.World

}

func newEnvironment(iv []pair) (environment, os.Error) {

   e := environment{make(map[string]*intV), eval.NewWorld()}
   for _, p := range iv {
       v := intV(p.value)
       err := e.world.DefineVar(p.name, eval.IntType, &v)
       if err != nil {
           return environment{}, err
       }
       e.binding[p.name] = &v
   }
   return e, nil

}

func main() {

   // create 12 environments with intial values
   e12 := make([]environment, 12)
   var err os.Error
   for i := range e12 {
       e12[i], err = newEnvironment([]pair{{"hv", i + 1}, {"count", 0}})
       if err != nil {
           fmt.Println(err)
           return
       }
   }
   // define single piece of code.
   // the parser returns a list of abstract syntax trees.
   fset := token.NewFileSet()
   spoc, err := parser.ParseStmtList(fset, "spoc", `
       if hv > 1 {
           if hv % 2 == 0 {
               hv /= 2
           } else {
               hv = 3*hv + 1
           }
           count++
       }
   `)
   if err != nil {
       fmt.Println(err)
       return
   }
   // iterate until all sequences complete

all:

   for {
       // check for all sequences complete
       for i := 0; *e12[i].binding["hv"] == 1; i++ {
           if i == 11 {
               break all
           }
       }
       // iterate sequences
       for _, e := range e12 {
           // output hv in this environment
           // (library deficiency that it can't be done in the spoc.)
           fmt.Printf(" %3d", *e.binding["hv"])
           // bind code,
           code, err := e.world.CompileStmtList(fset, spoc)
           if err != nil {
               fmt.Println(err)
               return
           }
           // evaluate,
           _, err = code.Run()
           if err != nil {
               fmt.Println(err)
               return
           }
           // and abandon the code binding.  it's a little wasteful.
           // code binding could be done up front, once for each environment,
           // and saved in the environment struct.  it's repeated here just
           // to avoid the question of whether it is still a "single piece
           // of code" if it is compiled 12 times and pointers to the 12
           // instances are saved.
       }
       fmt.Println()
   }
   fmt.Println("Counts:")
   for _, e := range e12 {
       fmt.Printf(" %3d", *e.binding["count"])
   }
   fmt.Println()

}

// int value implementation. (you'd think this should be exported // from exp/eval, but then the exp does stand for experimental.) type intV int func (v *intV) String() string { return fmt.Sprint(*v) } func (v *intV) Get(*eval.Thread) int64 { return int64(*v) } func (v *intV) Set(_ *eval.Thread, x int64) { *v = intV(x) } func (v *intV) Assign(t *eval.Thread, o eval.Value) {

   *v = intV(o.(eval.IntValue).Get(t))

}</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

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.

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

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

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

<= 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