Go’s History in Code
This post intends to showcase programming patterns, or stuff, which is common between Newsqueak, Alef, Plan9 C, Limbo, and Go.
All of these code snippets should be complete as shown and compilable/runnable in the state presented. The Alef examples might have subtle typos, but this is due to the fact I had to manually re-type them out from VirtualBox since I didn’t have copy/paste available to the guest.
Commentary has been inserted before examples where I believe it will be helpful.
If I missed a feature or misreported a fact, feel free to open an issue/PR against the blog on GitHub.
Motivation
Articles or posts talking about Go’s predecessors have a habit of referring to the languages listed above, but can fail to provide concrete resources for seeing how these languages work. This post aims to provide a reference for such languages.
I find being able to play with languages and see complete programs showing how the language fits together as a whole rather than just small snippets extremely valuable for learning.
In particular with older languages, it can be difficult to find ways to practically experiment with the language and get a feel for the ergonomics of the error reporting, building of complex programs, etc. without having access to the compiler/interpreter/environment itself. As such, paths to being able to write in these languages have been provided for each language presented.
Note that this reference is not intended to be exhaustive.
Building and running examples
Newsqueak
The unix port of squint is found at https://github.com/rwos/newsqueak.
The papers describing Newsqueak:
To run a program from a prompt:
$ squint foo.nq
# output, if any
$
Alef
Your best bet at trying Alef is installing a Plan9 2nd edition (2e) virtual machine. A text guide for this process is in a prior blog post and a complementary video guide for installation.
Papers on Alef: http://doc.cat-v.org/plan_9/2nd_edition/papers/alef/
More resources on 2e: http://9.postnix.pw/hist/2e/
Direct download to a VirtualBox image of 2e: http://9.postnix.pw/hist/2e/plan92e_vbox.tgz
There’s also a work-in-progress port of Alef from 2e to 9front/386 which can be found on the public grid griddisk at /burnzez/rep/alef/root
and maybe /burnzez/alef
. Griddisk is accessible over 9p via tcp!45.63.75.148!9564
. You can more easily access the grid from unix via the gridnix scripts.
From a prompt on a complete Plan9 2e installation:
term% 8al foo.l
term% 8l foo.8
term% 8.out
# output, if any
term%
Plan9 C
The most actively maintained Plan9 fork is 9front.
A more Bell Labs-like experience may be found in 9legacy. Instructions and workflow should be similar.
Papers describing the Plan9 C dialect:
- http://doc.cat-v.org/plan_9/programming/c_programming_in_plan_9
- http://doc.cat-v.org/plan_9/4th_edition/papers/compiler
The Plan9 C dialect was partially described with a narrative in a previous blog post.
From a 386 9front system:
term% 8c foo.c
term% 8l foo.8
term% 8.out
# output, if any
term%
From an amd64 9front system:
term% 6c foo.c
term% 6l foo.6
term% 6.out
# output, if any
term%
Arm uses 5c
and 5l
for compiling/linking respectively as per the manuals 2c(1) and 2l(1).
Limbo
The official Inferno repository: https://bitbucket.org/inferno-os/inferno-os/
The purgatorio Inferno fork: https://code.9front.org/hg/purgatorio
There are a variety of other resources for Inferno and Limbo available 1.
Papers describing Limbo:
- http://doc.cat-v.org/inferno/4th_edition/limbo_language/descent
- http://doc.cat-v.org/inferno/4th_edition/limbo_language/limbo
- http://doc.cat-v.org/inferno/4th_edition/limbo_language/addendum
From a prompt inside the Inferno virtual machine (or native):
; limbo foo.b
; foo
# output, if any
;
Go
Go can be acquired from https://golang.org.
The specification for Go: https://golang.org/ref/spec
To run a single file program:
$ go run foo.go
# output, if any
$
Intro - tokenizing
This section demonstrates standard library naïve tokenizing.
Newsqueak
Nope.
Alef
#include <alef.h>
#define NTOKS 9
#define MAXTOK 512
#define str "abc » 'test 1 2 3' !"
void
main(void)
{
int n, i;
byte *toks[MAXTOK];
print("%s\n", str);
n = tokenize(str, toks, NTOKS);
for(i = 0; i < n; i++)
print("%s\n", toks[i]);
exits(nil);
}
Output
abc » 'test 1 2 3' !
abc
»
'test
1
2
3'
!
Plan9 C
Related reading: tokenize(2)
#include <u.h>
#include <libc.h>
#define NTOKS 9
#define MAXTOK 512
char *str = "abc ☺ 'test 1 2 3' !";
void
main(int, char*[])
{
int n, i;
char *toks[MAXTOK];
print("%s\n", str);
n = tokenize(str, (char**)toks, NTOKS);
for(i = 0; i < n; i++)
print("%s\n", toks[i]);
exits(nil);
}
Output
abc ☺ 'test 1 2 3' !
abc
☺
test 1 2 3
!
Limbo
Related reading: sys-tokenize(2)
implement Tokenizing;
include "sys.m";
sys: Sys;
include "draw.m";
Tokenizing: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
str: con "abc ☺ 'test 1 2 3' !";
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
sys->print("%s\n", str);
(n, toks) := sys->tokenize(str, "\n\t ");
for(; toks != nil; toks = tl toks) {
sys->print("%s\n", hd toks);
}
exit;
}
Output
abc ☺ 'test 1 2 3' !
abc
☺
'test
1
2
3'
!
Go
package main
import (
"fmt"
"strings"
)
const str = "abc ☺ 'test 1 2 3' !"
func main() {
fmt.Println(str)
fields := strings.Fields(str)
for _, f := range fields {
fmt.Println(f)
}
}
Output
abc ☺ 'test 1 2 3' !
abc
☺
'test
1
2
3'
!
Asynchronous spawning
Many of the languages which inspired Go contained simple abstractions for running functions in asychronous coroutines, processes, or threads.
Newsqueak
double := prog(n : int) {
print(2*n, "\n");
};
# Begin main logic
begin double(7);
begin double(9);
begin double(11);
Output
14
18
22
Alef
#include <alef.h>
void
double(int n)
{
print("%d\n", 2*n);
}
void
main(void)
{
task double(7); /* A coroutine */
proc double(9); /* A process */
par {
double(11); /* A process */
double(13); /* A process */
}
sleep(5);
}
Output
18
26
22
14
Plan9 C
Related reading: thread(2)
#include <u.h>
#include <libc.h>
#include <thread.h>
void
doubleN(void *n)
{
print("%d\n", 2*(*(int*)n));
}
void
threadmain(int, char*[])
{
int s₀ = 7, s₁ = 9;
threadcreate(doubleN, &s₁, 4096); // A thread
proccreate(doubleN, &s₀, 4096); // A process
sleep(5);
threadexitsall(nil);
}
Output
14
18
Limbo
implement Coroutines;
include "sys.m";
sys: Sys;
include "draw.m";
Coroutines: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
double(n: int) {
sys->print("%d\n", 2*n);
}
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
spawn double(7);
sys->sleep(5);
exit;
}
Output
14
Go
package main
import (
"fmt"
"time"
)
func double(n int) {
fmt.Println(2*n)
}
func main() {
go double(7)
time.Sleep(5 * time.Millisecond)
}
Output
14
Sending and receiving on channels
Channels are a core part of the Communicating Sequential Processes (CSP) 2 model for concurrent synchronization and communication.
Go and its predecessors support standard library/primitive-level tooling for working with channels.
Newsqueak
max := 10;
# Prints out numbers as they're received
printer := prog(c: chan of int)
{
i : int;
for(i = 0; i < max; i++){
n := <- c;
print(n, " ");
}
print("\n");
};
# Pushes values into the channel
pusher := prog(c: chan of int)
{
i : int;
for(i = 0; i < max; i++){
c <-= i * i;
}
};
# Begin main logic
printChan := mk(chan of int);
begin printer(printChan);
begin pusher(printChan);
Output
0 1 4 9 16 25 36 49 64 81
Alef
Note the ?
and [n]
syntax for channels as defined in the “Alef User’s Guide”. 3
#include <alef.h>
int max = 10;
void
pusher(chan(int) printchan)
{
int i;
for(i = 0; i < max; i++)
printchan <-= i * i;
}
void
printer(chan(int) printchan)
{
int n, i;
for(i = 0; i < max; i++){
n = <-printchan;
print("%d\n", n);
}
}
void
main(void)
{
chan(int) printchan;
chan(int)[2] bufchan;
alloc printchan, bufchan;
par {
pusher(printchan);
printer(printchan);
}
while(bufchan?){
i = ++i * ++i;
bufchan <- = i;
print("sent %d\n", i);
}
while(?bufchan)
print("received %d\n", <-bufchan);
}
Output
0
1
4
9
16
25
36
49
64
81
sent 2
sent 12
received 2
received 12
Plan9 C
Related reading: thread(2)
#include <u.h>
#include <libc.h>
#include <thread.h>
const max = 10;
void printer(void *v)
{
Channel *printchan = (Channel*) v;
int i, n;
for(i = 0; i < max; i++){
recv(printchan, &n);
print("received → %d\n", n);
}
threadexits(nil);
}
void pusher(void *v)
{
Channel *printchan = (Channel*) v;
int i, *n;
n = calloc(1, sizeof (int));
for(i = 0; i < max; i++){
*n = i * i;
send(printchan, n);
}
threadexits(nil);
}
void
threadmain(int, char*[]) {
int bufsize = 2;
Channel *printchan = chancreate(sizeof (int), 0);
proccreate(printer, printchan, 4096);
proccreate(pusher, printchan, 4096);
sleep(100);
threadexitsall(nil);
}
Output
received → 0
received → 1
received → 4
received → 9
received → 16
received → 25
received → 36
received → 49
received → 64
received → 81
Limbo
implement Channels;
include "sys.m";
sys: Sys;
include "draw.m";
Channels: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
max : con 10;
printer(c: chan of int) {
i : int;
for(i = 0; i < max; i++){
n := <- c;
sys->print("%d ", n);
}
sys->print("\n");
}
pusher(c: chan of int) {
i : int;
for(i = 0; i < max; i++){
c <-= i * i;
}
}
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
printChan := chan of int;
spawn printer(printChan);
spawn pusher(printChan);
sys->sleep(1);
exit;
}
Output
0 1 4 9 16 25 36 49 64 81
Go
Note that Go is not the only language to support the optional ok
value when reading out of a channel, the Plan9 C functions send(2)
and recv(2)
have a return value: “Send and recv return 1 on success, -1 if interrupted.” 4
package main
import (
"fmt"
)
const max = 10
func printer(c chan int, done chan bool) {
for {
n, ok := <- c
if !ok {
break
}
fmt.Print(n, " ")
}
fmt.Println()
done <- true
}
func pusher(c chan int) {
for i := 0; i < max; i++ {
c <- i * i
}
close(c)
}
func main() {
c := make(chan int, 2)
done := make(chan bool)
go printer(c, done)
go pusher(c)
<- done
}
Output
0 1 4 9 16 25 36 49 64 81
Selecting on multiple channels
Continuing with the CSP channel communication model, Go and its predecessors support primitives for alternating - or selecting - on multiple channels, typically in switch/case-like statement specifying a case of ready to send/receive.
Newsqueak
max := 2;
# Selects on two channels for both being able to receive and send
selector := prog(prodChan : chan of int, recChan : chan of int, n : int){
i : int;
for(;;)
select{
case i =<- prodChan:
print("case recv ← ", i, "\n");
case recChan <-= n:
print("case send → ", n, "\n");
}
};
# Pushes `max` values into `prodChan`
producer := prog(n : int, prodChan : chan of int){
i : int;
for(i = 0; i < max; i++){
print("pushed → ", n, "\n");
prodChan <-= n;
}
};
# Reads `max` values out of `recChan`
receiver := prog(recChan : chan of int){
i : int;
# Stop receiving, manually
for(i = 0; i < max; i++)
print("received → ", <- recChan, "\n");
};
# Begin main logic
prodChan := mk(chan of int);
recChan := mk(chan of int);
begin producer(123, prodChan);
begin receiver(recChan);
begin selector(prodChan, recChan, 456);
Output
pushed → 123
pushed → 123
case recv ← 123
case send → 456
received → 456
case send → 456
received → 456
case recv ← 123
Alef
#include <alef.h>
int max = 2;
void
selector(chan(int) prodchan, chan(int) recchan, int n)
{
int i;
for(;;)
alt{
case i =<- prodchan:
print("case recv ← %d\n", i);
case recchan <-= n:
print("case send → %d\n", n);
}
}
void
producer(int n, chan(int) prodchan)
{
int i;
for(i = 0; i < max; i++){
print("pushed → %d\n", n);
prodchan <-= n;
}
}
void
receiver(chan(int) recchan)
{
int i;
for(i = 0; i < max; i++){
int n;
n = <- recchan;
print("received → %d\n", n);
}
}
void
main(void)
{
chan(int) prodchan;
chan(int) recchan;
alloc prodchan;
alloc recchan;
proc producer(123, prodchan);
proc receiver(recchan);
proc selector(prodchan, recchan, 456);
sleep(15);
}
Output
pushed → 123
case send → 456
case recv ← 123
received → 456
received → 456
pushed → 123
case send → 456
case recv ← 123
Plan9 C
Related reading: thread(2)
#include <u.h>
#include <libc.h>
#include <thread.h>
const int max = 2;
typedef struct Tuple Tuple;
struct Tuple {
Channel *a;
Channel *b;
};
void
selector(void *v)
{
Tuple *t = (Tuple*)v;
Channel *prodchan = t->a;
Channel *recchan = t->b;
// Set up vars for alt
int pn;
int *rn = malloc(1 * sizeof (int));
*rn = 456;
// Set up alt
Alt alts[] = {
{prodchan, &pn, CHANRCV},
{recchan, rn, CHANSND},
{nil, nil, CHANEND},
};
for(;;)
switch(alt(alts)){
case 0:
// prodchan open for reading
recv(prodchan, &pn);
print("case recv ← %d\n", pn);
break;
case 1:
// recchan open for writing
send(recchan, rn);
print("case send → %d\n", *rn);
break;
default:
break;
}
}
void
producer(void *v)
{
Channel *prodchan = (Channel*)v;
int *n = malloc(1 * sizeof (int));
*n = 123;
int i;
for(i = 0; i < max; i++){
print("pushed → %d\n", *n);
send(prodchan, n);
}
chanclose(prodchan);
}
void
receiver(void *v)
{
Channel *recchan = (Channel*)v;
int i;
int n;
for(i = 0; i < max; i++){
recv(recchan, &n);
print("received → %d\n", n);
}
chanclose(recchan);
}
void
threadmain(int, char*[])
{
// Set up channels
Channel *prodchan = chancreate(sizeof (int), max);
Channel *recchan = chancreate(sizeof (int), max);
Tuple *chans = malloc(1 * sizeof (Tuple));
chans->a = prodchan;
chans->b = recchan;
// Start processes
proccreate(producer, prodchan, 4096);
proccreate(receiver, recchan, 4096);
proccreate(selector, chans, 4096);
sleep(1000);
threadexitsall(nil);
}
Output
pushed → 123
received → 456
case send → 456
pushed → 123
received → 456
case send → 456
case recv ← 123
case send → 456
case recv ← 123
Limbo
implement Select;
include "sys.m";
sys: Sys;
print: import sys;
include "draw.m";
Select: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
max : con 2;
selector(prodChan: chan of int, recChan: chan of int, n: int) {
for(;;)
alt {
i := <- prodChan =>
print("case recv ← %d\n", i);
recChan <-= n =>
print("case send → %d\n", n);
* =>
break;
}
}
producer(n: int, prodChan: chan of int) {
for(i := 0; i < max; i++){
print("pushed → %d\n", n);
prodChan <-= n;
}
}
receiver(recChan: chan of int) {
for(i := 0; i < max; i++)
print("received → %d\n", <- recChan);
}
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
prodChan := chan of int;
recChan := chan of int;
spawn producer(123, prodChan);
spawn receiver(recChan);
spawn selector(prodChan, recChan, 456);
sys->sleep(1000);
exit;
}
Output
pushed → 123
case send → 456
received → 456
case recv ← 123
pushed → 123
case recv ← 123
case send → 456
received → 456
Go
package main
import (
"fmt"
"time"
)
func printer(intChan chan int, strChan chan string, stopChan chan bool) {
strClosed := false
loop:
for {
select {
case n := <- intChan:
fmt.Println(n)
case s, ok := <- strChan:
if !ok {
strClosed = true
} else {
fmt.Println(s)
}
case stopChan <- true:
if strClosed {
break loop
}
}
}
fmt.Println("done.")
}
func makeInts(intChan chan int, stopChan chan bool) {
for i := 0; i < 3; i++ {
intChan <- i*i
}
<- stopChan
}
func makeStrings(strChan chan string) {
strings := []string{"a", "b", "☺"}
for _, s := range strings {
strChan <- s
}
close(strChan)
}
func main() {
stopChan := make(chan bool, 1)
stopChan <- true
intChan := make(chan int)
size := 3
strChan := make(chan string, size)
go printer(intChan, strChan, stopChan)
go makeInts(intChan, stopChan)
go makeStrings(strChan)
time.Sleep(10 * time.Millisecond)
}
Output
0
a
1
b
☺
4
done.
Unnamed struct members and promotion
Go, including some of its predecessors, includes the ability to have unnamed sub-structures which can be contextually promoted.
The details of how this promotion works, if present, is found under each language’s respective specification/documentation.
Pay attention to how and where different elements of different structures are called in these examples.
Note that gcc supports this feature in a similar way under the -fplan9-extensions
flag. 5
Newsqueak
Nope.
Alef
#include <alef.h>
aggr Point {
int x;
int y;
};
aggr Line {
Point p1;
Point p2;
};
aggr Circle {
Point;
int radius;
};
aggr Shape {
int type;
union {
Circle;
Line;
};
};
adt Person {
extern int age;
extern void ageup(*Person, int);
extern Person init();
};
adt Worker {
extern Person;
extern byte *position;
extern Worker init(Person);
};
Worker
Worker.init(Person p)
{
Worker w;
w.position = "none";
w.Person = p;
return w;
}
void
Person.ageup(Person *p, int n)
{
p->age += n;
}
Person
Person.init()
{
Person p;
p.age = 1;
return p;
}
int equal(Point p1, Point p2)
{
return p1.x == p2.x && p1.y == p2.y;
}
Point
mirror(Point p)
{
p.x *= -1;
p.y *= -1;
return p;
}
void
main(void)
{
Point p0, p1;
Circle c;
Shape s;
Line l;
Person sean, ana;
Worker engineer;
p0 = (Point)(3, -1);
c.Point = p0;
p0 = c;
p1 = mirror(c);
if(equal(p0, c))
print("p0 = c\n");
else
print("p0 ≠ c\n");
print("c's point = (%d,%d)\n", c.x, c.y);
print("p1 = (%d,%d)\n", p1.x, p1.y);
l = (Line)(p0, p1);
s.Line = l;
s.type = 0; /* a line */
if(s.type == 0)
print("Shape is line (%d,%d) → (%d,%d)\n", s.p1.x, s.p1.y, s.p2.x, s.p2.y);
sean = .Person.init();
engineer = .Worker.init(sean);
engineer.ageup(2);
print("engineer position \"%s\" is %d years old\n",
engineer.position, engineer.age);
ana = engineer;
print("ana age = %d\n", ana.age);
}
Output
p0 = c
c's point = (3,-1)
p1 = (-3,1)
Shape is line (3,-1) → (-3,1)
engineer position "none" is 3 years old
ana age = 3
Plan9 C
This example is partially derived from the unnamed subunion example presented in the ‘Plan 9 C Compilers’ paper. 6
#include <u.h>
#include <libc.h>
double π = 3.14;
typedef struct Point Point;
typedef struct Circle Circle;
typedef struct Number Number;
typedef struct Value Value;
struct Number {
union {
double dval;
float fval;
long lval;
};
};
struct Value {
Number;
};
struct Point {
int x;
int y;
};
struct Circle {
Point;
int radius;
};
Point
mirror(Point p)
{
return (Point) {-1 * p.x, -1 * p.y};
}
void
main(int, char*[])
{
Point p₀ = {.x = 3, .y = -1};
Circle c = {p₀, 12};
Point p₁ = c.Point;
print("p₀ = (%d,%d)\nradius = %d\n", c.x, c.y, c.radius);
print("p₁ = (%d,%d)\n", p₁.x, p₁.y);
Point p₂ = mirror((Point){c.x, c.y});
print("p₂ = (%d,%d)\n", p₂.x, p₂.y);
Value v = {π};
print("value = %f\nd = %p\nf = %p\nl = %p\n",
v.dval, &v.dval, &v.fval, &v.lval);
exits(nil);
}
Output
p₀ = (3,-1)
radius = 12
p₁ = (3,-1)
p₂ = (-3,1)
value = 3.140000
d = 7fffffffeed0
f = 7fffffffeed0
l = 7fffffffeed0
Limbo
Nope.
Go
package main
import (
"fmt"
)
type Point struct {
x int
y int
}
type Circle struct {
Point
radius uint
}
func mirror(p Point) Point {
return Point{-1*p.x, -1*p.y}
}
func (p *Point) mirror() {
p.x *= -1
p.y *= -1
}
func main() {
p := Point{x: 3, y: -1}
c := Circle{p, 12}
p2 := c
fmt.Println(p)
fmt.Println(c)
fmt.Println(p2)
p3 := mirror(Point{c.x, c.y})
fmt.Println(p3)
fmt.Println(c.Point, c.Point.x, c.Point.y)
c.mirror()
fmt.Println(c)
}
Output
{3 -1}
{{3 -1} 12}
{{3 -1} 12}
{-3 1}
{3 -1} 3 -1
{{-3 1} 12}
Multiple returns
C in particular is an offender of not enabling returns without a named type (struct
) or allocated memory in place to facilitate multiple values being returned to a caller.
Go and some of the languages that influence it attempt to solve this issue while maintaining fairly C-like semantics.
Newsqueak
Nope.
Alef
This example is derived from the tuple example found in the “Alef User’s Guide”. 3
#include <alef.h>
tuple(int, byte*, byte*)
foo()
{
return (7, "fußbol", "skål");
}
void
main(void)
{
int n;
byte *game, *cheer;
(n, game, cheer) = foo();
print("(%d %s %s)\n", n, game, cheer);
}
Output
(7 fußbol skål)
Plan9 C
Nope.
Limbo
implement MultRet;
include "sys.m";
sys: Sys;
include "draw.m";
MultRet: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
swap(a, b: int): (int, int) {
return (b, a);
}
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
(x, y) := swap(3, 7);
sys->print("3, 7 → %d, %d\n", x, y);
exit;
}
Output
3, 7 → 7, 3
Go
package main
import (
"fmt"
)
func foo()(n int, s string) {
n = 4
s = "☺"
return
}
func bar() (a, b, c string) {
return "a", "b", "c"
}
func main() {
n, s := foo()
a, b, c := bar()
fmt.Println(n, s, a, b, c)
}
Output
4 ☺ a b c
Break and continue overloading
Go and some of its predecessors support some form of overloading on top of break/continue to allow more precise flow control in nested iterative/branching logic.
Newsqueak
Nope.
Alef
This example is derived from an example in the “Alef User’s Guide”. 3
Note that there is no overloaded form of continue
.
#include <alef.h>
void
main(void)
{
chan(int)[1] dummy;
chan(int)[2] ch;
int a;
alloc ch, dummy;
dummy <-= 1;
ch <-= 3;
ch <-= 4;
while(?ch)
alt {
case a = <-ch;
print("got %d\n", a);
break 2;
case <- dummy;
print("dummy\n");
dummy <-= 1;
break;
}
}
Output
dummy
dummy
dummy
dummy
got 3
Plan9 C
Nope.
Limbo
implement BreakContinueTag;
include "sys.m";
sys: Sys;
include "draw.m";
BreakContinueTag: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
i := 0;
loop:
for(;;){
i++;
case i {
11 =>
break loop;
* =>
if(i % 2 == 0)
continue loop;
}
sys->print("%d\n", i);
}
exit;
}
Output
1
3
5
7
9
Go
package main
import (
"fmt"
)
func main() {
i := 0
loop:
for {
i++
switch {
case i % 2 == 0:
continue loop
case i > 10:
break loop
}
fmt.Println(i)
}
}
Output
1
3
5
7
9
Lists / iteration
This section demonstrates syntactic properties regarding lists or list-like iterative logic.
Newsqueak
Nope.
Alef
This example is derived from the iterator example in the “Alef User’s Guide”. 3
#include <alef.h>
void
main(void)
{
int i, arr[10];
arr[i = 0::10] = i * i;
print("%d ", arr[0::10]);
print("\n");
}
Output
0 1 4 9 16 25 36 49 64 81
Plan9 C
Nope.
Limbo
This is a modified version of the ‘Lists’ example in LimboByExample. 7
implement Lists;
include "sys.m";
sys: Sys;
print: import sys;
include "draw.m";
Lists: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
names: list of string;
ages: list of int;
persons: list of (string, int);
print("Lens: %d, %d, %d\n", len names, len ages, len persons);
names = "Spike" :: names;
ages = 27 :: ages;
names = "Ed" :: "Jet" :: names;
ages = 13 :: 36 :: ages;
print("Lens: %d, %d, %d\n", len names, len ages, len persons);
n := names;
a := ages;
while(n != nil && a != nil) {
persons = (hd n, hd a) :: persons;
n = tl n;
a = tl a;
}
print("Persons:\n");
for(; persons != nil; persons = tl persons) {
(name, age) := hd persons;
print("\t%s: %d\n", name, age);
}
print("Tmp lens: %d, %d\n", len n, len a);
print("Lens: %d, %d, %d\n", len names, len ages, len persons);
exit;
}
Output
Lens: 0, 0, 0
Lens: 3, 3, 0
Persons:
Spike: 27
Jet: 36
Ed: 13
Tmp lens: 0, 0
Lens: 3, 3, 0
Go
package main
import (
"fmt"
)
func main() {
nums := make([]int, 0, 10)
fmt.Printf("Length = %d\nCapacity = %d\n", len(nums), cap(nums))
nums = append(nums, 1)
nums = append(nums, 2, 3, 4)
for i, n := range nums {
fmt.Printf("%d: %d\n", i, n)
}
fmt.Printf("Length = %d\nCapacity = %d\n", len(nums), cap(nums))
}
Output
Length = 0
Capacity = 10
0: 1
1: 2
2: 3
3: 4
Length = 4
Capacity = 10
Modules / packages / separable compilation
The idea of separating source across files is fairly universal in modern programming languages. The semantics of this process is demonstrated below for Go and some of its predecessors.
Newsqueak
Newsqueak can include files as text. This text can be assigned to a value or otherwise is inserted into the calling file as text and potentially interpreted as source.
include "util.nq";
print("Hello ");
smiley();
smiley := prog() {
smile := include "smile";
print(smile);
};
;
"☺"
Output
Hello
☺
Alef
Alef does not differ significantly from Plan9 C and this example is omitted.
Alef uses headers on its own, but does not allow the inclusion of C header files. 3
Related reading: alef(1)
Plan9 C
There are several compiler features which show themselves in the header: #pragma src
and #pragma lib
. Further reading on these can be found in the ‘Plan 9 C Compilers’ paper. 6
Note that these #pragma
directives typically are found with full system paths provided.
#include <u.h>
#include <libc.h>
#include "./libutil/util.h"
void
main(int, char*[])
{
print("Hello ");
smiley();
exits(nil);
}
#pragma src "./libutil"
#pragma lib "./libutil/libutil.a"
void smiley(void);
#include <u.h>
#include <libc.h>
#include "util.h"
void
smiley(void)
{
print("☺\n");
}
</$objtype/mkfile
BIN = ./
TARG = modules-example
OFILES = main.$O
CFLAGS = $CFLAGS -I ./libutil
</sys/src/cmd/mkone
</$objtype/mkfile
LIB = ./libutil.a
HFILES = util.h
OFILES = util.$O
</sys/src/cmd/mklib
Output
For this example, to build and run from 9front you’ll use mk(1):
tenshi% lc
libutil/ main.c mkfile
tenshi% cd libutil
tenshi% mk
./libutil.a doesn't exist: assuming it will be an archive
6c -FTVw util.c
ar vu ./libutil.a util.6
ar: creating ./libutil.a
a - util.6
tenshi% cd ..
tenshi% mk
6c -FTVw -I ./libutil main.c
6l -o 6.out main.6
tenshi% 6.out
Hello ☺
tenshi%
Limbo
This is a slightly reduced version of the ‘Modules’ example in LimboByExample. 7
implement Modules;
include "sys.m";
include "draw.m";
# Note the lack of `include "persons.m";`
include "towns.m";
sys: Sys;
print: import sys;
persons: Persons;
Person: import persons;
towns: Towns;
Town: import towns;
Modules: module {
init: fn(nil: ref Draw->Context, nil: list of string);
};
init(nil: ref Draw->Context, nil: list of string) {
sys = load Sys Sys->PATH;
persons = load Persons "./persons.dis";
towns = load Towns "./towns.dis";
persons->init();
towns->init();
print("%d\n", persons->getpop());
print("%d\n", towns->persons->getpop());
p := persons->mkperson();
p.name = "Spike";
p.age = 27;
print("%d\n", persons->getpop());
print("%d\n", towns->persons->getpop());
t := towns->mktown();
t.pop = array[] of {p, ref Person(13, "Ed")};
t.name = "Mars";
print("%s\n", t.stringify());
exit;
}
implement Persons;
include "persons.m";
population: int;
init() {
population = 0;
}
getpop(): int {
return population;
}
mkperson(): ref Person {
population++;
return ref Person;
}
Person.stringify(p: self ref Person): string {
return p.name;
}
include "persons.m";
Towns: module {
init: fn();
mktown: fn(): ref Town;
persons: Persons;
Town: adt {
pop: array of ref Persons->Person;
name: string;
stringify: fn(t: self ref Town): string;
};
};
implement Towns;
include "towns.m";
init() {
persons = load Persons "./persons.dis";
}
mktown(): ref Town {
return ref Town;
}
Town.stringify(t: self ref Town): string {
Person: import persons;
s := "Name: " + t.name + "\nSize: " + string len t.pop + "\nMembers:";
for(i := 0; i < len t.pop; i++)
s += "\n→ " + t.pop[i].stringify();
return s;
}
</mkconfig
DISBIN = ./
TARG=\
modules.dis\
persons.dis\
towns.dis\
</mkfiles/mkdis
Output
For this example, to build and run from Inferno you’ll use mk(1):
; mk
limbo -I/module -gw modules.b
limbo -I/module -gw persons.b
limbo -I/module -gw towns.b
; modules
0
0
1
0
Name: Mars
Size: 2
Members:
→ Spike
→ Ed
;
Go
This example just shows including a local package.
Modern Go recommends using the module system 8 and most public Go projects will have import paths in forms such as "github.com/foo/bar"
.
package main
import (
util "./util"
"fmt"
)
func main() {
fmt.Print("Hello ")
util.Smile()
}
package util
import (
"fmt"
)
func Smile() {
fmt.Println("☺")
}
Output
Hello ☺