Welcome to Wipple!

Wipple is a programming language created by Wilson Gramer 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.

How to use this guide

Click the menu icon in the top left corner to see the table of contents. From there, you can jump to any page in the guide. If you want to read the guide in order, click the arrows at the bottom of each page.

The Wipple Playground

The Wipple Playground is a place to experiment with Wipple code in a Jupyter Notebook-like environment. The playground includes a beginner-friendly guide to learning Wipple as well!

Quick start 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`, but 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
}

And you can use destructuring to get the inner values:

{ name age } : bob

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 : { name } -> format ("Hello, _!") 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 Earth : just "world"


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

Generics

Wipple has a powerful type system that lets you express relationships between values. Often, you’ll want to implement a function or instance that works for any input type — for example, implementing Equal for Maybe Value where Equal Value is implemented.

Wipple lets you express generics using type functions, which use the => operator. The left-hand side of the type function introduces type parameters into scope, and the right-hand side is a type depending on these parameters. For example, we can define Maybe as follows:

Maybe : Value => type {
    Some Value
    None
}

Type functions can also be used with traits, constants and instances:

Show : A => trait (A -> Text)

unwrap :: A => Maybe A -> A
unwrap : ...

A where (Show A) => instance Show (Maybe A) : ...

That where clause in the above example allows you to introduce bounds on the type parameters — that is, the type, trait, constant or instance may only be used if there are instances matching the trait with the provided parameters.

You can provide as many parameters and bounds as you want:

A B C where (T A) (U B) (V C) => ...

In a type declaration, you don’t need to actually use the parameters anywhere in the type. This is useful for creating “type states” that represent data at the type level:

Idle : type
Hovering : type

Drone : State => type

take-off :: Drone Idle -> Drone Hovering
take-off : just Drone

land :: Drone Hovering -> Drone Idle
land : just Drone


my-drone :: Drone Idle
my-drone : Drone

my-drone . take-off . land -- works!
my-drone . land -- cannot land because drone is already idle

Mutability

Wipple doesn’t allow you to reassign the value of a variable once you’ve declared it. Instead, you can shadow an existing variable by assigning to the same name:

x : 1 -- first assignment
x : "hello" -- second assignment

Notice how the types don’t need to be the same — this is because the two xs are distinct values.

Importantly, any code referring to the original x will continue to do so:

x : 1
show-x : () -> show x
x : 2
show-x () -- displays 1, not 2

However, there are circumstances where you actually need to change a variable’s value and have that change be shared across the program. To accommodate this, Wipple provides a Mutable type!

You can create a new Mutable value by using the mutable function:

-- mutable :: A => A -> Mutable A
x : mutable 1

To retrieve the value inside, use get:

-- get :: A => Mutable A -> A
show-x : () -> show (get x)

And use set! to change it:

-- set :: A => A -> Mutable A -> ()
x . set! 2

Now when you call show-x, you’ll get 2 instead of 1!

By convention, any function that changes the value of a Mutable input ends with !. There is no need to append ! to functions that only mutate internal state.

There are many useful functions for changing mutable values; here are just a few:

Function Type Description
swap! A => Mutable A -> Mutable A -> () Swaps the values of its inputs
add! Left Right where (Add Left Right Left) => Right -> Mutable Left -> () Adds a value to its input
increment! A where (Add A Number A) => A -> () Increments its input by 1

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
[help "A person named bob"]
bob : Person "Bob"

test {
    -- Ensure math works
    assert (2 + 2 = 4)
}
        </td>
        <td>
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
        </td>
    </tr>
</tbody>

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:

Function Type Description
it A => A -> A Returns its input
just A B => A -> B -> A Returns 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).