Welcome to Wipple!

Wipple is a powerful, expressive programming language that's designed for DSLs. It's natural to read, write and learn. Wipple embraces traits, macros, custom operators, and interpreter plugins to let you shape the language into anything you want.

Wipple is implemented in Swift, and is under heavy development — it's meant primarily for me (Wilson Gramer) to learn how programming languages work, so it probably shouldn't be used in production (yet!). Contributions are welcome!

What does "Wipple" mean?

"Wipple" is the pronunciation of "WPL", which stands for "Wilson's Programming Language".

Installing Wipple

Open your terminal and paste in this command:

curl -fsSL https://raw.githubusercontent.com/wipplelang/wipple/main/install.sh | sh

Grab a cup of hot chocolate — it may take a while! ☕

Wipple currently supports macOS and Linux. If you want to run it on Windows, you can use WSL.

The Wipple Playground

In the Wipple Playground, you can type in code line-by-line, and the output will be displayed immediately. The Playground is useful for learning how Wipple works, or just to experiment, without having to write an entire program. Let's try it out now!

Opening the Playground

Go to your terminal and type:

$ wipple playground

When you see a $ in a code example, that means to type the text in your terminal. When you see a >, that means to type the text in the Playground. Don't type the $ or >, just type what comes after it.

Now you can write some code:

> 1 + 2
3

You just wrote your first Wipple program — a simple calculator! Try typing in some more complicated math expressions and see how Wipple solves them for you. We'll learn what makes all of this work later on.

When you're ready to exit, type:

> stop!

Wipple overview

This overview is meant for people who already have experience with other programming languages — it only introduces concepts new to Wipple and is more technical than the beginner guide. If you're new to programming, Wipple is a great language to start with! Check out the beginner guide here.

Welcome to Wipple! Wipple is a powerful, expressive programming language that's designed to implement DSLs. Wipple is dynamically typed and uses a trait-based type system (explained below). Wipple embraces macros, custom operators, and plugins to let you shape the language into anything you want. Here's a brief overview of the main things you need to be productive writing Wipple code.

Architecture

Wipple is interpreted and uses a highly modular plugin system. The language is implemented in five layers:

  • Packages group Wipple code together and allow you to mix in code from other files. Packages are managed through the Wipple package manager.
  • Plugins are written in Swift and allow direct access to the interpreter, environment and related functionality.
  • Hooks allow your plugin to observe events that occur during evaluation and replace values with others. Core functionality, like name resolution and list evaluation, is implemented using hooks!
  • Traits describe how values can be used. You can create your own value types and let them interface with existing code by conforming them to the necessary traits.
  • Values represent code and data at a primitive level.

Wipple's plugin API is simple to use, and makes integrating other languages into Wipple a breeze. Check out the plugin documentation for more information!

Constructs

Wipple has a set of builtin constructs similar to Lisp-derived languages like Clojure. Value types that have a source code representation are implemented directly in the interpreter, and all other value types (including functions, macros and booleans) are implemented through Wipple plugins.

Names

Names are the simplest construct and represent themselves. While many languages use names as identifiers for variables and types, names in Wipple can also be passed around as data (just like all Wipple code!). Names can contain any character except spaces and punctuation used for other constructs.

Here are a few examples of valid Wipple names:

hello-world
favorite-color
:
->
defined?
random!
24-hours-ago

By convention, names in Wipple use dashes to separate words — in Java, for example, my name would become myName, but in Wipple it would become my-name. Names that represent booleans or functions evaluating to booleans end in ?, and names referring to values that are "impure" or "unsafe" end in !. In general, names consisting entirely of punctuation represent operators.

Numbers and strings

Wipple represents numbers as integers and floats. Integers are written like 42, and floats are written like 3.14. (note that you need digits on both sides of the decimal point — .5 is a name while 0.5 is a float).

Strings are represented with double quotes, like "Hello, world!". Strings support all unicode characters and the same set of escape characters as JavaScript/JSON.

Integers, floats and strings all evaluate to themselves.

Lists, bracket lists and structs

Lists represent a list of values and can either stand on their own or be evaluated as a set of function calls. Lists are written like (a b c), which would be evaluated as follows:

  • Evaluate a (a function).
  • Call a with b.
  • Call the result (a function) with c.

If a list contains a single value, it's the same as the value itself — that is, (a) is evaluated the same as a. The empty list (()) evaluates to itself and can also be written as nil.

Bracket lists are a bit of syntax sugar to make writing lists easier in some cases. Bracket lists evaluate to lists with each item evaluated — if a was defined as foo and b was defined as bar, then [a b] would evaluate to (foo bar).

Structs represent a list of value pairs, where the first value in the pair is the "key". Structs are written like (| name "Bob" age 42 |), which evaluates to a struct associating "Bob" with name and 42 with age. You can use the . operator to access the values by their keys, like (| name "Bob" age 42 |) . name (evaluates to "Bob").

Quoted values

A quoted value is a value whose evaluation is delayed. Quoted values are written like 'foo, which evaluates to foo. Quoted values are often used to represent lists as data — eg. '(a b c) is a list containing a, b and c, and is not evaluated as a series of function calls.

Blocks

A block consists of statements and creates its own scope. Blocks are evaluated by evaluating each statement as a list and returning the result of the last. For example,

{
    foo : 3
    bar : 4
    foo + bar
}

evaluates to 7. The definitions for foo and bar are restricted to the block scope and are unavailable after the block is evaluated.

Wipple files are implicitly blocks (ie. braces are automatically inferred and multiple statements can be used at the top level).

Booleans

Booleans have no direct source representation, but are defined as true and false names.

Functions and macros

Functions and macros have no direct source representation, but can be created using the fn and macro macros, respectively, or the -> and => operators. Functions and macros only accept a single parameter — to use multiple inputs, you can nest them. For example, fn a (fn b a) (or a -> b -> a) evaluates to a function that accepts two values and returns the first.

Functions are closures — that is, they capture the environment in which they are defined. Macros, on the other hand, are evaluated in the environment in which they are called. This means that macros should be pure, because they can only rely on their parameters (and globals) to exist. It's a good idea to start with a macro, and then use a function if you need to capture other values in scope.

Traits

Traits are a powerful feature of Wipple, but they work a bit differently than types in other languages. Essentially, a trait is a construct that provides information on how a value can be used. If a value satisfies a trait, it is guaranteeing that it can be used in the way the trait defines.

Wipple traits come in two variants — abstract traits and concrete traits. Concrete traits can be explicitly added to a value using the :: operator. Abstract traits cannot be added, only derived via conformances (see below) and checked against using the is? operator.

For example, one trait Wipple provides out of the box is Display, which is used by format and write!. To satisfy the Display trait, you provide a string representing your value:

person : name -> {
    person : (|
        name name
    |)

    person :: Display (format "Hi! I'm _" name)

    person
}

bob : person "Bob"

write! bob -- "Hi! I'm Bob"

Currently, the person function accepts any value for name. We can restrict the input to strings only by checking if it satisfies the String trait:

  person : name -> {
+     -- if name isn't a string, then the function will fail with an error
+     ensure! (name is? String) "name must be a string"

      person : (|
          name name
  ...

You can also create your own traits by composing the builtin ones. For example, we can make a Greet trait that describes how to add a greeting to a provided string, and a Play trait that describes how to play a sound:

Greet : Data 'greet

-- Use the find-Data function to find the Data trait with the 'greet' key in the
-- provided value
find-Greet : find-Data 'greet

greet-using! : greeter -> string -> {
    ensure (string is? String) "must provide string to greet"

    -- Will fail with error if 'greeter' doesn't satisfy Greet
    greet-fn : greeter | find-Greet

    -- Call the greeter function with the provided string
    greeting : greet-fn string

    write! greeting
}


Play : Data 'play
find-Play : find-Data 'play

play! : value -> {
    play! : value | find-Play
    play!
}


hola : nil
hola :: Greet (format "Hola, _!")


Doorbell : Unique!

-- see below for more information about conformances
Doorbell ::= doorbell ->
    Play (Get {
        use audio -- some dependency
        play-sound! (doorbell . sound)
    })

ring! : doorbell -> {
    ensure (doorbell is? Doorbell) "expected doorbell"
    play! doorbell
}

doorbell : sound -> {
    doorbell : (|
        name "Doorbell created with Wipple"
        sound sound
    |)

    doorbell :: Doorbell

    doorbell :: Display (format "a doorbell that plays _ when rung" sound)

    doorbell :: Greet (format "Ding dong, _!")

    doorbell
}

front-porch : doorbell "sound.mp3"


-- Time to put it all together!

"Bob" | greet-using! hola -- "Hola, Bob!"

"Alice" | greet-using! front-porch -- "Ding dong, Alice!"

ring! front-porch -- plays sound.mp3

-- "My doorbell is a doorbell that plays sound.mp3 when rung"
write! (format "My doorbell is _" front-porch)

As you can see, traits are commonly used to provide "overloads" for functions that don't care about the type of the input, only a specific piece of functionality. In Wipple, a best practice is to put functionality over identity — that is, use traits for each piece of functionality instead of a specific trait to identify a value (unless if the function is coupled with the function that creates the desired input, like ring! above).

The key difference between Wipple traits and types in other languages is that traits belong to values, not references. This means that if you reassign a name, the traits are lost along with the previous value. If a static analyzer for Wipple becomes available in the future, it will need to look at the traits belonging to the values referred to by names, not the names themselves, and should allow a name to be reassigned to a value satisfying different traits than the previous value.

Trait conformances

Work in progress!

Goals, non-goals and future directions

Goals

  • Learn how parsers and program interpreters work
  • Work similarly to Lisp, but with whitespace and operator precedence
  • Enable concise and readable code
  • Usable for small projects
  • Good documentation
  • "Repple" (Wipple REPL)
  • Modular — supports plugins and hooks
  • Package system and package manager
  • "Batteries not included" — small set of builtins

Non-goals

  • Compilation
  • Static analysis/typechecking
  • Efficient, optimized or otherwise "production-ready"
  • Parser-level syntax sugar (most can be implemented via macros)

Future directions

  • "Try it online" sandbox