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:
- Wishful Thinking: Write your program as if you had a magic wand, and then recursively implement missing logic. This practice (1) naturally prevents YAGNI mistakes, (2) produces more intuitive API boundaries in large programs, and (3) makes code quickly testable with placeholder logic.
- Next-Gen Autocomplete: With most programming languages, Github Copilot surveys the elements of the workspace to guess what the author wants to make. Unfortunately, the boilerplate code at the beginning is annoying to type and difficult to extrapolate. With scrapscript, AI completion tools can take your goal and generate its boilerplate. Furthermore, AI tools can use contextual type information to make even better guesses.
- Interactive Code: No more “variable not found” errors in the REPL! Scrapscript can recursively add additional where-clauses until all variables in your program are defined.
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.