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:

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:

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

tok.l

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

tok.c

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

tok.b

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

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

co.nq

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

co.l

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

co.c

#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

co.b

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

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

chans.nq

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

chans.l

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

chans.c

#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

chans.b

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

chans.go

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

select.nq

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

select.l

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

select.c

#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

select.b

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

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

unnamed.l

#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

unnamed.c

#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

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

multret.l

#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

multret.b

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

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

bctag.l

#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

bctag.b

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

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

lists.l

#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

lists.b

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

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

main.nq

include "util.nq";

print("Hello ");
smiley();

util.nq

smiley := prog() {
	smile := include "smile";
	print(smile);
};

;

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.

main.c

#include <u.h>
#include <libc.h>
#include "./libutil/util.h"

void
main(int, char*[])
{
	print("Hello ");
	smiley();

	exits(nil);
}

util.h

#pragma src "./libutil"
#pragma lib "./libutil/libutil.a"

void smiley(void);

util.c

#include <u.h>
#include <libc.h>
#include "util.h"

void
smiley(void)
{
	print("☺\n");
}

mkfile (main)

</$objtype/mkfile

BIN = ./

TARG = modules-example

OFILES = main.$O

CFLAGS = $CFLAGS -I ./libutil

</sys/src/cmd/mkone

mkfile (libutil)

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

modules.b

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

persons.b

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

towns.m

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

towns.b

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

mkfile

</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".

main.go

package main

import (
util	"./util"
	"fmt"
)

func main() {
	fmt.Print("Hello ")
	util.Smile()
}

util.go

package util

import (
	"fmt"
)

func Smile() {
	fmt.Println("☺")
}

Output

Hello ☺

References


  1. https://github.com/henesy/awesome-inferno ↩︎

  2. http://usingcsp.com/cspbook.pdf ↩︎

  3. http://doc.cat-v.org/plan_9/2nd_edition/papers/alef/ug ↩︎

  4. http://man.cat-v.org/9front/2/thread ↩︎

  5. https://gcc.gnu.org/onlinedocs/gcc-4.6.0/gcc/Unnamed-Fields.html#Unnamed-Fields ↩︎

  6. http://doc.cat-v.org/plan_9/4th_edition/papers/compiler ↩︎

  7. https://github.com/henesy/limbobyexample ↩︎

  8. https://blog.golang.org/using-go-modules ↩︎