Scarlet Industries

A bigger program

A web server in front of a stock room.

The program

The stock room is a process that owns the stock. It waits for a message, answers it, and calls itself with the new stock to wait for the next one. The server runs each connection in a process of its own, and those processes never touch the stock directly. They send the stock room a message and wait for its reply.

import scarlet/binary
import scarlet/http
import scarlet/http.{Get, Post, Request, Response}
import scarlet/map
import scarlet/process
import scarlet/process.{Subject}

type Ask {
	Count(name String, reply Subject(Int))
	Buy(name String, reply Subject(Result(Int, Nil)))
}

fn stockroom(inbox Subject(Ask), stock Map(String, Int)) Nil {
	match process.receive(inbox) {
		Count(name, reply) -> {
			process.send(reply, map.get(stock, name) or 0)
			stockroom(inbox, stock)
		}
		Buy(name, reply) -> match map.get(stock, name) or 0 {
			0 -> {
				process.send(reply, Err(Nil))
				stockroom(inbox, stock)
			}
			n -> {
				process.send(reply, Ok(n - 1))
				stockroom(inbox, map.set(stock, name, n - 1))
			}
		}
	}
}

fn open(stock Map(String, Int)) Subject(Ask) {
	boot = process.subject()
	_ = process.spawn(fn() {
		inbox = process.subject()
		process.send(boot, inbox)
		stockroom(inbox, stock)
	})
	process.receive(boot)
}

fn ask(room Subject(Ask), message fn(Subject(a)) Ask) a {
	reply = process.subject()
	process.send(room, message(reply))
	process.receive(reply)
}

fn route(room Subject(Ask), req Request) Response {
	match (req.method, http.path(req)) {
		(Get, <<'/stock/', name:binary>>) -> {
			item = binary.to_string(name) or ''
			n = ask(room, fn(reply) Count(item, reply))
			http.text('${n} ${item} left')
		}
		(Post, <<'/buy/', name:binary>>) -> {
			item = binary.to_string(name) or ''
			match ask(room, fn(reply) Buy(item, reply)) {
				Ok(n) -> http.text('sold, ${n} ${item} left')
				Err(Nil) -> http.text('${item} is sold out')
			}
		}
		_ -> http.not_found()
	}
}

pub fn main() {
	room = open(map.from_list([('tea', 2), ('jam', 0)]))
	match http.serve('0.0.0.0', 8080, fn(req) route(room, req)) {
		Ok(_) -> println('listening on http://localhost:8080')
		Err(e) -> println('could not listen: ${e}')
	}
}

Run it

scarlet run stock_server.scrl

Then, from another terminal:

$ curl localhost:8080/stock/tea
2 tea left
$ curl -X POST localhost:8080/buy/tea
sold, 1 tea left
$ curl -X POST localhost:8080/buy/tea
sold, 0 tea left
$ curl -X POST localhost:8080/buy/tea
tea is sold out