Introduction

Welcome to Wipple! Wipple is a programming language for building DSLs that's natural to read, write and learn.

You can use this documentation to learn how to write Wipple code, how to express concepts from other programming languages in Wipple, and how to manage your own Wipple projects.

Philosophy

I (Wilson Gramer) initially started building Wipple to learn how programming languages work. Every language is built to satisfy their creator's vision of a "perfect" way of expressing code — and Wipple is no exception! — but I noticed that often this perfection is tarnished by reasons of practicality or history. Therefore, Wipple's design philosophy is no magic: builtin constructs are just regular functions and have no special syntax, making it easy to learn through experimentation.

I've designed Wipple under the same assumption as Bel — "if computers were as powerful as we wanted, what would programming languages look like?" Wipple code is about describing your ideas more than it is about how those ideas are actually executed or represented in memory by the computer. Even constructs like booleans are represented using variants (sum types) in Wipple, because truth and falsehood are axioms — even if computers store them as a 1 or 0, logically they are defined in terms of their relationships to other values.

Wipple also takes a more practical approach than purely functional languages like Haskell or Idris — even if describing side effects in terms of some kind of IO construct is more "correct", I think it's a lot harder to teach. Mutation is allowed, for example, but Wipple will guide you toward using functional concepts most of the time instead. And even if representing all code using only lists and atoms is more "pure" (or even, arguably, more beautiful), I think having statements and operators make code lot easier to read and write. Finally, to the end user, Wipple has sensible defaults and little configuration: all Wipple code is formatted the same, the standard library is tiny, versioning is handled in your own repository instead of through a package manager, and you can start writing code in the Wipple Playground right now from any device. To the hacker, though, you can shape Wipple to do almost anything you want.

Wipple is written in Rust, and the project is available on GitHub. Contributions are welcome! Because Wipple is primarily an experiment for me, there are no current plans for source or API stability. However, just because Wipple focuses on expression of ideas doesn't mean it can't also be fast and bug-free!

With that out of the way, let's write some Wipple code!

Learn Wipple

Welcome! Wipple is a programming language that's designed to be natural to read, write and especially learn. This guide is meant for people who have never written code before — if you're already experienced in other languages, please read the reference instead.

It's easy to get started with Wipple, no installation required. In fact, all you need is a web browser! Just visit the Wipple Playground and you have everything you need to run Wipple code. We'll be using the playground frequently in this guide, so you can learn by doing.

A tour of the Wipple Playground

Wipple Playground

The playground consists of a code editor into which you can type. The editor will automatically detect errors in your code and highlight them! Try pressing return a few times so your cursor is on a new line, type a random word (eg. wipple), and hover your mouse over the red squiggles. What happens?

By the way, any time you see text in this font, it represents code. You can copy and paste the code into the playground at any time to try things out on your own! Look for the button and click it to copy the code to your clipboard. Try it now!

show "Hello, world!"

You should see Hello, world! in the console.

Now click the right arrow to continue on to the next page!

Math

To get familiar with the Wipple Playground, let's start by writing a simple calculator! Copy and paste each example into the playground using the button. Make sure to delete what's already in the editor before pasting in each new example.

Alright, here's our first example:

2 + 2

...wait, nothing happened. Why not?

Computers do exactly what you tell them to do

The problem is that you told the computer to add 2 and 2 together, but you didn't tell it what to do with the result! So the answer (4) is just discarded. Remember that computers always do exactly what you tell them to do, and never anything you don't. To fix this, we can add show to the front of our calculation, which instructs the computer to display it on the screen:

show (2 + 2)

Hooray, now 4 appears on the screen! Try changing the numbers and experiment with other symbols, and see what happens.

Some more examples

Copy this code into the playground! Can you guess what the output will be?

show 1
show (1 + 1)
show (1 - 1)
show (2 * 3)
show (3 / 4)
show (0.1 + 0.2)
show (1 / 0)

What do you notice about writing code over multiple lines?

More complex calculations

You can use Wipple as a full calculator by grouping calculations with parentheses. Try it out!

show (2 + 2 + 2)
show (1 - (2 + 3))
show ((3 + 2) / 10)
show ((2 + 2) * (2 + 3 * 2))

OK, now that you've hopefully got the hang of the playground, let's move on to something more interesting!

Variables

You're probably familiar with variables in math — a variable is just a way to give some complex calculation a name. It's the same thing in Wipple! Let's try it out:

x : 2 + 2
show x

Instead of using an = sign, Wipple uses : (a colon). You can pronounce a : b as "a is b". Now, anywhere the computer sees x, it will replace it with 2 + 2!

Let's try a more complicated example. Can you guess what the output will be?

a : 3
b : a + 2
c : a + b
show c

Note that unlike in math, you can use words for variables in Wipple. Like this:

sum : 2 + 2
show sum

my-awesome-variable : 3 + 4
show my-awesome-variable

Try making some variables of your own in the playground!


Can you guess what this code does?

show hmmm

No output! In fact, you should notice a red squiggle underneath hmmm. Try to fix this error — what did you have to do? (Hint: hover over the red squiggle to see more information about the error!)

Text

So far we've only worked with numbers, but Wipple supports text as well!

show "Hello, world!"

Notice how "Hello, world!" is in a different color than show. This indicates that we're dealing with text instead of variables or calculations. Just keep in mind that text is always written between quotation marks! a means the variable a, while "a" means the letter a.

Try show-ing your name on the screen!

Now that we're familiar with the Wipple Playground, we can start learning how to write some "real" Wipple code. Keep the playground open so you can follow along when you see any code examples!

So how does Wipple actually work?

Imagine you're having a conversation with another person. In order to understand one another, you both have to be speaking the same language. The job of a language is to provide rules on what words you can use, what they mean, and what happens when they are used together in different ways. "I love Wipple" is a valid sentence in English, but Jabberwocky is nonsense!

Wipple is really two things — the syntax, which provides the rules on what's valid to type in, and the semantics, which provides the rules on what actually happens when you run your program. Every piece of valid syntax has its own semantics.

Let's go back to the playground and try out a simple example:

sum : 1 + 2
show sum

There are a couple of things happening here. First the text you type is parsed into a format the computer can understand (kind of like how when we hear the world "cookie", we think about the smell of fresh cookies baking in the oven). The format looks something like this:

  • The name sum
  • The : symbol
  • The number 1
  • The + symbol
  • The number 2
  • and so on.

These individual items are called tokens. The computer then uses these tokens to build up a list of instructions to execute — for example, 1 + 2 is converted to an "add" instruction. Pretty cool!

Wait a moment, how does the computer know that 1 + 2 means "add"? Well, it contains a big table of all the names and symbols in the program and what they mean. It works just like a dictionary, where each word has a definition.

NameMeaning
sumThe result of 1 + 2
+Code to add two numbers
showCode to display things on the screen

That's why referring to any old name doesn't work in Wipple — you have to use a name that's in the table.

There's one more special name in Wipple — the : symbol. It adds new entries into the table, allowing you to define your own names!

Now that we have a better understanding of what's actually going on when you write Wipple code, we can start looking at one of the foundational Wipple concepts: the function. Take a quick break, and then click the arrow to go to the next page!

Functions

Wipple is what's called a functional programming language. That means functions are used everywhere in Wipple code!

You probably remember from math that a function f(x) = 3x takes an input (x) and returns an output (3x). It works the same way in Wipple! You just write it a little differently:

f : x -> 3 * x

The arrow is pronounced "becomes", so the code above is read as "f is when x becomes 3 times x". The process of x becoming 3 times x is a function!

Let's create a function to add two numbers, a and b:

add : a -> b -> a + b

Notice that functions only accept a single input. If you want multiple inputs, you need multiple functions (arrows)! The above code can be rewritten like so:

add : a -> (b -> a + b)

Now how do we use functions? It's simple — just write the name of the function, followed by its inputs:

add 1 2

And of course, we need to show the answer! (Guess what, show is a function too!)

show (add 1 2)

If you're familiar with programming already, you might ask what happens if we only provide a single input. In Wipple, you just get back the inner function!

add 1

Here, a is 1. What's b? We give b a value next:

(add 1) 2

Notice this is the same code as before — add 1 2! Now for the super cool part — we can give add 1 a name, and use it as its own function:

increment : add 1
show (increment 42)

You should see 43 on the screen!

If you're confused, try hovering over parts of the program, and the playground will tell you what kind of code you're hovering over. For example, hovering over increment will show increment :: Number -> Number, indicating that it accepts a number and returns a number. Hovering will be explained in more detail in the next section!

Quick Start

This several contains quick start guides for translating concepts in other languages to Wipple. If you're already an experienced developer, you're in the right place! If you want to learn Wipple from scratch, be sure to read Learn Wipple.

For JavaScript Developers

Welcome to Wipple! This guide goes over some basic JavaScript concepts and their equivalent in Wipple. When you finish this guide, you'll have a foundational understanding of Wipple code that you can use to experiment on your own.

Hello, world

Wipple's equivalent of console.log is show:

show "Hello, world!"

Notice that there's no semicolons in Wipple code — just put each statement on its own line.

Comments, numbers and strings

You can write a comment using --. Wipple only has line comments:

-- This is a comment
this is executed -- this is not

Numbers are represented in base 10 instead of floating point, but they are written the same way:

42
3.14
-1

Strings are called "text" in Wipple, and must use double quotes:

"Hello, world!"
"line 1\nline 2"

You can use format to do string interpolation:

format "Hello, _!" "world" -- Hello, world!

Variables

In Wipple, you can declare variables using the : operator:

answer : 42
name : "Wipple"

Wipple uses static single assignment, which means that you can't change the value of an existing variable after you create it. However, you can declare the same variable twice — the new variable shadows the old one:

x : 42
x : x + 1
show x -- 43

if statement

Wipple doesn't have an if statement like in JavaScript. Instead, if works more like the ternary operator, and can be used anywhere an expression is needed. By convention, boolean variables end in a question mark.

password : "letmein123"
valid? : password = "password123!" -- use a single '=' to compare values
show (if valid? "Access granted" "Access denied") -- Access denied

Basic types

Wipple is a strongly-typed language, which means that your code is verified at compile-time. Luckily, Wipple has type inference, so you usually don't need to think about types at all! You can use :: to annotate the type of a value.

42 :: Number
"Hello" :: Text

If you mismatch the types, Wipple will emit an error:

42 :: Text -- Mismatched types: Expected Text, found Number

Objects

Wipple calls objects "types", which you can create using type:

Person : type {
    name :: Text
    age :: Number
}

You can create an instance of this object like so:

bob : Person {
    name : "Bob"
    age : 35
}

Functions

Wipple's functions work like JavaScript's arrow functions. In fact, they both use the arrow notation!

increment : x -> x + 1
show (increment 42) -- 43

One big difference is that Wipple functions may only accept a single parameter. If you want multiple parameters, use multiple functions!

add : a -> b -> a + b
show (add 1 2) -- 3

If that's confusing, here's the equivalent JavaScript code:

const add = (a) => (b) => a + b;
console.log(add(1)(2)); // 3

Methods

Wipple doesn't allow you to add methods to an object (although you can store functions inside types like any other value). Instead, you can declare functions like this:

greet :: Person -> Text
greet : person -> format ("Hello, _!") (person name)

greet bob -- Hello, Bob!

Alternatively, you can use the . operator to chain function calls:

bob . greet -- Hello, Bob!

Inheritance

Wipple has neither classes nor inheritance. Instead, you can use traits! Traits are pretty advanced, but here's a simple example in TypeScript and in Wipple:

TypeScript

// Greet is an interface that can be implemented with a function returning text
interface Greet {
    greet(): string;
}

// For any value implementing Greet, return a greeting
function greet<A extends Greet>(x: A): string {
    return `Hello, ${x.greet()}`;
}

class Person implements Greet {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    // Greet for Person values is defined as the person's name
    greet() {
        return this.name;
    }
}

class Earth implements Greet {
    constructor() {}

    // Greet for Earth values is defined as "world"
    greet() {
        return "world";
    }
}

greet(new Person("Bob")); // Hello, Bob!
greet(new Earth()); // Hello, world!

Wipple

-- Greet is a trait that can be defined with a function returning text
Greet : A => trait A -> Text

-- For any value where Greet is defined, return a greeting
greet :: A where (Greet A) => A -> Text
greet : x -> format "Hello, _!" (Greet x)


Person : type {
    name :: Text
}

-- Greet for Person values is defined as the person's name
instance Greet : Person { name } -> name


Earth : type

-- Greet for Earth values is defined as "world"
instance Greet : just "world"


greet (Person { name : "Bob" }) -- Hello, Bob!
greet Earth -- Hello, world!

Language Reference

This section contains information about Wipple syntax, semantics and standard functions, and compares them to other languages. You can use the reference to learn Wipple quickly if you're already familiar with another language.

Syntax

Wipple has a minimal syntax fundamentally inspired by Lisp. Wipple code consists of seven kinds of expressions:

  • Names (x, do-something!, ->) are used to identify variables. When quoted, they represent data similar to text.
  • Numbers (42, 3.14, -1) are used for calculations and are stored in decimal format.
  • Text ("Hello, world", "😀") is used to represent human-readable data and is stored in Unicode format.
  • Lists ((a b c)) are used to group expressions together. Inside blocks, the parentheses are inferred.
  • Attributes ([a b c]) are used to apply additional information to an expression at compile-time.
  • Blocks ({ a b c }) are used to execute a series of lists in order. Source files are inferred as blocks.
  • Quoted forms ('a, '(a b c), ''(a b c)) are used to insert expressions into the structure of another expression, or to represent code as data.

Comments begin with -- and are ignored.

For example, this source file consists of the following expressions:

Source code Expression tree
[doc "A person named bob"]
bob : Person "Bob"

test {
    -- Ensure math works
    assert (2 + 2 = 4)
}
Block
  List (attributes: doc "A person named bob")
    Name "bob"
    Name ":"
    Name "Person"
    Text "Bob"
  List
    Name "test"
    Block
      List
        Name "assert"
        List
          Number 2
          Name "+"
          Number 2
          Name "="
          Number 4

A note on lists

Lists may span multiple lines, but the way they are parsed depends on whether they belong to a block or are between parentheses.

Inside a block, you can use indentation to merge multiple lines into a single list:

-- Parsed as two lists
a b c
d e f

-- Parsed as one list
a b c
    d e f

Between parentheses, you can use any indentation you wish — all expressions belong to the list until the ending ).

How do operators work if they aren't part of the syntax?

Lists are evaluated based on the operators defined in the code. You can even define your own operators to change how lists are evaluated! (TODO: Section on defining operators)

Variables

In Wipple, you can declare new variables using the : operator, which is pronounced "is":

favorite-color : orange -- "favorite color is orange"

By convention, variable names are lowercase and words are separated using dashes, like in CSS. If thisStyle is called "camel case", then this-style is called "kebab case"!

Note that : must stand on its own, surrounded by whitespace. This doesn't work:

favorite-color: orange

You can redeclare variables within the same block. But note, redeclaring is not the same as reassigning! Any code referencing the previous variable's value will continue to do so.

n : 1
add : x -> x + n

n : 2
add 10 -- 11, not 12

If you want to change the value of an existing variable at runtime, you can do so using Mutable:

n : Mutable 1
add : x -> x + get n

n | set! 2
add 10 -- 12

This isn't magic; the actual value of n is a reference to a piece of memory, which remains unchanged, but the value stored at that piece of memory can be mutated (hence the need for get, which reads from the memory, and set!, which writes to it).

Finally, you can create constants by specifying the type of the variable above its declaration:

answer :: Number
answer : 42

Constants may not be redeclared in the same scope. In addition, constants are lazily loaded and may not capture variables. A file consisting entirely of constants may be imported by another file with use.

Patterns and Logic

Wipple has a concept of "patterns", which bind to one of many options for a piece of data. For example, we can define a Maybe type:

Maybe : A => type {
    Some A
    None
}

use Maybe

And then bind to a Some or None value:

x? : Some 42
Some x : x? -- x : 42

If x doesn't contain a Some value, the program crashes:

x? : None
Some x : x? -- runtime error

You can use patterns in function parameters too!

unwrap : Some x -> x -- unwrap :: for A -> Maybe A -> A

On its own, this isn't very useful — why would you want your program to crash? To get around this, we can use Wipple's fundamental logic operation, when. You give when a bunch of functions, and it will call the one whose pattern matches the input:

Grade : type {
    A
    B
    C
    D
    F
}

use Grade

when grade {
    A -> "Top of the class"
    B -> "Pretty good"
    C -> "Getting there"
    D or F -> "Need to study"
}

Thanks to when, Wipple doesn't have regular booleans! Boolean logic is implemented using the Boolean type:

Boolean : type {
    True
    False
}

use Boolean

if : bool then else ~> when bool {
    True -> then
    False -> else
}

show (format "x _ 5" (if (x = 5) "is" "is not"))

You can bind variables inside when like so:

Map : f -> x? -> when x? {
    Some x -> Some (f x)
    None -> None
}

area : when shape {
    Square s -> s ^ 2
    Rectangle l w -> l * w
    Circle r -> 3.14 * r ^ 2
}

The cases you provide to when must be exhaustive (ie. they must cover all variants of the type). To ignore some cases, or to handle multiple cases in one branch, you can use _ and or:

when color? {
    Some (Red or Blue) -> "hooray"
    _ -> "oh no"
}

Alternatively, you can use when?:

when? color? (Some (Red or Blue)) (show "hooray")

If you just want to execute a piece of code when a condition is true, you can use when!:

when! (2 + 2 = 4) (show "woohoo")

The opposite form is unless!:

unless! : template bool body -> when! (not bool) body

Functions

Wipple is a hybrid procedural-functional programming language, encouraging the use of functions and composition to make series of steps more natural to read and write. You can declare a new function using the -> operator, which is pronounced "becomes":

-- "name becomes 'Hello, (name)!'"
name -> format "Hello, _!" name

-- "greet is the action of a name becoming ..."
greet : name -> ...

-- "x becomes itself"
x -> x

Functions in Wipple may only accept one input. To have multiple inputs, make multiple functions!

add : a -> b -> a + b

To call a function, just write the function followed by the inputs in a list:

add 2 3

(a b c) is the same as ((a b) c). Importantly, this means that functions can be partially applied:

increment : add 1 -- 'add 1' returns a new function
increment 5 -- 6

Many times, you'll want to write a program as a series of steps. Say you want to load some data from a file, and then parse it:

file : load "my-file.csv"
contents : parse file
show contents

You can simplify this sequence a bit by using the . operator, pronounced "then":

-- "load, then parse, then show"
load "my-file.csv" . parse . show

You can use . with any function! x . f is equivalent to f x. You can use this property to simulate methods like in object-oriented languages:

minus : x -> n -> n - x

3 . minus 2 . minus 1 -- 0

By convention, functions that take a "receiving" argument should put the receiver last so that it can be used with . notation.

Wipple also supports functors with the | operator, whose pronounciation depends on the context in which it's used. For example, you can use | to perform an operation on each item in a list, or perform an operation on a Maybe if it contains a value:

increment : x -> x + 1

-- "increment each number in numbers"
numbers : '(1 2 3)
numbers | increment -- '(2 3 4)

-- "if number? contains a value, increment it"
number? : Some 1
number? | increment -- Some 2

Here are some builtin functions that come in handy when using the . and | operators:

FunctionTypeDescription
itA => A -> AReturns its input
justA B => A -> B -> AReturns a function that ignores its input

Loops

To repeatedly evaluate an expression, you can use loop. loop accepts a block evaluating to a Flow value, namely Continue or End:

Flow : A => type {
    Continue
    End A
}

ten : {
    n : mutable 0
    loop {
        if (n = 10) {
            End n
        } {
            increment! n
            Continue
        }
    }
}

You can also use while and until to repeatedly evaluate a condition:

while : condition body ~> loop {
    if condition {
        body
        Continue
    } {
        End ()
    }
}

until : condition body ~> while (not condition) body

ten : {
    n : mutable 0
    while (n < 10) (increment! n)
    n
}

ten : {
    n : mutable 0
    until (n = 10) (increment! n)
    n
}

Types

Wipple has a powerful static type system that works similarly to Haskell's. In short, every expression has a type, which can be expressed using the :: operator (pronounced "is a"):

42 :: Number -- "42 is a number"
"Hello" :: Text
'(1 2 3) :: List Number
x -> x :: Number -> Number

You can also create your own types with type:

Name : type Text

Person : type {
    name :: Name
    age :: Number
}

Color : type {
    Red
    Orange
    Yellow
    Green
    Blue
    Purple
}

Maybe-Number : type {
    Some Number
    None
}

By convention, variables representing types and variants of enums are capitalized.

The Maybe-Number type above can be made more general using type functions, which use a => arrow:

Maybe : A => type {
    Some A
    None
}

This also works on other values, like regular functions:

it :: A => A -> A
it : x -> x

By convention, generic types are named A, B, C, etc., but you should try to give a more specific name if applicable.

Finally, you can use an underscore (_) to represent a placeholder type:

Identity : A => -> type (A -> A)
(x :: Number) -> x :: Identity _ -- inferred as Identity Number

Traits

A trait is a variable whose value depends on its type, similar to Haskell's typeclasses. For example, we can define an Equal trait and give values for each type we want to compare as equal:

Equal : A => trait A -> A -> Boolean


User : type {
    id :: User-ID
    name :: Text
    age :: Number
}

instance Equal : User { id : id1 } -> User { id : id2 } -> id1 = id2

To use a trait, refer to it by its name:

equals? :: A where (Equal A) => A -> A -> Boolean
equals? : b -> a -> equal a b


alice : User { ... }
bob : User { ... }

alice . equals? bob

Another useful trait is Default, which provides a "default value" for a type. For example:

instance Default : 0
instance Default : ""
instance Default : False
instance Default : None
...


add-identity :: A B where (Add A A B) (Default A) => A -> B
add-identity : n -> n + Default

add-identity 42 -- 42 + 0 = 42

Quoting

You can prefix expressions with a single quote (') to change how they are evaluated: instead of reducing the expression as normal, its structural components are each evaluated individually, and the resulting expression is a value at runtime.

The primary use of quotation is when building lists:

numbers : '(1 2 3)

Without the quote, the expression would be interpreted as a function call, which is invalid for numbers.

Unlike Lisp, quoting in Wipple is "shallow" and only uses the structural form of the value being directly quoted. This means that '((f x) (f y)) evaluates f x and f y. You can quote the list twice to preserve the entire structure:

f : x -> x
x : 1
y : 2

'((f x) (f y)) :: List Number
''((f x) (f y)) :: List (List Name)

Names have no substructure, so you only need to quote them once:

'x :: Name

Quoting numbers and text has no effect. Blocks and attributes are compile-time constructs and can't be quoted.

Templates

Templates are functions that take and return expressions. Wipple templates are "hygienic", meaning that any variables the template refers to will resolve to values in the template's scope, and vice versa.

if : bool then else ~> when bool {
    True -> then
    False -> else
}

In the future, more control over the structural form of the expression (like Rust macros) will be added.

Naming Conventions

  • Names in Wipple use kebab-case.
  • Regular variables and constants are lowercase.
  • Types, enum variants, and traits are Uppercase.
  • Append ? to Booleans (eg. valid?), Maybes and Results (eg. username?), functions which return these values, and functions which conditionally execute their input (eg. when?).
  • Append ! to functions which mutate state (eg. increment!) or return different outputs given the same input (eg. get!). External output is considered to have no effect on the program's state (eg. show, drive-motor).
  • Prefer punctuation for fundamental or abstract operators (eg. :, |), and prefer short English names for other operators (eg. and, or). Only use operators when there is a fundamental or natural relationship between the left- and right-hand side, otherwise prefer functions or templates.
  • Prefer full words over abbreviations (eg. error over err or e, items over xs), and prefer to name things after their actual function instead of after historical names (eg. first over car, show over print).