Wipple Home Blog

Blog

I’m excited to announce that I’ve been working on a new compiler for Wipple! The new compiler is being written from the ground up for performance and stability. In addition, I’ve made some changes to Wipple’s syntax and features to make the language easier to learn and use that will debut alongside the new compiler.

Syntax updates

The most visible change in Wipple is the new syntax for comments and blocks. Comments are now written using brackets, and blocks are written with parentheses:

[Represents a sport.]
Sport : type (
name :: Text
emoji :: Text
players :: Number
)

instance (Show Sport) : ...

basketball : Sport (
name : "Basketball"
emoji : `πŸ€`
players : 5
)

show basketball [Basketball πŸ€ has 5 players]

In writing, parentheses are used much more often than braces, and students I’ve worked with who are still learning to type have trouble finding the braces on the keyboard. It’s also hard for beginners to remember when to use braces versus parentheses, so having a single grouping symbol reduces the learning curve.

Comments’ bracket syntax takes the place of attributes, which have been removed from the language. Wipple’s new compiler parses comments rather than ignoring them, and can use them to generate documentation just like the previous [help] attributes.

New compilation model

Although the final program is the same, Wipple’s new compiler works much differently internally. Rather than parsing and compiling every file every time, the new compiler generates intermediate artifacts that can be reused. When compiling a library, you specify the list of files to be treated as a single unit:

$ wipple compile base/*.wipple \
--interface base.wippleinterface \
--library base.wipplelibrary

(base is the new name for the standard library, previously called std).

The .wippleinterface file contains the definitions of types, traits, constants, and instances contained within the provided files. The .wipplelibrary file contains the compiled bytecode for these constants and instances. The artifacts correspond to C’s .h and .o files, respectively.

Now, we can use base.wippleinterface to refer to standard library definitions from other files:

$ wipple compile turtle.wipple base.wippleinterface \
--interface turtle.wippleinterface \
--library turtle.wipplelibrary

Now only turtle.wipple will be parsed and compiled β€”Β the results of base are reused!

Finally, we can compile our main program against turtle and base to get an executable:

$ wipple compile main.wipple base.wippleinterface turtle.wippleinterface \
--library main.wipplelibrary
$ wipple link *.wipplelibrary -o main
$ ./main

main contains all the bytecode for our program β€”Β behind the scenes, it contains #!/usr/bin/env wipple run to actually run the code. You can also call wipple run main directly.

This new compilation model is more complicated, but the Wipple Playground will do it all automatically for you. And it means that instead of shipping the standard library’s source code with the playground, I can just provide the .wippleinterface and .wipplelibrary files to be linked against! The result should be a dramatic speedup.

In order for all of this to work, the new compiler no longer monomorphizes constant and instance definitions, instead opting for dynamic dispatch for traits at runtime. Essentially, all of the instances of a trait are stored in a list that’s iterated over whenever a trait expression is encountered. This decision is a tradeoff between compilation speed and runtime speed β€”Β the program will run more slowly if a lot of traits are used, but the compiler doesn’t have to recursively resolve generic definitions anymore. In the Wipple Playground, code is compiled on every keystroke and usually spends most of the time waiting for user interaction while it’s running, so I think this is a good tradeoff.

Structure expressions and lazy values

In the new compiler, support for custom syntax has been removed. Instead, there are two new constructs that cover almost all of the use cases for custom syntax: structure expressions and lazy values!

Structure expressions are blocks containing only variable assignments. Previously, to initialize a structure, you provided the structure’s name followed by this block. Now, the typechecker will infer the structure! This means you can write your own functions with "named parameters":

[Options for `fraction`.]
Fraction-Options : type (
round :: Number
)

[Display a number as a fraction.]
fraction :: Fraction-Options -> Number -> Text
fraction : ...

pi : 3.14 . fraction (round : 1)

show pi [31 / 10]

Structures also now support default values:

Box : type (
width :: Pixels
width : 100 pixels

height :: Pixels
height : 100 pixels

color :: Color
color : `red`
)

[Draw a box on the screen.]
box :: Box -> ()
box : ...

box (color : `blue`)

If you still want to specify the name of the structure, you can β€”Β the new compiler generates a constructor function that just returns the provided structure expression:

Box :: Box -> Box

The second new feature is lazy values: values that aren’t computed right away. Non-lazy values are implicitly converted into lazy values, and you can use evaluate to compute the value. For example, repeat is now a regular function that accepts a lazy body:

[Determines whether to evaluate a `repeat` body again.]
Repeat-Predicate : Predicate Body Result => trait (Predicate -> Body -> Result)

[Repeat using the `Control-Flow` produced by the `repeat` body.]
with-control-flow :: With-Control-Flow _
with-control-flow : With-Control-Flow

With-Control-Flow : Result => type

Result => instance (Repeat-Predicate With-Control-Flow (Control-Flow Result) Result) :
_ body -> body

[Repeat so long as the provided condition is `True`.]
while :: lazy Boolean -> While
while : condition -> (condition : condition)

While : type (condition :: lazy Boolean)

instance (Repeat-Predicate While () ()) : (condition : condition) body ->
if (evaluate condition) Continue (Break ())

[Repeat forever.]
forever :: Forever
forever : Forever

Forever : type

instance (Repeat-Predicate Forever _ _) : _ _ -> Continue

[Execute a block of code repeatedly.]
repeat :: Predicate Body Result where (Repeat-Predicate Predicate Body Result) =>
Predicate -> lazy Body -> Result
repeat : predicate body -> (
predicate : Repeat-Predicate predicate

[The predicate and body are grouped together in a tuple to allow for
tail-call optimization.]
repeat' :: Body Result => ((Body -> Result) ; lazy Body) -> Result
repeat' : (predicate ; body) -> when (predicate (evaluate body)) (
Continue -> repeat' (predicate ; body)
Break result -> result
)

repeat' (predicate ; body)
)

The resulting API is identical to the one that uses custom syntax!

[Displays "1 2 3 4 5 6 7 8 9 10" on the screen.]
counter : mutable 1
repeat (while (get counter < 10)) (
show (get counter)
increment! counter
)

Conclusion

In the playground and throughout the courses, Wipple will look and work mostly the same. But under the hood, almost everything has changed. The new compiler’s implementation is available on GitHub under the new-compiler branch. I look forward to releasing it once it’s ready!

December 17, 2023

Introducing a new design for Learn Wipple! Lessons are now grouped into courses:

Screenshot showing the new course list in the Wipple Playground

Within a course, the lessons appear on the left side of the screen for easy browsing:

Screenshot showing the new lesson view in the Wipple Playground

I’m in the process of revamping the lessons, too, so expect more to change in the coming weeks!

Wipple has a new attribute, [help-alternative], that makes it easier to discover similar commands in your code. When you Ctrl-click (or Cmd-click on a Mac) an identifier, you get a list of alternative commands to replace the selection with:

Animation showing alternatives in Wipple Playground

Previously, the documentation and type information for an identifier would only appear if you hovered your mouse over it and beginner mode was off. Now, you can Ctrl-click to bring up this information in both modes! In beginner mode, the type information is hidden.

Beginner mode off:

Wipple Playground context menu with beginner mode off

Beginner mode on:

Wipple Playground context menu with beginner mode on

The Wipple Playground has a brand-new module designed to teach students about climate change. A new lesson titled "Writing efficient code" explores how you can write the same program in different ways to save energy. When you run your code, Wipple analyzes your CPU’s power consumption and displays a report showing the cost and emissions of various energy sources used to run your program at scale.

Screenshot of Wipple's energy module

This module is the result of a project I worked on with Demetrios Kennedy at Worcester Polytechnic Institute. You can view the lesson here, and you can use the energy module as part of your own programs by clicking Energy when you open the playground!

Wipple has a new feature that makes it easier to put a single statement across multiple lines. Previously, you had to explicitly use the backslash character (\) to tell Wipple to merge the next line with the previous one:

numbers : 1 , 2 , 3

numbers \
. transform (+ 1) \
. filter (< 3) \
. each show

Now, Wipple will automatically treat the statement as continuing across multiple lines if you use any "common operator" like :, -> and .!

numbers : 1 , 2 , 3

numbers
. transform (+ 1)
. filter (< 3)
. each show

The set of common operators is fixed in order to keep formatting separate from compilation; that is, Wipple doesn’t need to parse attributes like [operator] in order to format your code. You can always continue to use \ where needed!

November 13, 2023

Wipple’s Mutable type is used to provide a binding to a value that’s shared across multiple places in the program. Now, Mutable is more flexible: you can create a binding to a part of a value! Let’s take a look at an example to see why this is useful.

Say you have a Mutable Person with a name, and you want to add a suffix to the name. Previously, you would have to retrieve the name, change it, and then build a whole new Person value to pass to set!.

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

graduate! :: Mutable Person -> ()
graduate! : person -> person . set! (Person {
name : (name of get person) + ", Ph.D."
age : age of get person
})

Now, you can use projections to make this code much simpler!

graduate! :: Mutable Person -> ()
graduate! : project-field name | add! ", Ph.D."

In a language with traditional references like C++, that code might look like this:

void graduate(Person &person) {
person.name += "Ph.D.";
}

So how does project-field work? Under the hood, Wipple has two new constructs. The first is where for simplifying the process of updating a single field in a structure. It can be used anywhere, not just for Mutable values!

-- The functional way
graduate :: Person -> Person
graduate : person -> \
person where { last-name : (last-name of person) + ", Ph.D." }

And the second is the way Mutable is implemented. Previously, Mutable was essentially a reference to a value on the heap. That functionality has been moved to the new Reference type, and Mutable is now implemented in terms of Reference. But in addition to reference-based Mutable values, you can now create computed Mutable values that act like two-way bindings:

-- Remove leading and trailing whitespace from a `Text` value
trim-whitespace :: Text -> Text
trim-whitespace : ...

-- Project a `Mutable Text` so that it never contains leading or trailing whitespace
project-trim-whitespace :: Mutable Text -> Mutable Text
project-trim-whitespace : project trim-whitespace (new _ -> trim-whitespace new)

That new project function is best explained by looking at its type. You provide a function that computes a B from an A, and a function that applies the new B to the original A. project is intended to be partially applied; that is, you usually don’t provide the Mutable A immediately. Instead, you use project to define your own functions that operate on Mutable values.

project :: A B => (A -> B) -> (B -> A -> A) -> Mutable A -> Mutable B

project-field isn’t magic, either β€”Β it’s implemented as a syntax rule!

project-field : syntax {
project-field 'field -> \
project ({ 'field } -> 'field) (new val -> val where { 'field : new })
}

Finally, you can create a Mutable value that ignores changes with the constant function:

one : constant 1
increment! one
show (get one) -- 1

The API for interacting with mutable values hasn’t changed at all β€”Β you still use get and set! as normal, and all the new features work automatically!

November 10, 2023

Inspired by the reading guides students use to focus on one line at a time in a book, Wipple now has a focus mode that highlights the active line and fades away the other lines!

Screenshot of focus mode in the Wipple Playground

You can enable it in the Wipple Playground settings.

For a long time, Wipple has used the list syntax to construct a list and the , syntax to construct a tuple. Today, this changes β€” the , syntax is now used for lists!

numbers : 1 , 2 , 3
numbers . each show
1
2
3

I decided to make this change for two reasons. First, lists are used much more often than tuples, so it makes sense to give list syntax priority. Second, Wipple parses syntax rules before resolving variables, so having a syntax rule named list means that you can’t declare a variable named list as well. The standard library worked around this by using names like l or input, but now you can just use the obvious variable name list.

If you provide elements of different types, you still get a nice error message:

my-list : 1 , "2"
error:
  β”Œβ”€ test.wpl:1:15
  β”‚
1 β”‚ my-list : 1 , "2"
  β”‚               ^^^
  β”‚               β”‚
  β”‚               expected `Number`, but found `Text`
  β”‚               this element must have the same type as the other elements
  β”‚
  = for more information, see https://wipple.dev/playground/?lesson=errors/mismatched-types

To create an empty list, use the , operator by itself (or the Default implementation defined below):

instance (Default (List _)) : (,)

And to create a list with a single element:

just-one-number :: List Number
just-one-number : 1 ,

Trailing commas are allowed, so you can easily add a new item to a large list:

constants : (
1.41 ,
1.62 ,
2.72 ,
3.14 ,
6.28 ,
)

The , syntax for lists is defined in Wipple, too, meaning Wipple now supports variadic operators!

[operator Variadic-Precedence]
, : syntax {
, ...elements -> ...
}

And finally, to create a tuple, you now separate each element with a semicolon (;):

my-tuple :: Number ; Text ; Boolean
my-tuple : 1 ; "a" ; True

first ; second ; third : my-tuple
show first
show second
show third
1
a
True

These changes are live on the Wipple Playground, and the lessons have been updated to use the new syntax.

Let’s say we want to implement Default for a tuple, defined to be a tuple of the default value of each element. To accomplish this, we can define an instance Default (A , B) for any types A and B:

A B => instance (Default (A , B)) : (Default , Default)

This will fail to compile…

error:
  β”Œβ”€ test.wpl:1:38
  β”‚
1 β”‚ A B => instance (Default (A , B)) : (Default , Default)
  β”‚                                      ^^^^^^^ could not find instance `Default A` for any type `A`
  β”‚
  = for more information, see https://wipple.dev/playground/?lesson=errors/missing-instance

error:
  β”Œβ”€ test.wpl:1:48
  β”‚
1 β”‚ A B => instance (Default (A , B)) : (Default , Default)
  β”‚                                                ^^^^^^^ could not find instance `Default B` for any type `B`
  β”‚
  = for more information, see https://wipple.dev/playground/?lesson=errors/missing-instance

…because whatever types A and B end up being don’t necessarily have a Default implementation. For example, there is no default Grade:

Grade : type { A B C D F }

-- What implementation would we use here???
(Default) :: (Grade , Grade)

To resolve this, we can use a where clause to add bounds to the instance, propagating the Default requirements to the caller:

A B where (Default A) (Default B) => \
instance (Default (A , B)) : (Default , Default)

Great β€” now within the instance, we can assume that Default A and Default B exist, and our code compiles!

However, before today, there was a bug in Wipple that caused the compiler to crash or even allow invalid code to compile. Let’s say we want to infer the second element of the tuple:

my-tuple : Default :: (Number , _) -- instance (Default Number) : 0

Previous versions of Wipple would accept this code, inferring the second element to be Number as well! Logically, this doesn’t make sense β€” within the Default (A , B) instance, A and B have no relation to each other; their Default bounds are separate, so there’s no reason the type of one should be able to determine the type of the other.

Even worse, the information about B’s type wasn’t passed back to the instance, meaning the type of B was still unknown within the instance and no Default implementation was ever found. Due to the order in which Wipple performs type inference, this caused the compiler to crash after typechecking completed, or sometimes even accept code with mismatched types!

So why did this bug occur? When Wipple encounters a trait in expression position, it searches for an instance that’s compatible in the current context. For example, the following code prints X because the instance Show X is chosen over Show Y:

X : type
instance (Show X) : "X"

Y : type
instance (Show Y) : "Y"

value : X
show value -- the input to `show` is a value of type `X`

This works fine because we’re at the top level. But inside the body of a generic constant or instance, we are dealing with abstract type parameters about which no information can be assumed except what is provided by bounds.

show :: A where (Show A) => A -> ()
show : input -> {
-- First, produce a `Text` value using `Show`...
text : Show input

-- Then, display it on the screen.
intrinsic "display" text
}

A generic constant by itself doesn’t ever appear in the final program β€” it only appears in monomorphized form, where the type parameter A is replaced with a concrete type like Number or Text. If we refer to show in the program, the compiler immediately creates a new copy of show’s body where all the type parameters are replaced with placeholders that can be substituted with any type. For example, if we have the following program (ignoring the bounds for a moment):

show :: A => A -> ()
show : <body>

show 3.14

Then the equivalent monomorphized program looks like this:

(<body> :: (_ -> ())) 3.14

And the placeholder is inferred to be Number due to 3.14. You can see this effect more clearly if you assign a generic function to a variable, and then attempt to call the variable with inputs of different types:

monomorphized-show : show
monomorphized-show 3.14
monomorphized-show "Hello, world!"
error:
  β”Œβ”€ test.wpl:3:20
  β”‚
3 β”‚ monomorphized-show "Hello, world!"
  β”‚                    ^^^^^^^^^^^^^^^ expected `Number`, but found `Text`
  β”‚
  = for more information, see https://wipple.dev/playground/?lesson=errors/mismatched-types

The next step is to resolve the bounds. Just like with the body, any type parameters in the bounds are also replaced with placeholders. Bounds are evaluated after inferring the concrete types of the type parameters (unless you mark the type parameter with infer), and once a bound is monomorphized, it is added to the list of available instances.

So let’s go back to our original example and perform monomorphization (I’ll denote the different placeholders with lowercase letters):

-- We have:
A B where (Default A) (Default B) => \
instance (Default (A , B)) : (Default , Default)

-- So when Wipple sees this:
Default :: (Number , _)

-- It generates this:
((Default :: a) , (Default :: b)) :: (Number , _)

After type inference, a is known to be Number and b is still unknown. And here lies the bug: bound instances have a higher priority than declared instances. This means that when searching the list of available instances, we check instance (Default a) and instance (Default b) before checking any instances defined on concrete types. This search is done in the same order as the bounds.

So, because a is Number and Default a is the first bound, there are no other high-priority instances to choose from yet, and we fall back to the declared instance Default Number. We then register the body of Default Number as the body of the Default a bound.

But when searching for a suitable instance Default b, we now have this high-priority Default a bound available! And since we know a is Number, we again choose the Default Number instance and infer b as Number.

Before this bug was fixed, you could actually see the order of bounds checking in the problem. This code compiled:

A B where (Default A) (Default B) => \
instance (Default (A , B)) : (Default , Default)

(Default) :: (Number , _)

But this code did not:

A B where (Default A) (Default B) => \
instance (Default (A , B)) : (Default , Default)

(Default) :: (_ , Number)
error:
  β”Œβ”€ test.wpl:4:2
  β”‚
4 β”‚ (Default) :: (_ , Number)
  β”‚  ^^^^^^^
  β”‚  β”‚
  β”‚  could not determine the type of this expression
  β”‚  this has type `_ , Number`
  β”‚
  = annotate the type with `::`: `:: {%type%}`
  = for more information, see https://wipple.dev/playground/?lesson=errors/unknown-type

The fix for the bug is actually pretty simple β€”Β just wait to add the bounds to the list of available instances until after all bounds have been monomorphized. That way, type inference within bounds can only use the low-priority instances declared for concrete types. Now, Wipple correctly raises an error at compile time!

error:
  β”Œβ”€ test.wpl:4:2
  β”‚
4 β”‚ (Default) :: (Number , _)
  β”‚  ^^^^^^^
  β”‚  β”‚
  β”‚  could not determine the type of this expression
  β”‚  this has type `Number , _`
  β”‚
  = annotate the type with `::`: `:: {%type%}`
  = for more information, see https://wipple.dev/playground/?lesson=errors/unknown-type

I hope this article helped you understand a bit more about how Wipple’s type system works under the hood. As you can see, there are a lot of parts interacting with each other, and things can fail in subtle ways. I’m working on improving Wipple’s automated test suite to catch issues like this β€” as of this writing, there are 75 tests!

If you’re interested in learning more about Wipple’s type system, try exploring this lesson on type-level programming in the Wipple Playground!

October 30, 2023

Learning to read error messages is an important part of learning to code, and I want Wipple’s error messages to be useful to beginners and provide help to fix the code. When I first implemented error reporting in the Wipple Playground, it looked like this:

Screenshot of the Wipple Playground reporting errors

There’s a lot going on here, and it can get overwhelming very quickly! So to make it easier for beginners, I hid all the errors when beginner mode was enabled:

Screenshot of the Wipple Playground reporting errors in beginner mode

Now the user had to hover over each place in the code where an error occurred, but it was still pretty overwhelming (not to mention annoying if you’re just trying to place your cursor).

Screenshot of hovering over a piece of code to reveal the error

In response to these issues, I have redesigned the way errors are displayed in the Wipple Playground! First, I switched out the red for a calmer blue color. By default, only the primary error message is displayed; you can click "Show more" to reveal all the details and the location in the source code. This button is per error message, so you can read about a single issue in more depth without expanding all the other diagnostics. And finally, the fix-it button appears right below the description so it can be easily applied.

Screenshot of the new error design in the Wipple Playground

Here’s a GIF of the new design in action!

Animation showing the new error design

Previously, Wipple’s built-in math operations like / and sqrt would cause the program to crash if provided an invalid input. This caused problems when graphing functions using the math library:

plot (x -> 1 / x) -- crashed when x = 0!

Really, what we want is to skip graphing any points that produce an undefined result. So now, Wipple’s Number type has a new member β€”Β undefined!

Rather than crashing, all of Wipple’s math operations now return undefined if provided an invalid or undefined input. This means undefined propagates through the program:

x : sqrt -1
show x -- undefined
show (x + 1) -- undefined

When comparing undefined with another number, the result is always False. If you need to check whether an number is undefined, you can use undefined?:

x : 0 / 0
show (x = undefined) -- False
show (undefined? x) -- True

This behavior matches the NaN value in the IEEE 754 floating-point standard (Wipple’s Number type has a decimal representation, however), and indeed undefined is represented as NaN in JavaScript.

Last week, I visited Tyngsborough Elementary School in Tyngsborough, Massachusetts to teach Wipple to the 4th and 5th graders in Science and Technology class. Students spent about 30 minutes creating a Turtle drawing using the Wipple Playground. For many students, Wipple was their first text-based programming language, so learning Wipple was also an opportunity to practice typing. As I walked around the classroom, I was amazed by everyone’s creativity!

Teaching Wipple at Tyngsborough Elementary School

On my first day at TES, I showed the students how to use repeat to run a block of code multiple times. I instructed them to copy their code to the clipboard, type repeat (4 times) {}, and paste their code between the braces. I noticed many students found this challenging β€”Β all the keyboard shortcuts were overwhelming them and taking away their attention from the code itself. So this week, I made three changes to make Wipple more mouse-friendly: an Edit menu, snippets, and a new Insert button!

Edit menu

Previously, the code editor in the Wipple Playground operated entirely on keyboard shortcuts. Now, there’s a new Edit menu in the top left of the screen to activate common commands like Copy, Paste, and Select All with your mouse. Keyboard shortcuts are an important part of learning to type, but I want first-timers to be able to write and manipulate short programs quickly without getting overwhelmed. Learning to code is already a challenging task!

Screenshot of the new Edit menu in the Wipple Playground

Snippets

Wipple now supports defining "snippets" that expand to new code or wrap existing code. Here are a few examples:

snippet "Move forward" : forward (50 pixels)

snippet "Show" : show 'code

snippet "Repeat" : repeat (5 times) {
'code
}

These snippets are parsed by the compiler and are available in the analyzed program so IDEs can suggest them. You write snippets alongside your other code, all in the same file, so there’s no setup required. You can also insert a placeholder ('code) to indicate that the snippet wraps the user’s selection.

Insert button

The + button in the top right of the editor now lives right next to your cursor, and suggests snippets based on your selection. Check it out!

Demonstration of the insert button in the Wipple Playground

Once I made these changes, students were able to get up to speed much more quickly and spend more time being creative. I think the best way to learn to code is by applying what you’re already passionate about, and I look forward to bringing Wipple to more students in the near future. I can’t wait to see what they’ll create!

Made by Wilson Gramer