guide news chat

guide

introduction

Scrapscript is a tiny programming language and messaging notation. Use it to make small programs and share them with others! Our community celebrates connectedness, correctness, and compression.

Scrapscript is still in early development! Throughout this guide, you’ll see “proposals”. If you have any opinions, feel free to post in Discourse or email Taylor.

numbers

Scrapscript offers conventional integers and floats.

1 + 1
2
1.0 + 1.0
2.0

You cannot mix-and-match integers with floats.

1 + 1.0
type error

Use to-float, round, ceil, and floor to convert between integers and floats.

1.0 + to-float 1
2.0

text

In scrapscript, text is called “text” instead of “strings”.

"hello" ++ " " ++ "world"
"hello world"

Text preserves newlines.

"hello" ++ "
" ++ "world"
"hello
world"

Emojis are text too!

"🐸"
"🐸"

There’s nice syntax for embedding text inside text.

"hello` "🐸" `frog"
"hello🐸frog"

bytes

Encode arbitrary data in scrapscript using Base64. This is helpful for human manipulation and debugging, but don’t worry – we can send it over the wire as raw bytes using flat scraps.

bytes/to-utf8-text ~~aGVsbG8gd29ybGQ=
"hello world"

You can also express individual bytes in hexadecimal.

bytes/to-utf8-text <| ~~aGVsbG8gd29ybGQ= +< ~21
"hello world!"

hole

Thirty spokes share the hub of a wheel;
yet it is its center that makes it useful.

You can mould clay into a vessel;
yet, it is its emptiness that makes it useful.

Cut doors and windows from the walls of a house;
but the ultimate use of the house will depend on that part where nothing exists.

Therefore, something is shaped into what is; but its usefulness comes from what is not.

Tao Te Ching

You may see this thing around: (). In scrapscript, we call it “hole” because it looks like a gaping hole.

()
()

Emptiness is what makes a hole useful!

variables

Compared to most other modern programming languages, scrapscript is written “backwards”.

You can read the following statement as “x where x equals 100”:

x . x = 100
100

You can use where-statements as expressions:

200 + (x . x = 150)
350

Scrapscript doesn’t nitpick over blankspace, so you can chain multiple clauses on different lines:

a + b + c
. a = 1
. b = 2
. c = 3
6

Writing “backwards” makes parsing slower in some cases, so why does scrapscript elect to make things slower and more confusing?

Scrapscript is written “backwards” for a few reasons:

lists

[1, 2, 3] +< 4
[1, 2, 3, 4]

Unlike JS and Python, everything in a list must have the same exact type.

["1", 1, 1.0]
type error

records

Sometimes you want to store heterogeneous data like a customer or an account.

rec::a
. rec = { a = 1, b = "x" }
1

You can use an existing record to “fill in” the values of a new record:

{ a = 2, c = ~FF, ..g }
. g = { a = 1, b = "x", c = ~00 }
{ a = 2, b = "x", c = ~FF }

But you cannot change the type of a record:

{ a = "y", ..g }
. g = { a = 1, b = "x" }
type error

operators

Scrapscript’s operators are mostly copied from Elm. Consider using Elm if you want to make delightful web experiences with a great community!

functions

f 1 2
. f = a -> b -> a + b
3

Use functions to pattern-match on inputs. The code will try each case sequentially until it finds a match. Don’t worry – the compiler will ensure that you don’t forget any cases!

Fun fact: this is the only method of control-flow in scrapscript!

f "b"
. f =
  | "a" -> 1
  | "b" -> 2
  | "c" -> 3
  |  x  -> 0
2

Remember that scrapscript doesn’t care about blankspace, so you can throw everything on one line:

f "b"
. f = | "a" -> 1 | "b" -> 2 | "c" -> 3 | x -> 0
2

Scrapscript’s functions are greedy – they grab code until they hit a . or matching ). Scrapscript doesn’t care about whitespace, so indentation won’t help you.

(f >> (x -> x) >> g) 7
. f = | 7 -> "cat"
      | 4 -> "dog"
      | _ -> "shark"
. g = | "cat" -> "kitten"
      | "dog" -> "puppy"
      |   a   -> "baby " ++ a
"kitten"

Unevaluated functions are values too!

(x -> x) (y -> y)
y -> y

You can match on ints, but you can’t match on floats:

| -1 -> "negative one"
|  0 -> "zero"
|  1 -> "one"
|  n -> "something else"
| ~00 -> ~00
| ~FF -> ~FF
|   n -> n

You can also match on text and bytes, along with their leading content:

| "hey" -> ""
| "hello " ++ name -> name
| _ -> ""
| ~~8J+QuA== ++ x -> x
| x -> x

There are many ways to match lists:

| [ x, y, z ] ++ tail -> ()
| first >+ second >+ tail -> ()
| [ x ] -> ()
| head >+ tail -> ()
| [] -> ()

You can match on records as long as you keep the argument types consistent:

| { a = 1, b = 2, c = 3, ..x } -> ()
| { a = 1, b = b         ..x } -> ()
| { a = 1, b = 2,            } -> ()
| {               c = c, ..x } -> ()

All destructuring is nestable:

| { a = [ 3, 4 ]
  , b = "howdy"
  }

  -> "howdy"

| { a = [ x ]
  , b = "hi " ++ name
  }

  -> "not right now, " ++ name

| _

  -> "..."

You can pattern match on functions of any arity:

| 1 -> 1 -> 1
| 1 -> 2 -> 3
| 3 -> 5 -> 8
| 5 -> 8 -> 12
| a -> b -> a + b

types

If you want to define multiple alternatives, use a custom type:

scoop::chocolate
. scoop :
  #vanilla
  #chocolate
  #strawberry

This is how true and false are impemented in scrapscript:

#true #false

Alternatives can “carry” their own data, if defined that way:

c::radius 4
. c : #radius int
(#radius int)::radius 4

If you want to use alternatives in multiple contexts, you can make types generic with functions:

point::3d 1.0 "A" ~2B
. point : x => y => z =>
  #2d { x : x, y : y        }
  #3d { x : x, y : y, z : z }

Functions have types too!

typ::fun (n -> x * 2)
. typ : #fun (int -> int)

Use pattern matching to grab the contents of each alternative:

hand::left 5 |>
  | #l n -> n * 2
  | #r n -> n * 3
. hand :
  #l int
  #r int
10

If it gets too confusing, break things into local types:

t
. t :
  #a a
  #b int
  #c byte
. a :
  #x
  #y
  #z

You can also nest and unnest in your matches:

| #a { x = #l 0 } -> ()
| #a { x =  _   } -> ()
| #b { x = #l 1 } -> ()
| #b { x =  _   } -> ()
| #c { x = #l 2 } -> ()
| #c { x =  -   } -> ()
| _               -> ()

CLI

Scrapscript is still in active development. The CLI is not available yet, but here’s a preview of what we’re building. Feel free to complain with the community.

To evaluate scrapscript, pipe it into scrap eval.

$ echo '1 + 2' | scrap eval
3
$ scrap eval < hello-world.ss
"hello world"

You can manipulate input with scrap eval apply:

$ echo '0' \
  | scrap eval apply 'n -> n + 1' \
  | scrap eval apply 'n -> n + 1' \
  | scrap eval apply 'n -> n + 1'
3

rocks

A “rock” is a “rock-bottom” unit of scrapscript. These are the building blocks out of which everything in scrapscript is implemented.

$$add 1 2
3
(#a $$int)::a 0
(#a $$int)::a 0

Rocks make scrapscript portable. By implementing the limited number of scrapscript rocks in your host systems, you get the rest of the scrapscript ecosystem for free!

A complete list of rocks is coming soon!

scrapyards

Note: Scrapscript’s tooling obviates the need for direct interaction with scrapyards. This section is mostly for fun and curiosity.

In scrapscript, any expression can be replaced with a cryptographic hash:

fib 31
. fib =
  | 0 -> 1
  | 1 -> 1
  | n -> fib (n - 1) + fib (n - 2)
1346269
fib 31
. fib = $sha1'e4caecf0d6f84d4ad72e228adce6c2b46a0328f9
1346269

A scrapyard is a key-value store of hashes and scraps. The storage medium doesn’t matter – scrapyards only need to adhere to either the filesystem API or the HTTP API to be accessed by the compiler.

The scrapscript team hosts a giant public scrapyard at yard.scrap.land for everybody, but you can also configure your CLI to read from additional scrapyards:

$ cat ~/.config/scrapscript/config.ss \
  | scrap eval apply \
    'config -> { ..config, yards = [ "https://yard.scrap.land", "/var/my-scrapyard" ] }' \
  > ~/.config/scrapscript/config.ss

You can create a local scrapyard and upload scraps to it:

$ scrap yard init /var/my-scrapyard
$ echo '123' | scrap flat | scrap yard push /var/my-scrapyard
$sha1'3efce6ae1ebf7fef7c7bdd8c270d76da5b079438

You can also make a local scrapyard accessible via a network:

$ scrap yard listen /var/my-scrapyard :8080
$ curl http://localhost:8080/#sha1$3efce6ae1ebf7fef7c7bdd8c270d76da5b079438
123

scrap map

Nobody wants ugly hashes in their elegant programs. Civilized folk use names for things!

If a scrapyard is a giant pile of scraps, a scrap map gives each hash a name and version.

$ cat ~/.config/scrapscript/config.ss \
  | scrap eval apply \
    'config -> { ..config, maps = [ "https://map.scrap.land", "/var/my-scrapmap" ] }' \
  > ~/.config/scrapscript/config.ss

No need to import or require anything – if a variable is not found in your program, it searches through available maps:

connie2036/fib
fib
. fib =
  | 0 -> 1
  | 1 -> 1
  | n -> fib (n - 1) + fib (n - 2)

Be default, scrapscript uses the latest version of scraps, but you can specify previous versions:

pair connie2036/planets@0 connie2036/planets@1
pair
[ "Pluto", "Neptune", "Uranus", "Saturn", "Jupiter", "Mars", "Earth", "Venus", "Mercury" ]
[ "Neptune", "Uranus", "Saturn", "Jupiter", "Mars", "Earth", "Venus", "Mercury" ]

You can also use the time-travel interpreter to choose versions based on time:

$ echo 'list/first connie2036/planets' \
  | scrap eval --t="2005-01-01"
just "Pluto"
$ echo 'list/first connie2036/planets' \
  | scrap eval --t="2006-12-31"
just "Neptune"

You can add scraps to maps and yards:

$ echo '() -> "hello"' \
  | scrap flat  \
  | scrap map commit connie2036/greet \
  | scrap pass sign ~/.ssh/id_rsa \
  | scrap map push /var/my-scrapmap
connie2036/greet@44

Signatures needn’t be enforced for local maps, but they’re essential for preventing tomfoolery in public maps!

platforms

The scrap eval command is useful for manipulating data, but it doesn’t permit any real interaction with the “outside” world. Scrapscript instead uses “managed effects” (like Elm and Roc) to maintain a small yet extensible surface area.

In other words, scrapscript acts as an algebra for performant “platforms”. Use platforms to control robots, interact with filesystems, create user interfaces, etc.

Here’s what a simple web-server platform might look like:

| "/home" -> q -> res::success <| "<p>howdy " ++ get-name q ++ "</p>"
| "/contact" -> _ -> res::success "<a href="mailto:[email protected]">email</a>"
| _ -> _ -> res::notfound "<p>not found</p>"
. res = #success text #notfound text
. get-name = maybe/default "partner" << dict/get "name"
$ scrap platform connie2036/server < my-server.ss

The platform SDK is not finished yet, but we aim to make effortless building materials in languages like Rust and Go.

Stay tuned!

flat scraps

Use scrap flat to make a minimized version of the scrap:

$ echo '3 * 5' | scrap eval | scrap flat | hexdump -C
0F

Flat scraps are compressed into a compact byte format like msgpack.

More details coming soon!

sending scraps

Send flat scraps via HTTP using the application/scrap MIME type:

$ brew install httpie
$ echo '"hello"' \
  | scrap eval \
  | scrap flat \
  | http -b POST connie2036.com/echo \
      Content-Type: application/scrap \
      Accept: application/scrapscript
"hello"

If you’re using a platform that supports it (e.g. remote), you can also use do this:

$ echo '"hello"' \
  | scrap platform remote apply '@connie2036/echo'
(result text remote/error)::ok "hello"

If a platform doesn’t know what to do with the remote type, it’ll just treat it like any other type:

$ echo '"hello"' | scrap eval apply '@connie2036/echo'
@connie2036/echo "hello"

In the following example, connie2036/echo defines a contract between the systems. Note that the server expects text as its request and response types. If the server sends a different response type than expected, the scrapscript runtime will fail gracefully on your behalf.

connie2036/echo
{ location = remote/http-post "https://connie2036.com/echo"
, request = scrap/type:text
, response = scrap/type:text
}
$ echo '123567' | scrap platform remote apply '@connie2036/echo'
remote type error

In these examples, @connie2036/echo is syntactic sugar for building (remote text text):remote.

$ echo 'remote/fetch ((remote text text)::remote connie2036/echo "goodbye")' | scrap platform remote
(result text remote/error)::ok "goodbye"

scrap passes

Scrap maps are public real-estate. Without authentication, bad actors could wreak all kinds of havoc in our precious namespaces.

To claim a chunk of the namespace, submit a claim to the map maintainers.

$ scrap map claim connie2036 [email protected] \
  | scrap pass sign ~/.ssh/id_rsa \
  | scrap map push https://map.scrap.land

If your claim is accepted, you’ll be able to push arbitrary scraps to your namespace in the map.

$ echo '() -> "hello"' \
  | scrap flat  \
  | scrap map commit connie2036/greet \
  | scrap pass sign ~/.ssh/id_rsa \
  | scrap map push https://map.scrap.land

Because all the scraps are cryptographically signed, anybody can verify authorship even if the scrap map database is somehow compromised.

To prevent squatting and mooching, map maintainers will likely require payment for “premium” namespaces and/or storing large amounts of data.

scrapbooks

$ scrap book init /var/my-scrapbook

Scrapbooks store and sync scraps. Use them to manage personal snippets and collaborate.

Coming from git, you can treat a scrapbook as a living development branch.

As a bonus, scrapbooks hook into text editors to provide a live “Google Docs” experience for teams.

smel shell

You may have noticed lots of Bash in this guide. Well, Bash is crusty, and a scrapscript-based shell called “smel shell” is in the early design phases.

Stay tuned!

scrawl

Scrapscript was designed from the ground-up to have a smalltalk/hypercard-like editor/browser experience. There’s a huge opportunity to change your text-editing tools based on platform (e.g. “Arduino mode”, “parser mode”, “VR-dev mode”).

We’re pretty early in ideation, so post in Discourse if you have any cool ideas!

the cheap web

Do you miss the good ol’ days of geocities and flash? Join the cheap web!

Stay tuned for new scrapscript tools for creating and sharing small web experiences.