Wipple Home Blog

Blog

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