Wipple Home Blog
Try the new Wipple Playground and give feedback ->

Blog

Previously, Wipple used attributes for custom error messages and other diagnostics. The new compiler doesn’t support attributes, though, so now there’s a new way that uses Wipple’s powerful type system! It looks like this:

-- Produce a piece of `Text` describing the value.
Describe : (Value : Text) => trait (Value -> Text)

Value where (Error ("cannot describe a _ value" Value)) =>
default instance (Describe Value) : ...

instance (Describe Text) : ...

Describe "Hello, world!" -- ✅
Describe (1 , 2 , 3) -- ❌ cannot describe a `List Number` value

Here’s another example:

Carnivore : type
Herbivore : type

Meat : type
Plants : type

Eats? : Animal Food => trait ()

Animal Food where (Error ("_s do not eat _" Animal Food)) =>
default instance (Eats? Animal Food) : ()

instance (Eats? Carnivore Meat) : ()
instance (Eats? Herbivore Plants) : ()

test :: Animal Food where (Eats? Animal Food) => Animal Food -> ()
test : ...

test Carnivore Meat -- ✅
test Herbivore Plants -- ✅
test Carnivore Plants -- ❌ `Carnivore`s do not eat `Plants`
test Herbivore Meat -- ❌ `Herbivore`s do not eat `Meat`

You can even use this system to correct misspellings:

print :: (Value : Text) where (Error "use `show` to display on the screen") => Value -> ()
print : ...

print "Hello, world!" -- ❌ use `show` to display on the screen

Custom error messages are available for testing on the new Wipple Playground — try it out at preview.wipple.dev!

How does it work?

Custom error messages rely on three new features: default instances, type-level Text, and the Error trait. Let’s look at how they work!

First, default is a new keyword that can be applied to an instance to reduce its priority. Wipple will first check all non-default instances, and if none apply, it will use the default instance if available:

Do-Something : A => trait (A -> ())

A => default instance (Do-Something A) : _ -> show "using default instance"
instance (Do-Something Text) : text -> show ("using specific instance: _" text)

Do-Something 42 -- using default instance
Do-Something "Hello, world!" -- using specific instance: Hello, world!

Default instances have two main use cases — first, it allows for specialization, meaning you can "override" the default behavior of a trait with an implementation specific to your type. For example, the count function currently takes a Sequence, but we could instead have it depend on a Count trait. This Count trait would have a default instance that works for all sequences, as well as a specialized implementation for Ranges, Lists, and other collections that already know their size. The other use case, of course, is custom error messages! If none of the other instances match, we can produce a custom error message when Wipple tries to use the default instance.

To make custom error messages work, Wipple now supports text at the type level. Normally, the compiler will display types to the user in code format (ie. Text rather than "Text"). Type-level text is always rendered as-is instead. You can even do formatting with _ placeholders!

And finally, the Error trait looks like this:

Error : Message => trait Message
Message => instance (Error Message) : unreachable

The Wipple compiler knows about Error, and whenever it appears in the where clause of a function or constant, Wipple will produce an error message. That instance (Error Message) on the second line is needed to prevent additional errors from appearing — we want Error itself to be implemented for all types so that the compiler always succeeds in producing a message.

Create your own messages

Putting all of this together, here are the steps to creating your own error messages:

  • If you want to produce a message when a trait isn’t implemented, add a default instance:

    Value where (Error ("_ has no empty value" Value)) =>
    default instance (Empty Value) : ...
  • If you want to produce a message when you encounter a specific type, add a regular instance:

    Right Sum where (Error ("cannot add _ to `()`; are you missing parentheses?" Right)) =>
    instance (Add () Right Sum) : ...
  • If you want to produce an error for a deprecated function or a common misspelling of a function, use Error directly:

    turn :: A where (Error "use `left` or `right` to turn") => Angle -> ()
    turn : ...

I’m excited to announce that the all-new Wipple Playground is available for testing at preview.wipple.dev — try it out and let me know what you think!

Screenshot of new Wipple Playground

The new Wipple Playground is the result of student feedback and features a huge list of improvements. And it’s powered by a new Wipple compiler that’s built for speed. Here are some of the most notable changes…

Save your projects with cloud sync

Screenshot of cloud sync

The new Wipple Playground saves your work in the cloud, so you can come back to a project later. You can even sign in with your Google account, and all of your projects will sync between devices!

Redesigned code editor

Screenshot of code editor

The redesigned code editor has a fresh new look and a ton of handy features:

  • Quick Help: Click on the magnifying glass button, then hover your mouse over any piece of code, and Wipple will tell you what it does.
  • Palette: Drag colors, functions, and other objects and drop them right into your code.
  • Inline errors: When you make a mistake, error messages appear right next to the code that caused the error. Click on the Fix button to fix your code automatically.
  • Insert new lines with your mouse: Click on the top or bottom of the code editor to insert a new line.
  • Resizable Turtle canvas: Drag the bottom-right corner of the canvas to resize it, so you can make larger drawings.
  • Save Turtle drawings as images: Click the camera button to save your drawing as an image!

Multiple pages

Screenshot of page editor

Now you can create multiple pages within a single playground.

Sharing (coming soon)

Soon, you’ll be able to share a link to your playground with others and let them collaborate.

New compiler

Wipple’s new compiler is substantially smaller and faster, and uses incremental compilation to reduce the amount of work it has to do for every program. Basically, instead of scanning through all the code that powers Turtle every time you change your drawing, it will only re-scan your commands and reuse everything else. In fact, the Wipple standard library is now pre-compiled and distributed with the playground!

Try it out!

You can try the new Wipple Playground today at preview.wipple.dev. It’s not finished yet, so if you run into issues, please click the Give Feedback button at the bottom of the screen. Thank you!

I’ve been working on Wipple’s new compiler since January, and I have some updates to share!

Compiler progress

First, the new compiler architecture is almost complete. I’ve split each part of the compiler into their own independent Rust crates. All the crates are generic over a "driver", meaning the compiler can be tested in pieces without having to build an entire executable.

In the Wipple Playground, the compiler itself compiles to WebAssembly, and thanks to ts-rs, it generates TypeScript typings, too! In fact, the interpreter is now written in TypeScript, removing a bunch of complexity that was required to send Wipple values between Rust and JS at runtime.

I am currently in the process of moving the "render diagnostics" phase of the compiler to TypeScript as well. That way, the compiler emits error codes as JSON and the Wipple Playground can render them in any number of formats. This will come in handy in the future when diagnostics need to be localized — it can all be done on the frontend.

More syntax updates

I’ve decided to roll back most of the syntax changes I described in the last update. Comments are once again written with --, and blocks use braces. However, the semantics of blocks have changed under the hood!

Block expressions

With the new compiler, when you wrap some code in a block, that code becomes a block expression whose value is computed later. It’s the same idea as the lazy type described in the last post:

block : {1 + 2}

In the above example, block has type {Number}, representing a piece of code that produces a Number. In order to actually run the code, you pass the block to the do function:

-- Evaluate the provided block, returning its result.
do :: Body => {Body} -> Body

three : do {1 + 2}
show three -- 3

What makes this model powerful is that other functions accept blocks, too, like repeat!

-- Execute a block of code repeatedly.
repeat :: Predicate Body Result where (...) => Predicate -> {Body} -> Result

repeat (3 times) {
show "Hello, world!"
}

Of course, if you just want to run multiple statements in order, you can pass a block to do directly:

greet :: Text -> Text
greet : name -> do {
show ("Hello, _!" name)
prompt "How are you?"
}

I think block expressions are a great way to solve the problem of delayed evaluation in Wipple. Braces almost universally represent code, but most languages have many different rules on where braces are valid. By making blocks a "first-class value" in Wipple and defining functions that accept blocks, there’s now a clear and consistent distinction between control flow and grouping with parentheses. As for whether parentheses are easier to find on the keyboard versus braces, I think the mental model of evaluation this design brings outweighs the initial learning curve of having two different grouping symbols.

Functions

One of the hardest things for people to learn in Wipple is the idea that functions only have one input. As soon as you encounter an error message related to functions, you have to understand how currying works, and the idea that (most of!) Wipple’s functions return other functions is pretty hard to wrap your head around. So, I have decided to lift the restriction — functions can now have multiple inputs!

For example, here’s how add would be defined with currying:

add :: Number -> Number -> Number
add : a b -> a + b

Right away, you have to know that the -> operator groups from right to left in order to read this code. I added the a b -> shorthand for a -> b -> a long time ago, but now, in the new compiler, the type of add has the same pattern as the definition:

add :: Number Number -> Number
add : a b -> a + b

And if you try to call add with the wrong number of inputs, you get a much nicer error message:

Code Before After
add 1 this code doesn't do anything expected 2 inputs, but found 1
add 1 2 3 expected `Number -> _`, but found `Number` expected 2 inputs, but found 3

Currying is still useful, though, especially once you’re introduced to the . operator. (x . f is equivalent to f x.) Most functions in the standard library now accept the "receiving" input as part of a separate function:

Before After
insert-entry :: Key -> Value -> Dictionary -> Dictionary insert-entry :: Key Value -> Dictionary -> Dictionary

In both cases, you call insert-entry like my-dictionary . insert-entry "a" 1 (or alternatively, (insert-entry "a" 1) my-dictionary — the parentheses are now required).

Variables

This one is still experimental, but I have been thinking of replacing mutable with language support for variables. Wipple will still emphasize immutable state, but variables (instead of recursion, for example) can sometimes make code easier to reason about.

The basic idea is that the exclamation mark (!), currently used as a convention for functions that mutate their Mutable input, can be used in a few places to tell Wipple to overwrite a variable’s value, rather than creating a new variable with the same name.

number : 1
number! : 2
show number -- 2

To keep things simple, you can only do this with single variable patterns.

Second, the exclamation mark can be used on a function call with a single variable input to overwrite that variable with the function’s return value:

increment :: Number -> Number
increment : n -> n + 1

number : 1
increment! number
show number -- 2

f! x is equivalent to x! : f x — the value of x is copied into the function, and the function cannot mutate x directly. This is different from the Mutable type, whose values can be freely moved around, stored in structures, etc.

New playground

I am also working on a brand-new Wipple Playground, and I’m excited to share more in the coming months!

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