Blog
September 15, 2024
Introducing a new code editor for the Wipple Playground!
The new editor features a sidebar with a Run button and an improved command palette. It also has all-new error messages, redesigned to be less overwhelming and more helpful.
Run button
Instead of compiling and running code automatically, the new editor has a Run button that students click when they’re ready. As a result, editing code is less distracting, because you can finish typing before errors pop up.
Command palette
The new command palette is displayed permanently in the sidebar, instead of hidden in a dropdown. Commands are categorized, with an option to view all commands as well. Dragging a command into the editor now shows a full preview of the code that will be added, so you know exactly what will change.
Redesigned error messages
Wipple’s new error messages are more friendly, appear one at a time, and include guiding questions to help students fix them on their own.
Not all errors have guiding questions yet — as part of my research at Worcester Polytechnic Institute, I will be improving the messages of and adding guiding questions to almost every error. I will also use the responses to the guiding questions to determine how students are learning!
Print button
Finally, the •••
menu contains a new Print button, so students can export their work as a PDF and take a printed copy home with them!
Conclusion
The new code editor is live at wipple.org. Click Start coding now to open the playground. I’m excited to continue refining the design in response to student feedback!
June 21, 2024
I am proud to announce the release of Wipple 1.0 today! Wipple 1.0 has been four years in the making — since April 2020, I have made over 2,000 changes. Wipple 1.0 brings a new compiler, playground, and website at wipple.org, and I can’t wait for you to try it out.
I started Wipple during high school as a way to teach programming to my friends on the robotics team. Today, it’s grown into a fully-featured educational tool that I’ve had the opportunity to teach to students across Massachusetts. Starting in the fall, as part of my Computer Science degree at Worcester Polytechnic Institute, I will be building a curriculum for teaching computer science principles using Wipple.
Let’s take a look at what’s new in Wipple 1.0!
What’s new?
New compiler
Wipple 1.0 brings a new compiler rewritten for speed and stability. It uses a build system similar to C’s, where .wipple
source files are pre-compiled into .wippleinterface
files (which store types and signatures) and .wipplelibrary
files (which store implementations). Basically, this means you can compile dependencies once, and build your application code against the interface file for super-fast typechecking. Then when you’re ready to run your code, you link it against the library file to produce an executable.
Here’s what that looks like on the command line:
$ cat pi.wipple
pi :: Number
pi : 3.14
$ cat main.wipple
show ("π = _" pi)
$ wipple compile pi.wipple \
--interface pi.wippleinterface \
--library pi.wipplelibrary
$ wipple compile main.wipple \
--dependency pi.wippleinterface \
--library main.wipplelibrary
$ wipple link pi.wipplelibrary main.wipplelibrary -o main
$ ./main
π = 3.14
Of course, the playground handles all of this for you automatically.
New playground
The new Wipple Playground has been redesigned to be familiar and easier to use. It has a Google Docs-like interface, so students can navigate it quickly.
Previously, the Wipple Playground stored all your code in the URL. Now, if you sign in, your code is saved to your Google account and synced across devices!
The new playground also features an updated code editor with significantly improved syntax highlighting and support for dropdowns and other inline widgets. The Add button lets you drag commands into the editor using your mouse, so beginners can make the transition from block coding to typing more seamlessly!
The new Look Up button lets you quickly view documentation for a piece of code just by hovering your mouse over it…
…and clicking on the code brings up a new documentation viewer, complete with examples!
Updated lessons
There are several new lessons available in the playground, too, including a physics course!
Some of the Advanced lessons are still being updated — in the meantime, you can read the Tour of Wipple in the documentation.
What’s next?
Before I start building the curriculum in the fall, I plan to spend the summer thoroughly documenting the Wipple codebase so others can understand how it’s organized and how everything works under the hood. The standard library also needs more documentation and examples for Wipple by Example. Finally, I’ve set up automated code coverage reporting, which will help inform what tests to add next.
Wipple 1.0 does not have a stability guarantee, but I will try my best to limit source-breaking changes between releases from now on.
If you’d like to track Wipple’s development, please visit the GitHub repository!
Conclusion
Thank you to everyone who has offered their feedback and supported me over the last four years. I’m looking forward to the next chapter of Wipple!
March 29, 2024
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 Range
s, List
s, 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 : ...
March 23, 2024
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!
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
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
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
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!
March 11, 2024
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!
January 10, 2024
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:
Within a course, the lessons appear on the left side of the screen for easy browsing:
I’m in the process of revamping the lessons, too, so expect more to change in the coming weeks!
December 16, 2023
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:
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:
Beginner mode on:
December 13, 2023
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.
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!
December 12, 2023
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!
You can enable it in the Wipple Playground settings.
November 6, 2023
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.
November 4, 2023
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:
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:
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).
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.
Here’s a GIF of the new design in action!
October 28, 2023
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.
October 23, 2023
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!
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!
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!
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!