Website | Source

Any chunk of the language can be replaced with a hash. These chunks are called "scraps". Scraps are stored/cached/named/indexed in global distributed "scrapyards".

Scrapscript rejects traditional package-management. Instead, "scrapyards" combine features from Smalltalk, Hackage, IPFS, GitHub, and StackOverflow. This new paradigm empowers devs to safely collaborate in live environments.

Every scrap carries its own immutable dependencies.

The language itself forms merkle trees; VCS tools like git are optional. Every expression is independently version-controlled through the global namespace.

Every expression in the ecosystem can be independently spliced and "time-travelled".

To avoid giant updates, scrapscript tooling can incrementally upgrade your code. Any chunk of code can be pinned independently to upgrade at a later time.

Easily inspect code regressions. Execute code with dependencies from a specific point in time.

Scrapscript is a full programming language designed to be sent over the wire with type-safety in mind.

Implementations

Guide

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:

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

    But you cannot change the type of a record:

    { ..g, a = "y" }
    ; 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:

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

    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:hello@example.com">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 connie.fun@yahoo.com \
      | 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").


    Tags: language   functional   hashable  

    Last modified 13 August 2025